• src/sbbs3/xtrn.cpp src/xp

    From Rob Swindell (on Windows@VERT to Git commit to main/sbbs/m on Wednesday, June 24, 2026 01:09:00
    https://gitlab.synchro.net/main/sbbs/-/commit/6816ce611ac542129603d4ac
    Modified Files:
    src/sbbs3/xtrn.cpp src/xpdev/multisock.c
    Log Message:
    Don't leak server listen sockets to spawned child processes (Windows)

    On Windows, sockets are inheritable by default and the timed-event / native external CreateProcess() path passed bInheritHandles=TRUE unconditionally, so every spawned child (a jsexec timed event, a native door, a Web Server CGI) inherited open handles to all server listen sockets. A long-lived child (e.g. a chat_llm_irc.js timed event) would then keep those sockets bound in the kernel after the parent (sbbsctrl/sbbscon) exited, leaving a ghost process that owned every Synchronet port and silently dropped incoming connections of all protocols.

    Two complementary fixes:

    - multisock.c: mark every listen socket (and accepted client socket)
    non-inheritable via SetHandleInformation(HANDLE_FLAG_INHERIT, 0) right after
    creation. This is the shared listen-socket path for all servers
    (Terminal/Mail/FTP/Web/Services), so children can no longer inherit a listen
    socket regardless of any CreateProcess inheritance flag.

    - xtrn.cpp external(): only pass bInheritHandles=TRUE when the child actually
    needs to inherit a handle we're sharing - the redirected stdio pipes
    (use_pipes) or the duplicated passthru/client socket that a native socket-door
    talks over. A timed event running jsexec shares neither and now gets FALSE.
    DOS doors communicate via named mailslots/events and need no inheritance.

    Fixes #1151.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From Rob Swindell (on Debian L@VERT to Git commit to main/sbbs/m on Saturday, August 08, 2026 17:53:00
    https://gitlab.synchro.net/main/sbbs/-/commit/b16969edb8278a6a62307343
    Modified Files:
    src/sbbs3/xtrn.cpp src/xpdev/filewrap.c filewrap.h
    Log Message:
    Close every descriptor an external program has no business with (#1174)

    Marking descriptors close-on-exec as they are created can only ever cover
    the ones we create. It reaches neither the descriptors a library opens
    behind our back - the four sockets libmosquitto holds to the MQTT broker are the plain example - nor the roughly 1700 open()/fopen() call sites in this tree, each of which would otherwise have to be found, flagged, and kept
    flagged forever.

    Close them in the child instead, where the set is knowable without an audit: after the redirections are in place and immediately before exec, everything above stderr goes, except the passthru socket a native socket-door is handed
    by number. An external now starts with stdio and, at most, that one socket. Previously it started with the node.dab handles, the node log, the client sockets and whatever else happened to be open at the moment it was launched.

    xp_close_inherited_fds() prefers close_range(2) and falls back to a loop over sysconf(_SC_OPEN_MAX). Between fork() and exec() in a process with other threads running, only async-signal-safe calls are legal, which rules out walking /proc/self/fd - readdir() allocates.

    The exec-failure message now goes to stderr directly. lprintf() cannot be trusted at that point: it is not fork-safe, and its descriptors are among
    those just closed. Stderr at that point is the pipe the parent already reads and logs, so the message still reaches the log it always did.

    This covers externals only. The CGI and self-restart exec paths in
    websrvr.cpp and js_global.cpp need the same treatment.

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net