Audit report AR-18
Reported privately in an independent security review, 2026-08-16.
- Report date
- 2026-08-16
- Kind
- Build
- Status on master
- Present
- Proposed issue state
- Open
Summary
The depends build decides whether to regenerate its archive of the local libmultiprocess source by comparing file modification times, so an existing archive with a future timestamp is reused instead of the current src/ipc/libmultiprocess tree. A depends build can then silently build the mpgen code generator from stale or substituted source. The report is correct. It needs someone who can write the depends source cache, or a leftover archive with a skewed clock; it is a build-cache integrity problem, not an IPC runtime bug.
The report
Depends treats a package with a $(package)_local_dir as a local source: it tars the directory into sources/, and the archive's hash becomes part of the package's build identity. native_libmultiprocess is such a package ($(package)_local_dir=../src/ipc/libmultiprocess). The shell fragment fetch_local_dir_sha256 in depends/funcs.mk rebuilds the archive only if it is missing or some path in the source directory is newer than it:
if ! [ -f $($(1)_source) ] || [ -n "$$(find $($(1)_local_dir) -newer $($(1)_source) | head -n1)" ]; then \
A retained native_libmultiprocess archive with a future timestamp therefore wins over the checked-out tree. Depends hashes that archive into the package identity and later extracts and builds it as the native mpgen dependency.
The report traces the local package flow to bitcoin#31741 (Bitcoin Core 30.0) and finds the timestamp check in releases 30.0 through 30.3 and 31.0 through 31.1 and on master. Preconditions it lists: filesystem control over the SOURCES_PATH used by the victim's depends build, an archive newer than every path in the tree, IPC enabled (the depends default outside Windows), and a build that does not clean the source cache. The result is one build that uses the retained archive as both source and identity, and cached packages that stay wrong until the depends state is cleaned. It says an attacker-chosen archive could plausibly change native build steps or mpgen output, but that neither was demonstrated, and that no write path into an official cache, changed binary, release or signing impact was established. A cache writable only by the builder prevents the condition.
The reproduction is a shell script, depends/test-local-source-archive.sh, that runs the real depends Makefile with a temporary SOURCES_PATH, plants a future-dated archive containing a stale marker, and checks that the fixed code replaces it with current-tree content. Its --reuse-existing-mutation mode imitates the old cache hit by skipping the second evaluation, so it does not run the old code as a passing test of the bug.
Proposed change
Regenerate the archive from the current directory every time the package identity is evaluated, write it to a temporary file next to the target, and move it into place atomically before hashing:
define fetch_local_dir_sha256
- if ! [ -f $($(1)_source) ] || [ -n "$$(find $($(1)_local_dir) -newer $($(1)_source) | head -n1)" ]; then \
- mkdir -p $(dir $($(1)_source)) && \
- ( ... $(build_TAR) --no-recursion -c -f $($(1)_source) -T - ) && \
- rm -f $($(1)_fetched); \
- fi && \
+ mkdir -p $(dir $($(1)_source)) && \
+ archive_tmp="$$(mktemp $($(1)_source).new.XXXXXX)" && \
+ trap 'rm -f "$$archive_tmp"' EXIT && \
+ ( ... $(build_TAR) --no-recursion -c -f "$$archive_tmp" -T - ) && \
+ mv -f "$$archive_tmp" $($(1)_source) && \
+ trap - EXIT && \
+ rm -f $($(1)_fetched) && \
Removing the checksum stamp forces a fresh hash. depends/packages.md is updated to say local archives are always regenerated, and the test script above is added. The report lists its own limits: this does not authenticate a source cache shared with another user (a concurrent writer can still swap the archive after the move), it adds archive creation to every evaluation, and the script is not yet wired into a test target.
Assessment
Correct as described; the find ... -newer check is on Bitcoin Core master, unchanged. It applies to any local-directory package, of which libmultiprocess is currently the only one. The more likely real-world trigger is not a malicious archive but a stale one: a restored or copied sources/ directory, or clock skew, leaving an archive dated after later edits to the tree. The effect is then a build of mpgen from old source, which could fail confusingly or generate code that does not match the headers in the tree.
No runtime IPC behaviour is involved, and nothing here is reachable from an IPC peer. It is outside the survey's stability scope and has no bug page.
Rewriting the cache logic to regenerate on every evaluation is more than the finding needs. A smaller alternative is to key reuse on content (for example a hash of the tree) instead of timestamps.
Status
Present on master. Next step: a Bitcoin Core depends change, either as proposed or content-based. No public issue or PR yet.
Reported privately in an independent security review on 2026-08-16.