From 31e9ef21c0163eae8c1308b3deb92144407cc37c Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Tue, 1 Oct 2024 16:27:26 +0400 Subject: [PATCH] fix: add CLOEXEC flag to socket creates directly Go runtime adds `CLOEXEC` flags automatically, but when using raw syscalls, we have to do it manually. Without this flag, file descriptors leak to the child processes. Signed-off-by: Andrey Smirnov --- dhcpv4/client4/client.go | 5 +++-- dhcpv4/server4/conn_unix.go | 4 +++- dhcpv6/server6/conn_unix.go | 4 +++- internal/xsocket/xsocket.go | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 internal/xsocket/xsocket.go diff --git a/dhcpv4/client4/client.go b/dhcpv4/client4/client.go index 253bf286..12700773 100644 --- a/dhcpv4/client4/client.go +++ b/dhcpv4/client4/client.go @@ -10,6 +10,7 @@ import ( "time" "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/internal/xsocket" "golang.org/x/net/ipv4" "golang.org/x/sys/unix" ) @@ -77,7 +78,7 @@ func MakeRawUDPPacket(payload []byte, serverAddr, clientAddr net.UDPAddr) ([]byt // makeRawSocket creates a socket that can be passed to unix.Sendto. func makeRawSocket(ifname string) (int, error) { - fd, err := unix.Socket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW) + fd, err := xsocket.CloexecSocket(unix.AF_INET, unix.SOCK_RAW, unix.IPPROTO_RAW) if err != nil { return fd, err } @@ -123,7 +124,7 @@ func htons(v uint16) uint16 { } func makeListeningSocketWithCustomPort(ifname string, port int) (int, error) { - fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_DGRAM, int(htons(unix.ETH_P_IP))) + fd, err := xsocket.CloexecSocket(unix.AF_PACKET, unix.SOCK_DGRAM, int(htons(unix.ETH_P_IP))) if err != nil { return fd, err } diff --git a/dhcpv4/server4/conn_unix.go b/dhcpv4/server4/conn_unix.go index 18dd9866..802b774b 100644 --- a/dhcpv4/server4/conn_unix.go +++ b/dhcpv4/server4/conn_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package server4 @@ -9,6 +10,7 @@ import ( "os" "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/insomniacslk/dhcp/internal/xsocket" "golang.org/x/sys/unix" ) @@ -17,7 +19,7 @@ import ( // // The interface must already be configured. func NewIPv4UDPConn(iface string, addr *net.UDPAddr) (*net.UDPConn, error) { - fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_UDP) + fd, err := xsocket.CloexecSocket(unix.AF_INET, unix.SOCK_DGRAM|unix.SOCK_CLOEXEC, unix.IPPROTO_UDP) if err != nil { return nil, fmt.Errorf("cannot get a UDP socket: %v", err) } diff --git a/dhcpv6/server6/conn_unix.go b/dhcpv6/server6/conn_unix.go index fd45ce4b..ef111ad7 100644 --- a/dhcpv6/server6/conn_unix.go +++ b/dhcpv6/server6/conn_unix.go @@ -1,3 +1,4 @@ +//go:build !windows // +build !windows package server6 @@ -9,6 +10,7 @@ import ( "os" "github.com/insomniacslk/dhcp/interfaces" + "github.com/insomniacslk/dhcp/internal/xsocket" "golang.org/x/sys/unix" ) @@ -18,7 +20,7 @@ import ( // // The interface must already be configured. func NewIPv6UDPConn(iface string, addr *net.UDPAddr) (*net.UDPConn, error) { - fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) + fd, err := xsocket.CloexecSocket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) if err != nil { return nil, fmt.Errorf("cannot get a UDP socket: %v", err) } diff --git a/internal/xsocket/xsocket.go b/internal/xsocket/xsocket.go new file mode 100644 index 00000000..79b674c2 --- /dev/null +++ b/internal/xsocket/xsocket.go @@ -0,0 +1,36 @@ +package xsocket + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +// CloexecSocket creates a new socket with the close-on-exec flag set. +// +// If the OS doesn't support the close-on-exec flag, this function will try a workaround. +func CloexecSocket(domain, typ, proto int) (int, error) { + fd, err := unix.Socket(domain, typ|unix.SOCK_CLOEXEC, proto) + if err == nil { + return fd, nil + } + + if err == unix.EINVAL || err == unix.EPROTONOSUPPORT { + // SOCK_CLOEXEC is not supported, try without it, but avoid racing with fork/exec + syscall.ForkLock.RLock() + + fd, err = unix.Socket(domain, typ, proto) + if err != nil { + syscall.ForkLock.RUnlock() + return -1, err + } + + unix.CloseOnExec(fd) + + syscall.ForkLock.RUnlock() + + return fd, nil + } + + return fd, err +}