upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-08-19 18:17:06 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-08-19 22:48:27 +0000
commit11766bec0ea465da263f78a6f473ca792da59280 (patch)
treef07c7eecabdf6a0b5f2299447dcf957472e7ef58 /src/main.cpp
parent88d30ade28d6836e64d0157da57896bdb320e8a2 (diff)
net: stop serving the 2009 marketplace to anonymous peers
Four inherited handlers took attacker-controlled input and wrote to disk with nothing authorising it. None of them had a caller left anywhere in the tree, so all four were reachable only from the outside. checkorder called GenerateNewKey() -- AddKey(), a wallet.dat write -- for any peer that asked, and handed back the pubkey. Its comment claims the cache keeps one key per ip, and on a 2009 network of routable addresses it did. Not here: inbound peers arrive through a rendezvous tunnel and are tagged by BtfMarkerAddr() over three random bytes, so addr.ip is fresh every connection and the cache never hits. Reconnect in a loop and the node mints a key per connection, none of them in any backup taken beforehand, which for a pre-BIP32 wallet is how coins go missing. The map holding them was static and never trimmed. submitorder AddToWallet()'d a CWalletTx from any peer that sent one. It had to be a valid transaction; it did not have to be ours. review checked a signature the sender made with its own key, which proves possession and authorises nothing, then appended to reviews.dat by reading the whole vector, pushing one entry and writing it all back -- quadratic disk on an unbounded map<string,string> off the socket -- then walked a link graph the same peer supplied and relayed the message to every other node. reply dispatched into CRequestTracker. Nothing ever called PushRequest, so the table was permanently empty and the handler was a parser and nothing else. Removed with the subsystem behind them: market.cpp, CProduct, CReview, CUser, CReviewDB, the advert publish templates and the subscribe channel machinery, none of which had an inbound handler either. MSG_REVIEW, MSG_PRODUCT and MSG_TABLE stay in the inv enum, unserved: CInv puts type on the wire as an integer and MSG_TX and MSG_BLOCK are shared with every other node. Unknown commands are already ignored for extensibility, so peers still running these lose nothing but an answer they never asked for. Verified by a clean build of bitflash-node. (cherry picked from commit ad9c393866c007a115f32d70a9017ce91066a7d1)
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp115
1 files changed, 37 insertions, 78 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 62068d2..efff96c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -2782,9 +2782,10 @@ bool AlreadyHave(CTxDB& txdb, const CInv& inv)
2782 { 2782 {
2783 case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash); 2783 case MSG_TX: return mapTransactions.count(inv.hash) || txdb.ContainsTx(inv.hash);
2784 case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash); 2784 case MSG_BLOCK: return mapBlockIndex.count(inv.hash) || mapOrphanBlocks.count(inv.hash);
2785 case MSG_REVIEW: return true;
2786 case MSG_PRODUCT: return mapProducts.count(inv.hash);
2787 } 2785 }
2786 // MSG_REVIEW / MSG_PRODUCT / MSG_TABLE are the 2009 marketplace inventory
2787 // types. Nothing announces or serves them any more (see ProcessMessage),
2788 // so they fall through to the default and are never requested.
2788 // Don't know what it is, just say we already got one 2789 // Don't know what it is, just say we already got one
2789 return true; 2790 return true;
2790} 2791}
@@ -2942,7 +2943,6 @@ bool ProcessMessages(CNode* pfrom)
2942 2943
2943bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) 2944bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
2944{ 2945{
2945 static map<unsigned int, vector<unsigned char> > mapReuseKey;
2946 if (LogAcceptsCategory("net")) { printf("received: %-12s (%d bytes) ", strCommand.c_str(), (int)vRecv.size()); for (int i = 0; i < min(vRecv.size(), (unsigned int)25); i++) printf("%02x ", vRecv[i] & 0xff); printf("\n"); } 2946 if (LogAcceptsCategory("net")) { printf("received: %-12s (%d bytes) ", strCommand.c_str(), (int)vRecv.size()); for (int i = 0; i < min(vRecv.size(), (unsigned int)25); i++) printf("%02x ", vRecv[i] & 0xff); printf("\n"); }
2947 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) 2947 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0)
2948 { 2948 {
@@ -3181,22 +3181,18 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3181 } 3181 }
3182 3182
3183 3183
3184 else if (strCommand == "review") 3184 // "review" used to be handled here. It took a CReview off the wire from an
3185 { 3185 // unauthenticated peer, checked a signature the sender had made with a key
3186 CDataStream vMsg(vRecv); 3186 // of its own choosing -- which proves possession of that key and authorises
3187 CReview review; 3187 // nothing -- and then appended the message to reviews.dat by reading the
3188 vRecv >> review; 3188 // whole vector for the target hash, pushing one entry, and writing all of
3189 3189 // it back. That is quadratic disk for a stream an attacker controls, on a
3190 CInv inv(MSG_REVIEW, review.GetHash()); 3190 // record whose mapValue is an unbounded map<string,string> read straight
3191 pfrom->AddInventoryKnown(inv); 3191 // from the socket, followed by AddAtomsAndPropagate walking a link graph
3192 3192 // the same peer supplied, and finally RelayMessage forwarding it to every
3193 if (review.AcceptReview()) 3193 // other node. Unbounded remote disk growth with network amplification and
3194 { 3194 // no authorisation anywhere in the path. The marketplace it belonged to has
3195 // Relay the original message as-is in case it's a higher version than we know how to parse 3195 // not existed since 2009; the handler is gone rather than bounded.
3196 RelayMessage(inv, vMsg);
3197 mapAlreadyAskedFor.erase(inv);
3198 }
3199 }
3200 3196
3201 3197
3202 else if (strCommand == "block") 3198 else if (strCommand == "block")
@@ -3229,65 +3225,28 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3229 } 3225 }
3230 3226
3231 3227
3232 else if (strCommand == "checkorder") 3228 // "checkorder", "submitorder" and "reply" -- the 2009 buy-from-a-merchant
3233 { 3229 // exchange -- used to be handled here. All three are gone.
3234 uint256 hashReply; 3230 //
3235 CWalletTx order; 3231 // "checkorder" called GenerateNewKey() for any peer that asked, which is
3236 vRecv >> hashReply >> order; 3232 // AddKey() and a wallet.dat write, and handed the pubkey back. The comment
3237 3233 // said "keep giving the same key to the same ip until they use it", and on
3238 /// we have a chance to check the order here 3234 // a 2009 network of routable IPs that held. It does not hold here: every
3239 3235 // inbound peer arrives through a rendezvous tunnel and is tagged with
3240 // Keep giving the same key to the same ip until they use it 3236 // BtfMarkerAddr() over three random bytes (net.cpp), so addr.ip is freshly
3241 if (!mapReuseKey.count(pfrom->addr.ip)) 3237 // random per connection and the cache never hits. Reconnect in a loop and
3242 mapReuseKey[pfrom->addr.ip] = GenerateNewKey(); 3238 // the node mints a key per connection -- unbounded wallet growth, unbounded
3243 3239 // growth of the static map that held them, and every one of those keys
3244 // Send back approval of order and pubkey to use 3240 // missing from every backup taken beforehand, which for this pre-BIP32
3245 CScript scriptPubKey; 3241 // wallet is how coins are lost.
3246 scriptPubKey << mapReuseKey[pfrom->addr.ip] << OP_CHECKSIG; 3242 //
3247 pfrom->PushMessage("reply", hashReply, (int)0, scriptPubKey); 3243 // "submitorder" took a CWalletTx from an unauthenticated peer and, if it
3248 } 3244 // was merely a valid transaction, AddToWallet()'d it. Valid is not the same
3249 3245 // as ours: any peer could write chosen transactions into wallet.dat.
3250 3246 //
3251 else if (strCommand == "submitorder") 3247 // "reply" dispatched to a CRequestTracker callback. Nothing in this tree
3252 { 3248 // has ever called PushRequest, so mapRequests was permanently empty and the
3253 uint256 hashReply; 3249 // handler could only ever be a parser for hostile input.
3254 CWalletTx wtxNew;
3255 vRecv >> hashReply >> wtxNew;
3256
3257 // Broadcast
3258 if (!wtxNew.AcceptWalletTransaction())
3259 {
3260 pfrom->PushMessage("reply", hashReply, (int)1);
3261 return error("submitorder AcceptWalletTransaction() failed, returning error 1");
3262 }
3263 wtxNew.fTimeReceivedIsTxTime = true;
3264 AddToWallet(wtxNew);
3265 wtxNew.RelayWalletTransaction();
3266 mapReuseKey.erase(pfrom->addr.ip);
3267
3268 // Send back confirmation
3269 pfrom->PushMessage("reply", hashReply, (int)0);
3270 }
3271
3272
3273 else if (strCommand == "reply")
3274 {
3275 uint256 hashReply;
3276 vRecv >> hashReply;
3277
3278 CRequestTracker tracker;
3279 CRITICAL_BLOCK(pfrom->cs_mapRequests)
3280 {
3281 map<uint256, CRequestTracker>::iterator mi = pfrom->mapRequests.find(hashReply);
3282 if (mi != pfrom->mapRequests.end())
3283 {
3284 tracker = (*mi).second;
3285 pfrom->mapRequests.erase(mi);
3286 }
3287 }
3288 if (!tracker.IsNull())
3289 tracker.fn(tracker.param1, vRecv);
3290 }
3291 3250
3292 3251
3293 else if (strCommand == "ping") 3252 else if (strCommand == "ping")