Audit report AR-10
Reported privately in an independent security review, 2026-08-07.
- Report date
- 2026-08-07
- Kind
- Hardening
- Status on master
- Present
- Proposed issue state
- Open
Summary
libmultiprocess reads C++ enum values off the wire with a plain static_cast, without checking that the integer matches a declared enumerator. Code that switches over the enum can then see a value no local caller could produce. The report is accurate about the code. A well-behaved peer never sends such a value, and for most enum types the cast itself is well defined, so this is hardening.
The report
The enum codec in include/mp/type-number.h does:
static_cast<LocalType>(input.get())
without checking whether the value corresponds to a declared enumerator. Downstream exhaustive switch statements and invariants may then receive states that are impossible in local C++ calls. The report lists invalid enum values among its suggested regression tests and gives no reproduction.
Proposed change
Run a generated or customizable validity check before constructing the enum value. No code.
Assessment
The code is unchanged on libmultiprocess master: return read_dest.construct(static_cast<LocalType>(input.get()));, with a comment explaining a suppressed clang-tidy out-of-range warning for enums without a zero value.
What an out-of-range value does depends on the enum:
- For
enum classand enums with a fixed underlying type, every value of the underlying type is a valid enum value in C++. The cast is well defined, and the risk is only logic that assumes one of the named values. - For unscoped enums without a fixed underlying type, a value outside the enum's range is undefined behaviour.
Bitcoin Core's schemas carry C++ enums as integer fields, not as Cap'n Proto enums. The static_assert on enum size in BuildPrimitive, added by mp#120, guards the build side only. A peer built by the same code never sends an unlisted value. A peer built from a newer schema could send a newer enumerator, which is a compatibility question worth deciding explicitly: reject it, or map it to a default. A peer sending arbitrary values on purpose already has unauthenticated RPC access under Bitcoin Core's trust model.
Related input-validation report: AR-27 (malformed JSON accepted as null, since fixed). A protocol fuzz target (AR-06) would exercise this path.
Status
Present on master. Next step, if wanted: an opt-in customization point in libmultiprocess (for example a validity-check overload that applications can define for an enum) that lets the reader throw on unknown values. Idea; no issue open.
Reported privately in an independent security review on 2026-08-07.