addrman serialize: nNew was wrong, oh ow #22450

issue MarcoFalke openend this issue on July 15, 2021
  1. MarcoFalke commented at 8:38 am on July 15, 2021: member

    Made observable by the fuzz tests in commit e0a2b390c144e123e2fc8a289fdff36815476964

    Crash in https://github.com/bitcoin/bitcoin/blob/d8f1e1327f9c2f9fcc804468f6a981580acdf30a/src/addrman.h#L261

    OSS-Fuzz reproducer: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=36208

    Can be reproduced locally in a few CPU hours via FUZZ=addrman_deserialize ./src/test/fuzz/fuzz -use_value_profile=1 (using the qa-assets seed as starting point).

  2. MarcoFalke added the label Bug on Jul 15, 2021
  3. MarcoFalke added the label P2P on Jul 15, 2021
  4. MarcoFalke added this to the milestone 22.0 on Jul 15, 2021
  5. jonatack commented at 8:53 am on July 15, 2021: member
    Another win for fuzzing, oh wow.
  6. fanquake commented at 8:57 am on July 15, 2021: member
    cc @vasild
  7. vasild commented at 10:11 am on July 15, 2021: member
    Fuzzer rulez!
  8. jnewbery commented at 11:02 am on July 15, 2021: member

    The repositioning/rebucketing approach of ResetI2PPorts() introduced in #22112 seems very brittle. I think it would make much more sense just to remove any I2P addresses which don’t have port 0, and then perhaps add them back through the public (and well-tested) Add() interface.

    I don’t know the exact bug that leads to this internal addrman inconsistency, but this loop seems suspect to me:

     0    for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; ++bucket) {
     1        for (int i = 0; i < ADDRMAN_BUCKET_SIZE; ++i) {
     2            const auto id = vvNew[bucket][i];
     3            if (id == -1) {
     4                continue;
     5            }
     6            auto it = mapInfo.find(id);
     7            if (it == mapInfo.end()) {
     8                return;
     9            }
    10            auto& addr_info = it->second;
    11            if (!addr_info.IsI2P() || addr_info.GetPort() == I2P_SAM31_PORT) {
    12                continue;
    13            }
    14
    15            auto addr_info_newport = addr_info;
    16            // The below changes addr_info_newport.GetKey(), which is used in finding a
    17            // bucket and a position within that bucket. So a re-bucketing may be necessary.
    18            addr_info_newport.port = I2P_SAM31_PORT;
    19
    20            // Reposition entries of vvNew within the same bucket because we don't know the source
    21            // address which led to the decision to store the entry in vvNew[bucket] so we can't
    22            // re-evaluate that decision, but even if we could, CAddrInfo::GetNewBucket() does not
    23            // use CAddrInfo::GetKey() so it would end up in the same bucket as before the port
    24            // change.
    25            const auto i_target = addr_info_newport.GetBucketPosition(nKey, true, bucket);
    26
    27            if (i_target == i) { // No need to re-position.
    28                addr_info = addr_info_newport;
    29                continue;
    30            }
    31
    32            // Reposition from i to i_target, removing the entry from i_target (if any).
    33            ClearNew(bucket, i_target);
    34            vvNew[bucket][i_target] = id;
    35            vvNew[bucket][i] = -1;
    36            addr_info = addr_info_newport;
    37        }
    38    }
    

    If the same I2P address appears in multiple new buckets, then the first time this loop finds that address, it’ll update the port and reposition it in that bucket. Any subsequent times that the address is encountered, it will already have had its port updated so the inner loop will continue due to addr_info.GetPort() == I2P_SAM31_PORT, but it’ll be in the wrong position in its bucket. If we later try to promote the entry from new to tried, then this loop in MakeTried() will fail to find the entry in those buckets:

    0    // remove the entry from all new buckets
    1    for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
    2        int pos = info.GetBucketPosition(nKey, true, bucket);
    3        if (vvNew[bucket][pos] == nId) {
    4            vvNew[bucket][pos] = -1;
    5            info.nRefCount--;
    6        }
    7    }
    8    nNew--;
    

    I’m not sure whether this is the exact cause of this assert, but it seems like it could at least lead to inconsistencies. That’s always a risk when reaching into the internal data structures of a complex class like addrman.

  9. vasild referenced this in commit 816f29eab2 on Jul 15, 2021
  10. MarcoFalke commented at 11:47 am on July 15, 2021: member

    More crashes:

     0$ FUZZ=addrman_deserialize ./src/test/fuzz/fuzz ./crash-ef7d4a4f940b8fdb4999f6a92969260cf8e1405f.log 
     1INFO: Running with entropic power schedule (0xFF, 100).
     2INFO: Seed: 2455641637
     3INFO: Loaded 1 modules   (228950 inline 8-bit counters): 228950 [0x55bb030060f8, 0x55bb0303df4e), 
     4INFO: Loaded 1 PC tables (228950 PCs): 228950 [0x55bb0303df50,0x55bb033bc4b0), 
     5./src/test/fuzz/fuzz: Running 1 inputs 1 time(s) each.
     6Running: ./crash-ef7d4a4f940b8fdb4999f6a92969260cf8e1405f.log
     7fuzz: addrman.cpp:149: void CAddrMan::SwapRandom(unsigned int, unsigned int): Assertion `nRndPos1 < vRandom.size() && nRndPos2 < vRandom.size()' failed.
     8==412116== ERROR: libFuzzer: deadly signal
     9...
    10    [#9](/bitcoin-bitcoin/9/) 0x55bb026c65f9 in CAddrMan::SwapRandom(unsigned int, unsigned int) addrman.cpp:149:5
    11    [#10](/bitcoin-bitcoin/10/) 0x55bb026c7758 in CAddrMan::Delete(int) addrman.cpp:173:5
    12    [#11](/bitcoin-bitcoin/11/) 0x55bb026d1c0b in CAddrMan::ClearNew(int, int) addrman.cpp:192:13
    13    [#12](/bitcoin-bitcoin/12/) 0x55bb026d1c0b in CAddrMan::ResetI2PPorts() addrman.cpp:819:17
    14    [#13](/bitcoin-bitcoin/13/) 0x55bb025d122a in void CAddrMan::Unserialize<CDataStream>(CDataStream&) ./addrman.h:455:9
    15    [#14](/bitcoin-bitcoin/14/) 0x55bb025b068d in void Unserialize<CDataStream, CAddrMan&>(CDataStream&, CAddrMan&) ./serialize.h:676:7
    16    [#15](/bitcoin-bitcoin/15/) 0x55bb025b068d in CDataStream& CDataStream::operator>><CAddrMan&>(CAddrMan&) ./streams.h:428:9
    17    [#16](/bitcoin-bitcoin/16/) 0x55bb025b068d in void (anonymous namespace)::DeserializeFromFuzzingInput<CAddrMan>(Span<unsigned char const>, CAddrMan&, std::optional<int>, int) test/fuzz/deserialize.cpp:87:12
    18    [#17](/bitcoin-bitcoin/17/) 0x55bb025b068d in addrman_deserialize_fuzz_target(Span<unsigned char const>) test/fuzz/deserialize.cpp:201:1
    19...
    

    crash-ef7d4a4f940b8fdb4999f6a92969260cf8e1405f.log

  11. vasild commented at 12:11 pm on July 15, 2021: member

    This is what the fuzzed addrman data produces at the end of Unserialize(), before RemoveInvalid() and before ResetI2PPorts():

    0nNew=-1, nTried=5
    1id=3, addr_info=[::]:0, in tried=1
    2id=2, addr_info=4ucsbzifedsqkihfauqokbja4ucsbzifedsqkihfauqokbja4ucq.b32.i2p:8192, in tried=1
    3id=1, addr_info=[::]:229, in tried=1
    4id=0, addr_info=4ucsbzifedsqkihfauqokbja4ucsbzifedsqkihfauqokbja4ucq.b32.i2p:256, in tried=1
    5id=-1, addr_info=[::]:8421, in tried=1
    6vRandom=-1,0,1,2,3,
    

    After RemoveInvalid():

    0nNew=-1, nTried=3
    1id=2, addr_info=4ucsbzifedsqkihfauqokbja4ucsbzifedsqkihfauqokbja4ucq.b32.i2p:8192, in tried=1
    2id=0, addr_info=4ucsbzifedsqkihfauqokbja4ucsbzifedsqkihfauqokbja4ucq.b32.i2p:256, in tried=1
    3id=-1, addr_info=[::]:8421, in tried=1
    4vRandom=-1,0,2,
    

    After ResetI2PPorts():

    0nNew=0, nTried=2
    1id=2, addr_info=4ucsbzifedsqkihfauqokbja4ucsbzifedsqkihfauqokbja4ucq.b32.i2p:0, in tried=0
    2id=0, addr_info=4ucsbzifedsqkihfauqokbja4ucsbzifedsqkihfauqokbja4ucq.b32.i2p:0, in tried=1
    3id=-1, addr_info=[::]:8421, in tried=1
    4vRandom=-1,0,2,
    

    Later Serialize() chokes on this because there is at least one entry in mapInfo[] with nRefCount > 0 and nNew is 0. The problem originated from the unserialized negative nNew. ResetI2PPorts() correctly moved one entry from “tried” to “new” and incremented nNew (from -1 to 0).

    TODO:

    • Why RemoveInvalid() did not remove id=-1, addr_info=[::]:8421, in tried=1?
    • Resolve the problem @jnewbery highlighted above.
    • See what is the other crash reported by @MarcoFalke above. @jnewbery

    remove any I2P addresses which don’t have port 0, and then perhaps add them back through the public (and well-tested) Add() interface

    I considered this approach and ditched it for some reason, but I do not remember why :/ It could have been that deleting an entry has to be done again by fiddling directly with the internal structures and was about as complicated as the current code. Will look at this again.

  12. MarcoFalke commented at 12:18 pm on July 15, 2021: member

    Why RemoveInvalid() did not remove id=-1, addr_info=[::]:8421, in tried=1?

    Maybe related: https://github.com/bitcoin/bitcoin/pull/22362

  13. vasild commented at 10:44 am on July 16, 2021: member

    Just for reference, attaching the fuzzer input to reproduce the problem that is reported in the OP/description of this issue.

    clusterfuzz-testcase-minimized-addrman_deserialize-5090634410098688.log

  14. vasild referenced this in commit 435e93e9bb on Jul 16, 2021
  15. vasild commented at 4:40 pm on July 16, 2021: member
  16. jonatack commented at 12:25 pm on July 17, 2021: member
     0[#530590](/bitcoin-bitcoin/530590/)	REDUCE cov: 2083 ft: 11547 corp: 872/18Mb lim: 1048575 exec/s: 80 rss: 683Mb L: 282/957238 MS: 2 EraseBytes-InsertRepeatedBytes-
     1fuzz: ./addrman.h:261: void CAddrMan::Serialize(Stream &) const [Stream = CDataStream]: Assertion `nIds != nNew' failed.
     2==4549== ERROR: libFuzzer: deadly signal
     3    [#0](/bitcoin-bitcoin/0/) 0x55b32a31ddb1 in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xea8db1)
     4    [#1](/bitcoin-bitcoin/1/) 0x55b32a263ce8  (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdeece8)
     5    [#2](/bitcoin-bitcoin/2/) 0x55b32a247ad3  (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdd2ad3)
     6    [#3](/bitcoin-bitcoin/3/) 0x7f24ff51d13f  (/lib/x86_64-linux-gnu/libpthread.so.0+0x1413f)
     7    [#4](/bitcoin-bitcoin/4/) 0x7f24ff189ce0 in __libc_signal_restore_set signal/../sysdeps/unix/sysv/linux/internal-signals.h:86:3
     8    [#5](/bitcoin-bitcoin/5/) 0x7f24ff189ce0 in raise signal/../sysdeps/unix/sysv/linux/raise.c:48:3
     9    [#6](/bitcoin-bitcoin/6/) 0x7f24ff173536 in abort stdlib/abort.c:79:7
    10    [#7](/bitcoin-bitcoin/7/) 0x7f24ff17340e in __assert_fail_base assert/assert.c:92:3
    11    [#8](/bitcoin-bitcoin/8/) 0x7f24ff182661 in __assert_fail assert/assert.c:101:3
    12    [#9](/bitcoin-bitcoin/9/) 0x55b32a377569 in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xf02569)
    13    [#10](/bitcoin-bitcoin/10/) 0x55b32a3562df in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xee12df)
    14    [#11](/bitcoin-bitcoin/11/) 0x55b32a427581 in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xfb2581)
    15    [#12](/bitcoin-bitcoin/12/) 0x55b32a427103 in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xfb2103)
    16    [#13](/bitcoin-bitcoin/13/) 0x55b32a34bc45 in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xed6c45)
    17    [#14](/bitcoin-bitcoin/14/) 0x55b32a34bb0d in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xed6b0d)
    18    [#15](/bitcoin-bitcoin/15/) 0x55b32a34b8a8 in __GNU_EH_FRAME_HDR (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xed68a8)
    19    [#16](/bitcoin-bitcoin/16/) 0x55b32b1b6cad in _end (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x1d41cad)
    20    [#17](/bitcoin-bitcoin/17/) 0x55b32b1b6958 in _end (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0x1d41958)
    21    [#18](/bitcoin-bitcoin/18/) 0x55b32a249363  (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdd4363)
    22    [#19](/bitcoin-bitcoin/19/) 0x55b32a24886a  (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdd386a)
    23    [#20](/bitcoin-bitcoin/20/) 0x55b32a24a567  (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdd5567)
    24    [#21](/bitcoin-bitcoin/21/) 0x55b32a24b1a5  (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdd61a5)
    25    [#22](/bitcoin-bitcoin/22/) 0x55b32a23a487 in typeinfo name for boost::system::detail::system_error_category (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdc5487)
    26    [#23](/bitcoin-bitcoin/23/) 0x55b32a2644a2  (/home/jon/projects/bitcoin/bitcoin/src/test/fuzz/fuzz+0xdef4a2)
    27    [#24](/bitcoin-bitcoin/24/) 0x7f24ff174d09 in __libc_start_main csu/../csu/libc-start.c:308:16
    28    [#25](/bitcoin-bitcoin/25/) 0x55b32a2110a9 in secp256k1_fe_negate /home/jon/projects/bitcoin/bitcoin/src/secp256k1/./src/field_5x52_impl.h:383:48
    29    [#26](/bitcoin-bitcoin/26/) 0x55b32a2110a9 in secp256k1_ecmult_strauss_wnaf /home/jon/projects/bitcoin/bitcoin/src/secp256k1/./src/ecmult_impl.h:547:13
    30    [#27](/bitcoin-bitcoin/27/) 0x55b32a2110a9 in secp256k1_ecmult /home/jon/projects/bitcoin/bitcoin/src/secp256k1/./src/ecmult_impl.h:574:5
    31
    32NOTE: libFuzzer has rudimentary signal handlers.
    33      Combine libFuzzer with AddressSanitizer or similar for better crash reports.
    34SUMMARY: libFuzzer: deadly signal
    35MS: 2 PersAutoDict-CrossOver- DE: "+\x00"-; base unit: fbd17f1aa0fcc4e97f0bcafb8e19837f75cde233
    36artifact_prefix='./'; Test unit written to ./crash-8dbb7526c0e5673271d4ef81bb819ae81f2bb000
    
  17. MarcoFalke commented at 11:03 am on July 19, 2021: member
    Wrote a fuzz test in #22493 to reproduce on “old” master (without commit e0a2b39)
  18. MarcoFalke closed this on Jul 19, 2021

  19. MarcoFalke referenced this in commit 54e31742d2 on Jul 19, 2021
  20. hebasto referenced this in commit 6a898ea3cb on Jul 19, 2021
  21. jarolrod referenced this in commit 29a02a90c3 on Jul 19, 2021
  22. josibake referenced this in commit f06b2586ba on Jul 21, 2021
  23. sidhujag referenced this in commit 3b4a8af0d5 on Jul 23, 2021
  24. janus referenced this in commit 976c067e5b on Nov 5, 2021
  25. PastaPastaPasta referenced this in commit 55d085a018 on Mar 5, 2022
  26. PastaPastaPasta referenced this in commit fccdbd2da3 on Mar 5, 2022
  27. PastaPastaPasta referenced this in commit 82a8a80fd6 on Mar 5, 2022
  28. PastaPastaPasta referenced this in commit de08467941 on Mar 7, 2022
  29. gades referenced this in commit 9223d69d83 on May 8, 2022
  30. div72 referenced this in commit 9287835d92 on May 18, 2022
  31. DrahtBot locked this on Aug 18, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-10-04 22:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me