Bug 92
Chain.getBlockHash with a height above the tip aborts the node
- Status
- Open, fix under review
- Symptom
- Assertion failure; worst effect seen: assertion failure
- Where seen
- Branch-build
- Trigger
- Unusual input from a peer or caller
- First reported
- 2026-09-09 (first report)
- Fixed
- Not yet
- In released binaries
- No
- Defect ever on master
- Yes
Summary
Calling getBlockHash on the Chain IPC interface with a height that is not in the active chain, for example one above the current tip, makes bitcoin-node abort on an Assert. The method was written for the wallet running in the same process, which only asks for heights it knows exist; over IPC the height comes from another program. Only the open PR bitcoin#29409, which exposes Chain over IPC, makes this reachable, so master and releases are not affected. The fix is in bitcoin#36520, not yet merged.
What happened
- @zaidmstrr, testing the
Chaininterface from bitcoin#29409, reported on 2026-09-09 that callinggetBlockHashwith a height above the tip aborted the node withChainImpl::getBlockHash(int): Assertion 'chainman().ActiveChain()[height]' failed, and suggested returning an error instead.
Root cause
ChainImpl::getBlockHash() in src/node/interfaces.cpp looks the height up in the active chain and wraps the result in Assert(...), which aborts the process if the lookup returns null. The interface comment states the precondition plainly: "Height must be valid or this function will abort." That is acceptable for in-process callers, but over IPC the height is input from another process. Any client can send an out-of-range value, and even a careful client that reads getHeight() first can lose a race with a reorganization that shortens the chain.
Fix
No fix is merged yet. In bitcoin#36520 ("interfaces: Avoid node crashes from invalid Chain IPC calls"), the commit 42fcc504e21b, by ryanofsky and written with Claude, makes getBlockHash throw std::out_of_range instead. libmultiprocess returns the exception to the IPC client as an error, and the node keeps running. A new invalidArguments test in src/test/interfaces_tests.cpp checks heights one above the tip and -1. The same commit also fixes null transaction arguments.
Next steps
- Review and merge bitcoin#36520, ideally before bitcoin#29409 exposes
Chainover IPC. - Check the other
Chainmethods that take heights or block hashes for asserts on untrusted input; ryanofsky noted that making every method safe for every argument is not a realistic goal for bitcoin#29409, but should be done where feasible.
Timeline
- 2026-09-09: reported by @zaidmstrr while testing bitcoin#29409.
- 2026-10-14: bitcoin#36520 opened; still open.
- Releases: not affected. On master,
Chainis not exposed over IPC.
Lessons
What would have prevented this bug, found it sooner, or made it easier to debug. The patterns page groups these lessons across all bugs.
Pattern An in-process interface trusting what now comes from another process: An assertion on what is now input from a peer
- Prevented by a different API shape
- Earliest detector a test with a non-C++ client (did not exist then)
- Diagnosis easy
- Fix design easy
- Debugging aids a clear assertion message
- Rust partly
Would Rust have prevented it? Partly. Rust would make the lookup return an Option and an unwrap would panic just like the Assert; the question is whether the caller is trusted, which no type system answers, though Option makes the missing case visible at the call site. More on Rust
Could it have been prevented?
Yes, by treating every method exposed over IPC as taking untrusted input. The
Chain::getBlockHash comment already said "Height must be valid or this function will
abort", which is a reasonable contract for the in-process wallet and a remote crash for
any other caller. The fix in bitcoin#36520
shows the rule: report an invalid argument as an error (here std::out_of_range, which
libmultiprocess returns to the client) rather than asserting. Doing that sweep before
bitcoin#29409 exposes the interface is
cheaper than finding each case by hand.
Could it have been found sooner?
Yes, by a test that calls each newly exposed method with out-of-range values, or by a
fuzz target over the IPC interface. Neither existed for Chain; the existing tests use
the interface the way the wallet does. It was found when a reviewer,
zaidmstrr,
called the method by hand with a height above the tip.
What made it hard to debug?
Nothing: the assertion message names the file, function and condition.
Lesson
When an internal interface becomes a remote one, its documented preconditions become input validation that someone has to write: assertions that protected against programming errors now let any client abort the server.
Classification
The survey's classification of this bug. The explorer charts these fields across all bugs; the method page explains them.
| Field | Value |
|---|---|
| Symptom | assert |
| Worst effect observed | assert |
| Seen in | branch-build |
| Scope | production |
| Trigger | Unusual input (unusual-input) |
| Phase | steady-state |
| Side | server |
| Component | bitcoin-ipc |
| Root cause kind | assumption-violated |
| Fix kind | local-fix |
| Detection | manual-test |
| Needs a peer that is not libmultiprocess C++ | no |
| Needed a sanitizer to see | no |
| Defect ever on master | yes |
| Released | no |
| Confidence | high |