BudgetScan

File descriptor leak when fcntl fails after socket creation

medium swesweep-fasthttp · tcplisten/socket_zos_s390x.go · newSocketCloexec · run #4

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.

Triage

Verdict
INVALID confidence 0.0
Model
qwen/qwen3.8-27b
Rounds
5
Cost
$0.0139 · 45.0 s
The reported bug is a file descriptor leak in `newSocketCloexec` on `zos/s390x`. The code creates a socket, and if subsequent `fcntl` calls fail, it returns an error without closing the socket fd. While this is a real resource leak, the threat model requires an external attacker to trigger the bug via untrusted input. The inputs to this function are `domain`, `typ`, and `proto`, which are integer constants (e.g., `AF_INET`, `SOCK_STREAM`, `IPPROTO_TCP`) determined by the application calling the public API (e.g., `Listen`), not by network payloads. For the leak to occur, `unix.Socket` must succeed (creating a valid fd) while `unix.FcntlInt` fails. On a healthy system, setting standard flags like `O_NONBLOCK` and `FD_CLOEXEC` on a valid socket fd will succeed. Failure of `FcntlInt` is an environmental condition (e.g., kernel bug, severe resource exhaustion, or specific security policy) that an external attacker sending data to the host application cannot control or predict to trigger this specific error path. Since the attacker cannot craft input to force the failure of the system call that triggers the leak, and the inputs themselves are not derived from adversarial data streams, this is a code quality/robustness issue (missing cleanup on rare system errors) rather than a security vulnerability exploitable under the defined threat model.

