F_SETFD error can be masked and return a socket without FD_CLOEXEC
low swesweep-fasthttp · tcplisten/socket_zos_s390x.go · newSocketCloexec · run #4
Description
The error from the first FcntlInt(F_SETFD) call is overwritten by the second FcntlInt(F_SETFL) call. If F_SETFD fails but F_SETFL succeeds, err is nil and the function returns a valid socket without close-on-exec. This can allow the descriptor to be inherited by unrelated child processes if the host process forks or execs.
Triage
Not triaged.
Raw scan report for this file
This file is a zOS/s390x-only helper in `tcplisten`. It has a narrow, indirect attack surface: `newSocketCloexec` is unexported, and its inputs are `domain`, `typ`, and `proto`, plus the kernel-returned file descriptor from `unix.Socket`. There are no pointers, slices, fixed-size buffers, integer arithmetic, tagged unions, goroutines, or `unsafe` usage in this function. The main security concerns are therefore syscall error handling, file-descriptor lifecycle, and incorrect socket-flag state rather than memory corruption.
Step-by-step:
1. `unix.Socket(domain, typ, proto)` is fallible. On failure it typically returns `-1` and an error. The code does not check `err` before using `fd`, so it proceeds to call `unix.FcntlInt` on an invalid descriptor. On zOS this is likely harmless because the kernel returns `EBADF`, but it masks the original socket-creation error and is incorrect error handling.
2. The first `unix.FcntlInt` call attempts to set `FD_CLOEXEC`. Its error is stored in `err`, but it is not checked before the second `FcntlInt` call. If the first call fails and the second call succeeds, `err` becomes nil and the function returns a valid socket without close-on-exec. That can allow the socket descriptor to be inherited by unrelated child processes if the host process forks/execs.
3. The second `unix.FcntlInt` call attempts to set `O_NONBLOCK`. If it fails after `unix.Socket` succeeded, the function returns an error but does not close the newly created socket file descriptor. This leaks one fd per failed call. If a host application repeatedly invokes the public listener API in response to untrusted input, this could contribute to file-descriptor exhaustion and denial of service.
4. Invalid `domain`, `typ`, or `proto` values are passed directly to the OS. In Go/syscall this normally results in a failed syscall rather than memory unsafety. Because the caller in this package appears to use fixed constants, direct external control is unlikely.
```json
[
{
"severity": "medium",
"title": "File descriptor leak when fcntl fails after socket creation",
"function": "newSocketCloexec",
"description": "If unix.Socket succeeds but a subsequent unix.FcntlInt call fails, newSocketCloexec returns an error without closing the newly created socket fd. Repeated failed listener creation can leak descriptors, eventually exhausting the host process file descriptor table and causing denial of service."
},
{
"severity": "low",
"title": "F_SETFD error can be masked and return a socket without FD_CLOEXEC",
"function": "newSocketCloexec",
"description": "The error from the first FcntlInt(F_SETFD) call is overwritten by the second FcntlInt(F_SETFL) call. If F_SETFD fails but F_SETFL succeeds, err is nil and the function returns a valid socket without close-on-exec. This can allow the descriptor to be inherited by unrelated child processes if the host process forks or execs."
},
{
"severity": "low",
"title": "FcntlInt is called on an invalid fd when socket creation fails",
"function": "newSocketCloexec",
"description": "The error returned by unix.Socket is not checked before using fd. On socket creation failure, fd is typically -1, and the code still invokes FcntlInt with uintptr(fd). This is usually benign because the kernel rejects the invalid descriptor, but it hides the original socket error and can produce misleading diagnostic information."
}
]
```