open
https://gitlab.synchro.net/main/sbbs/-/issues/1174
## Summary
Follow-up to #1151 (the Windows listen-socket inheritance leak, fixed in commit 6816ce611). That fix addressed Windows handle inheritance; the equivalent **POSIX `fork`/`exec` close-on-exec story should be audited and hardened separately**, as flagged in the original report.
On POSIX, a child created by `fork()` inherits **every** open file descriptor of the parent. Unless those fds are marked close-on-exec (`FD_CLOEXEC` / `O_CLOEXEC` / `SOCK_CLOEXEC`) or explicitly closed in the child before `exec`, the exec'd program inherits them — including all server listen sockets, the active client socket, message-base fds, SpiderMonkey/cryptlib fds, etc. This is the direct analog of the Windows leak: a long-lived external (a timed-event door, a CGI) could keep server listen sockets bound after the parent exits.
## Current state
- **Zero** uses of `O_CLOEXEC`, `FD_CLOEXEC`, or `SOCK_CLOEXEC` across the entire `src/` tree (`git grep` returns nothing).
- `sbbs3/xtrn.cpp` `external()` POSIX path: after `fork()` (xtrn.cpp:1021) the child sets up stdio redirection via `dup2()` and redirects unused stdio to `/dev/null`, then `execvp()`s (xtrn.cpp:1862) **without closing any other inherited descriptors** — no `closefrom()`, no fd-range close loop. So the door inherits all of the parent's other fds.
- `sbbs3/js_global.cpp:52` `execv("/proc/self/exe", ...)` (self-restart) has the same exposure.
- Listen sockets are created in the shared `xpdev/multisock.c` path (`socket()` at `xpms_add`) with no `SOCK_CLOEXEC`.
## Suggested approach
Mirror the Windows fix's "close at the source" strategy, which is more robust than per-exec cleanup:
1. **Create long-lived fds close-on-exec.** Set `SOCK_CLOEXEC` when creating listen/accept sockets in `multisock.c` (and `accept4(..., SOCK_CLOEXEC)` where available), and `O_CLOEXEC` on long-lived `open()`s. Where the platform lacks the atomic flag, fall back to `fcntl(fd, F_SETFD, FD_CLOEXEC)` immediately after creation.
2. **Belt-and-suspenders in the child**, for fds we don't control: after `fork()` and after the intended `dup2()` redirections, `closefrom(3)` (or a portable fd-range close) before `exec`, keeping only 0/1/2 and any fd the door is explicitly meant to receive (e.g. a socket-handle door passed its descriptor).
Care is needed not to break doors that are *intended* to inherit a specific descriptor (the POSIX equivalent of the Windows passthru/`client_socket_dup` socket-handle door) — those fds must be exempted from the close.
## Platform
POSIX (Linux/macOS/*BSD). The Windows half is resolved by #1151 / 6816ce611.
— *Authored by Claude (Claude Code), on behalf of @rswindell*
--- SBBSecho 3.37-Linux
* Origin: Vertrauen - [vert/cvs/bbs].synchro.net (1:103/705)