CRUX: An attacker cannot control the inputs or system conditions to force unix.FcntlInt to fail after unix.Socket succeeds, making the FD leak untriggerable via untrusted input.
GREP: FcntlInt
All rounds
[
  {
    "finding_title": "File descriptor leak when fcntl fails after socket creation",
    "verdict": "INVALID",
    "reasoning": "The code in `tcplisten/socket_zos_s390x.go` has two distinct error handling flaws. First, it ignores the error from `unix.Socket`. If `Socket` fails, `fd` is -1. The code then calls `unix.FcntlInt(-1, ...)`. While failing to check the first error is a bug, the subsequent `FcntlInt` calls on -1 will also fail, leading to an error return. No file descriptor is leaked in this specific path because no valid fd was created. Second, if `Socket` succeeds (fd > 0) but the first `FcntlInt` (F_SETFD) fails, the code overwrites `err` with the second `FcntlInt` (F_SETFL) call. If the second call succeeds (or even if it fails, the key is that `err` is not nil at the end), the function returns an error. The code does not call `unix.Close(fd)` before returning the error. This is a real file descriptor leak. \n\nHowever, we must evaluate reachability and threat model. The function `newSocketCloexec` is unexported. It is called by exported constructors (e.g., `Listen`). An attacker using the public API can trigger the creation of a socket. For the leak to occur, `unix.FcntlInt` must fail after `unix.Socket` succeeds. `FcntlInt` typically fails if the fd is invalid or permissions are insufficient. On a standard system, setting flags on a newly created socket should succeed. Failure would require a very specific environmental condition (e.g., kernel bug, resource exhaustion, or specific security policy). \n\nMore importantly, the threat model states: \"Not a vulnerability under this model: ... misuse that violates a documented precondition of the public API.\" and \"A crash, memory corruption or unbounded resource use reachable through the public API with a crafted payload is VALID.\" \n\nCan an attacker *craft a payload* to make `FcntlInt` fail? No. The inputs are just domain, type, protocol. These are standard constants. There is no data stream that can be manipulated to cause `fcntl` to fail on a valid socket in a way that is controllable by an external network attacker. The failure is an environmental/system error, not a result of adversarial input. Therefore, this is not a security vulnerability exploitable by an attacker sending malicious data. It is a code quality issue (resource leak on rare system errors) that does not meet the bar of being reachable via untrusted input causing meaningful harm. Additionally, the specific platform is `zos && s390x`, which is highly niche. Even if valid on other platforms, the lack of attacker-controllability for the failure condition makes it INVALID under the strict threat model of 'attacker triggers it'.\n\nCRUX: Whether an external attacker can control the inputs or conditions to make unix.FcntlInt fail after unix.Socket succeeds, thereby triggering the FD leak.\nGREP: FcntlInt",
    "elapsed": 10.3,
    "tokens": 2619,
    "tokens_in": 1968,
    "tokens_out": 651,
    "cost_usd": 0.00277956,
    "file": "tcplisten/socket_zos_s390x.go",
    "round": 1,
    "grep_used": true,
    "grep_results": "GREP `FcntlInt`:\n```\ntcplisten/socket_zos_s390x.go:13:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFD, unix.FD_CLOEXEC)\ntcplisten/socket_zos_s390x.go:14:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFL, unix.O_NONBLOCK)\n```"
  },
  {
    "finding_title": "File descriptor leak when fcntl fails after socket creation",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a file descriptor leak where `newSocketCloexec` fails to close the socket if `unix.FcntlInt` returns an error after `unix.Socket` succeeds. While the bug pattern (leaked FD) is real in the code, we must determine if an attacker can trigger it. The threat model for a library defines the attacker as code calling the public API and the data it passes. The entry point is `newSocketCloexec(domain, typ, proto)`, likely called by `Listen` or similar. The inputs `domain`, `typ`, and `proto` are integers. On z/OS s390x, `unix.Socket` will fail (returning -1) if these values are invalid, which leads to a safe error return (no FD leak) because the subsequent `FcntlInt` calls will also fail or the first one fails and the logic exits with error. If the values are valid, `unix.Socket` succeeds. The subsequent `unix.FcntlInt` calls set standard flags (`F_CLOEXEC`, `O_NONBLOCK`). An attacker cannot control the system's ability to set these flags on a newly created socket. `FcntlInt` failure here is dependent on environmental factors (kernel bugs, specific security policies like SELinux/LDAP constraints, or resource exhaustion), not on the content of the network payload or the specific integer values passed (as valid integers result in success). Since the attacker cannot craft input to force `FcntlInt` to fail on a valid socket, the leak is not triggerable via untrusted input. It is a robustness/code quality issue, not a security vulnerability under the provided threat model.\n\nCRUX: Whether an attacker can control the inputs or conditions to make unix.FcntlInt fail after unix.Socket succeeds.\nGREP: FcntlInt",
    "elapsed": 6.5,
    "tokens": 3219,
    "tokens_in": 2829,
    "tokens_out": 390,
    "cost_usd": 0.00235818,
    "file": "tcplisten/socket_zos_s390x.go",
    "round": 2,
    "grep_used": true,
    "grep_results": "GREP `FcntlInt`:\n```\ntcplisten/socket_zos_s390x.go:13:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFD, unix.FD_CLOEXEC)\ntcplisten/socket_zos_s390x.go:14:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFL, unix.O_NONBLOCK)\n```"
  },
  {
    "finding_title": "File descriptor leak when fcntl fails after socket creation",
    "verdict": "INVALID",
    "reasoning": "The code in `tcplisten/socket_zos_s390x.go` contains a genuine logic error: if `unix.Socket` succeeds but a subsequent `unix.FcntlInt` fails, the function returns an error without closing the file descriptor, leading to a leak. However, the threat model for a library requires that an external attacker can trigger the bug via untrusted input (parsers, decoders, etc.) to cause harm. The function `newSocketCloexec` takes integer arguments `domain`, `typ`, and `proto`. Even if an attacker could influence these values (e.g., via a configuration file or API parameter that maps to these syscalls), passing valid values results in `unix.Socket` succeeding and `unix.FcntlInt` (which sets standard flags like `FD_CLOEXEC` and `O_NONBLOCK`) also succeeding on a healthy system. Passing invalid values causes `unix.Socket` to fail, preventing any leak. There is no input data (like a network packet or file content) that can cause `unix.Socket` to succeed but `unix.FcntlInt` to fail on the resulting valid descriptor. The failure of `FcntlInt` depends on environmental factors (e.g., kernel resource exhaustion, specific OS policies) which are not controllable by an attacker sending adversarial payloads. Therefore, this is a code quality/robustness issue (resource leak on system error) rather than a security vulnerability exploitable via untrusted input.\n\nCRUX: Whether an attacker can control the input to cause unix.Socket to succeed while unix.FcntlInt fails, thereby triggering the FD leak.\nGREP: FcntlInt",
    "elapsed": 13.0,
    "tokens": 3652,
    "tokens_in": 3301,
    "tokens_out": 351,
    "cost_usd": 0.0024394199999999994,
    "file": "tcplisten/socket_zos_s390x.go",
    "round": 3,
    "grep_used": true,
    "grep_results": "GREP `FcntlInt`:\n```\ntcplisten/socket_zos_s390x.go:13:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFD, unix.FD_CLOEXEC)\ntcplisten/socket_zos_s390x.go:14:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFL, unix.O_NONBLOCK)\n```"
  },
  {
    "finding_title": "File descriptor leak when fcntl fails after socket creation",
    "verdict": "INVALID",
    "reasoning": "The reported vulnerability is a file descriptor leak in `newSocketCloexec` (specifically in `socket_zos_s390x.go`). The code creates a socket using `unix.Socket`, and if this succeeds, it attempts to set flags using `unix.FcntlInt`. If `FcntlInt` fails, the function returns an error but does not close the file descriptor, leading to a leak. \n\nTo determine if this is a security vulnerability, we must assess reachability under the specified threat model. The threat model defines the attacker as code calling the public API and the data it passes. The function `newSocketCloexec` is unexported and is called by public constructors (like `Listen`) with integer arguments `domain`, `typ`, and `proto`. These integers correspond to standard socket family, type, and protocol constants (e.g., `AF_INET`, `SOCK_STREAM`, `IPPROTO_TCP`). \n\nFor the leak to occur, `unix.Socket` must succeed, but `unix.FcntlInt` must fail. \n1. If the attacker passes invalid `domain`, `typ`, or `proto`, `unix.Socket` will fail, and no fd is created, so no leak occurs. \n2. If the attacker passes valid values, `unix.Socket` will succeed on a functioning OS. The subsequent `FcntlInt` calls attempt to set `FD_CLOEXEC` and `O_NONBLOCK`. These operations are almost always successful on a valid file descriptor in a standard operating environment. \n\nThere is no adversarial payload (network data, file content, etc.) that an attacker can send to a library to cause `unix.FcntlInt` to fail on a valid socket handle. Failure of `FcntlInt` in this context is an environmental condition (e.g., severe resource exhaustion, kernel bug, or specific security policy denial) rather than a result of untrusted input processing. Since the attacker cannot control the input to trigger this specific failure path, the condition is not reachable via untrusted input in the manner defined by the threat model. It is a robustness or code quality issue (handling of rare system errors), not a security vulnerability exploitable by an external attacker.\n\nFurthermore, the file is specific to `zos && s390x`, making it highly niche, but the core logic flaw regarding input controllability remains the deciding factor. Without the ability to force the error condition via malicious data, it does not meet the bar for a security vulnerability.\n\nCRUX: An external attacker cannot control the input to cause unix.Socket to succeed while unix.FcntlInt fails, as the inputs are standard constants and FcntlInt failure depends on environmental factors, not untrusted data.\nGREP: newSocketCloexec",
    "elapsed": 9.2,
    "tokens": 4320,
    "tokens_in": 3734,
    "tokens_out": 586,
    "cost_usd": 0.00332628,
    "file": "tcplisten/socket_zos_s390x.go",
    "round": 4,
    "grep_used": true,
    "grep_results": "GREP `newSocketCloexec`:\n```\ntcplisten/socket_other.go:11:func newSocketCloexec(domain, typ, proto int) (int, error) {\ntcplisten/socket_other.go:18:\t\treturn newSocketCloexecOld(domain, typ, proto)\ntcplisten/socket_zos_s390x.go:11:func newSocketCloexec(domain, typ, proto int) (int, error) {\ntcplisten/socket.go:12:func newSocketCloexecOld(domain, typ, proto int) (int, error) {\ntcplisten/tcplisten.go:59:\tfd, err := newSocketCloexec(soType, unix.SOCK_STREAM, unix.IPPROTO_TCP)\ntcplisten/socket_darwin.go:5:var newSocketCloexec = newSocketCloexecOld\n```"
  },
  {
    "finding_title": "File descriptor leak when fcntl fails after socket creation",
    "verdict": "INVALID",
    "reasoning": "The reported bug is a file descriptor leak in `newSocketCloexec` on `zos/s390x`. The code creates a socket, and if subsequent `fcntl` calls fail, it returns an error without closing the socket fd. While this is a real resource leak, the threat model requires an external attacker to trigger the bug via untrusted input. The inputs to this function are `domain`, `typ`, and `proto`, which are integer constants (e.g., `AF_INET`, `SOCK_STREAM`, `IPPROTO_TCP`) determined by the application calling the public API (e.g., `Listen`), not by network payloads. For the leak to occur, `unix.Socket` must succeed (creating a valid fd) while `unix.FcntlInt` fails. On a healthy system, setting standard flags like `O_NONBLOCK` and `FD_CLOEXEC` on a valid socket fd will succeed. Failure of `FcntlInt` is an environmental condition (e.g., kernel bug, severe resource exhaustion, or specific security policy) that an external attacker sending data to the host application cannot control or predict to trigger this specific error path. Since the attacker cannot craft input to force the failure of the system call that triggers the leak, and the inputs themselves are not derived from adversarial data streams, this is a code quality/robustness issue (missing cleanup on rare system errors) rather than a security vulnerability exploitable under the defined threat model.\n\nCRUX: An attacker cannot control the inputs or system conditions to force unix.FcntlInt to fail after unix.Socket succeeds, making the FD leak untriggerable via untrusted input.\nGREP: FcntlInt",
    "elapsed": 6.0,
    "tokens": 4847,
    "tokens_in": 4488,
    "tokens_out": 359,
    "cost_usd": 0.00296196,
    "file": "tcplisten/socket_zos_s390x.go",
    "round": 5,
    "grep_used": true,
    "grep_results": "GREP `FcntlInt`:\n```\ntcplisten/socket_zos_s390x.go:13:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFD, unix.FD_CLOEXEC)\ntcplisten/socket_zos_s390x.go:14:\t_, err = unix.FcntlInt(uintptr(fd), unix.F_SETFL, unix.O_NONBLOCK)\n```"
  }
]
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."
  }
]
```