Audit report AR-67

Reported privately in an independent security review, 2026-09-27.

Report date
2026-09-27
Kind
Hardening
Status on master
Present
Proposed issue state
Closed

Summary

In Bitcoin Core's copy of libmultiprocess, SpawnProcess reports success even when the child process fails to start its program, so the caller sees only a dropped connection instead of the real error, such as "No such file or directory". The report is accurate for Bitcoin Core master and says itself that current libmultiprocess already fixed it, in mp#312; Bitcoin Core gets the fix with its next update of the library. The effect is a less useful error message, not a crash or hang, so it has no bug page of its own.

The report

SpawnProcess creates a socket pair, forks, and returns the parent's socket and the child's pid without learning whether the child's execvp succeeded. If it failed, the child prints the error on its own stderr and exits with status 127, and the caller is left with a connection to a process that has already exited. If closing a descriptor fails in the parent after the fork, SpawnProcess throws without waiting for the child, which can leave it as a zombie until the parent exits. A libmultiprocess test spawns /nonexistent/binary and expects SpawnProcess to throw with ENOENT.

The report rates it low. The trigger is a local launch failure (a wrong path or permissions); fixing the path and retrying restores operation. No crash, funds effect or lasting resource use was shown.

Proposed change

The libmultiprocess fix adapted to the older interface in Bitcoin Core's copy: a second close-on-exec socket pair over which the child reports which call failed and its errno. A successful exec closes the child's end, so the parent reads end-of-file and returns as before; otherwise it reads the report, kills and reaps the child, and throws the child's error. The parent also kills and reaps the child before throwing on its own errors after the fork, and closes all descriptors if fork() fails. The report says a public Bitcoin Core change should come from a normal update of the library rather than a local patch.

Assessment

Accurate, and already fixed upstream. In Bitcoin Core master's copy of SpawnProcess the parent returns right after fork(), and the child calls perror after a failed execvp. mp#312 (merged 2026-08-11) added the error-reporting socket and the kill-and-reap helper, and the report's patch follows it closely. Bitcoin Core's copy of the library was last updated on 2026-07-14, before that.

In Bitcoin Core the practical effect is small. SpawnProcess is reached only through the test-only echoipc RPC; if the program cannot start, the first IPC call fails with a disconnect error instead of the exec error, and the normal cleanup still waits for the exited child, so no zombie is left. The parent-side close() failure would need closing a freshly created socket to fail, which does not happen in practice.

The same upstream fix also removed two real hazards on this code path, which the report does not discuss: the child's perror call after a failed execvp is unsafe after fork() in a multithreaded process and could deadlock the child (bug page), and a failed fork() leaked the socket pair (bug page). Both end in Bitcoin Core with the same library update. Related reports on the same function: AR-12, AR-45, AR-70.

Status

Fixed in libmultiprocess master by mp#312; present in Bitcoin Core master until its libmultiprocess copy is updated. Nothing further needed in libmultiprocess.

Reported privately in an independent security review on 2026-09-27.