rpc: erase empty map entry in removeCommand
What changed, and why it matters
This commit fixes a bookkeeping bug in Bitcoin Core's RPC command registry. When a command was fully removed (for example, when a wallet is unloaded), the command's name was still left behind in an internal list. That caused tools like `getopenrpcinfo` and `listCommands` to report commands that no longer actually exist. The fix simply deletes the empty entry so removed commands disappear from listings. There is no direct evidence this can be exploited to steal funds or crash nodes, but it could mislead monitoring tools or client software that relies on the command list.
Apply the patch. Review any RPC clients or monitoring that cache command lists from getopenrpcinfo, since prior versions may briefly expose commands that are no longer loaded. No emergency response is indicated.
Security signals we found
Stale/inconsistent internal state in RPC command table
Information-discrepancy bug: listCommands/getopenrpcinfo report non-existent RPCs
Potential for client confusion or failed RPC calls after dynamic module unload
No input validation bypass or memory corruption evident in diff
Evidence from the diff
CRPCTable::removeCommand() previously removed a CRPCCommand pointer from the vector stored under a command name, but if that removal emptied the vector, it left the name key in mapCommands with an empty vector. Because listCommands() iterates mapCommands keys unconditionally, the stale key was still returned. The patch erases the map entry when its vector becomes empty. The bug is observable via getopenrpcinfo after wallet unload, and could cause clients or introspection tools to attempt to call deregistered RPCs.
Changed components
src/rpc/server.cppCRPCTable::removeCommandlistCommands()getopenrpcinfo RPCInspect captured patch +3 / −0
diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp
index 0894ba1c..b551c467 100644
--- a/src/rpc/server.cpp
+++ b/src/rpc/server.cpp
@@ -264,6 +264,9 @@ bool CRPCTable::removeCommand(const std::string& name, const CRPCCommand* pcmd)
auto new_end = std::remove(it->second.begin(), it->second.end(), pcmd);
if (it->second.end() != new_end) {
it->second.erase(new_end, it->second.end());
+ if (it->second.empty()) {
+ mapCommands.erase(it);
+ }
return true;
}
}
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.