Skip to content

Conversation

maflcko
Copy link
Member

@maflcko maflcko commented Jan 3, 2020

Add a validation fuzz test for BIP 30 and CVE-2018-17144

@practicalswift
Copy link
Contributor

Super strong concept ACK: very happy to see more people interested in adding fuzzers. Very welcome! :)

Also: the habit of after fixing a security bug also implementing a fuzz target that could have found the bug is something I think we should strive for. I think @kcc (known for making C++ reasonably sane by introducing the sanitizers, libFuzzer, OSS-Fuzz, CFI, etc.) first coined the term "regression fuzzing" for that :)

A good example of "regression fuzzing" was this OOB read in libc++ <random> which not only was fixed but also got a couple of new fuzz targets added: https://bugs.chromium.org/p/chromium/issues/detail?id=994957

@DrahtBot
Copy link
Contributor

DrahtBot commented Jan 3, 2020

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK dergoegge, mzumsande
Concept ACK fanquake, jamesob
Stale ACK practicalswift, jonatack, fjahr

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #27491 (refactor: Move chain constants to the util library by TheCharlatan)
  • #23352 (test: Extend stale_tip_peer_management test by MarcoFalke)
  • #18470 (test: Extend stale tip test by MarcoFalke)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@@ -30,7 +30,7 @@ static void AssembleBlock(benchmark::State& state)
std::array<CTransactionRef, NUM_BLOCKS - COINBASE_MATURITY + 1> txs;
for (size_t b{0}; b < NUM_BLOCKS; ++b) {
CMutableTransaction tx;
tx.vin.push_back(MineBlock(g_testing_setup->m_node, SCRIPT_PUB));
tx.vin.push_back(CTxIn{MineBlock(g_testing_setup->m_node, SCRIPT_PUB)});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would emplace_back be more efficient / proper here?

@Crypt-iQ
Copy link
Contributor

Crypt-iQ commented Jan 8, 2020

This is kind of an aside, but thought I'd post here since this harness makes use of the ConsumeIntegralInRange function and I'm curious what others think.

I modified FuzzedDataProvider.h to ignore byte 48 so that afl-tmin could minimize better. afl-tmin zeros out redundant data with ascii '0' (http://lcamtuf.coredump.cx/afl/technical_details.txt) - this is done to reduce the number of execs. Since the harness uses range (0-2) and all 256 possible fuzz bytes are mapped to this range, ignoring byte 48 should allow for better minimization if running afl-tmin on a corpus. However, with and without ignoring byte 48, afl-tmin was not able to minimize at all, but logically, it SHOULD have regardless of whether the byte was ignored or not (I can look into this more so that there's more data on this). This is the only harness that used ConsumeIntegralInRange, so I wasn't able to test afl-tmin in a meaningful way on any other harnesses. Thoughts on ignoring byte 48?

@practicalswift
Copy link
Contributor

Very nice fuzzing harness! Thanks for adding!

Tested ACK 5b90f46 modulo squash of last commit

Compared to the other fuzzers this one is very slow (6 executions/second), but I guess that is largely unavoidable given the code being tested :)

Also, a slow fuzzing harness is better than no fuzzing harness.

@maflcko
Copy link
Member Author

maflcko commented Jan 10, 2020

Compared to the other fuzzers this one is very slow (6 executions/second), but I guess that is largely unavoidable given the code being tested :)

Good question. This is actually a question in the review club next week: https://bitcoincore.reviews/17860.html

You (and all other reviewers) are invited to join and share your thoughts and ideas.

@practicalswift
Copy link
Contributor

ACK 8b868f1

@rajarshimaitra
Copy link
Contributor

I am trying fuzzing utxo_total_supply with AFL and the fuzz is returning crash like this:
[-] PROGRAM ABORT : Test case 'id:000000,orig:f66a2f2925ab9d377ea5c18ba941e7d1601b7509' results in a crash

One possible cause for crash is memory limit as reported by the fuzzer (52MB), and it suggests doing this:
( ulimit -Sv $[51 << 10]; /path/to/binary [...] <testcase )

While setting ulimit at [51<<10] gives a SYSTEM ERROR : Unable to mmap file 'test/fuzz/utxo_total_supply', setting it to [51<<100] (and for higher values) gives the crash back.

I am new to fuzzing, and am not very sure whats exactly happening here. Seems like its crashing for the very first test case itself. Would appreciate some guidance to navigate this.

I am at 8b868f174

@practicalswift
Copy link
Contributor

@codeShark149 Does it work if you fuzz with libFuzzer instead?

Try this:

$ make distclean
$ ./autogen.sh
$ CC=clang CXX=clang++ ./configure --enable-fuzz --with-sanitizers=address,fuzzer,undefined
$ make
$ src/test/fuzz/utxo_total_supply

@Crypt-iQ
Copy link
Contributor

@codeShark149 I fixed that error on macOS by turning AFL's forkserver off export AFL_NO_FORKSRV=1. If AFL continually crashes on the initial input, then it's probably because the binary isn't being instrumented correctly.

@practicalswift
Copy link
Contributor

I've played a bit more with this harness:

I'm currently using a seed corpus of 3247 files. I'm fuzzing at a rate of 6 execs/second which means that it takes almost ten minutes just to initialize libFuzzer with all seeds in a fuzzing session.

A lot of the time is spent setting up (and tearing down) the TestingSetup object for each input.

Can we think of ways to speed up this fuzzing harness?

Perhaps a fully featured TestingSetup object is not needed? Would limiting the setup to only the absolute minimum needed speed up things?

@instagibbs
Copy link
Member

@practicalswift I suspected as much, that the testing harness is the heavy part of the test. On my non-beefy machine it looks like I'm getting 1 a second?

Alternative might be to allow it to run longer to build a longer chain?

@practicalswift
Copy link
Contributor

Another data point:

As a crude way to quantify the impact of the test setup I made TestingSetup persistent across test inputs (static TestingSetup). That obviously invalidates all assertions (and thus makes the fuzzer meaningless), but the speed jumps to 32 exec/second.

@sanjaykdragon
Copy link
Contributor

I've played a bit more with this harness:

I'm currently using a seed corpus of 3247 files. I'm fuzzing at a rate of 6 execs/second which means that it takes almost ten minutes just to initialize libFuzzer with all seeds in a fuzzing session.

A lot of the time is spent setting up (and tearing down) the TestingSetup object for each input.

Can we think of ways to speed up this fuzzing harness?

Perhaps a fully featured TestingSetup object is not needed? Would limiting the setup to only the absolute minimum needed speed up things?

You mind posting a profiler view of this? I'll try to optimize this if I can. (VS has an optimization profiler section).

@practicalswift
Copy link
Contributor

@sanjaykdragon I don't have any profiler view to post :) The quoted numbers are from the output you'll get when running utxo_total_supply with libFuzzer using the following steps

$ make distclean
$ ./autogen.sh
$ CC=clang CXX=clang++ ./configure --enable-fuzz \
      --with-sanitizers=address,fuzzer,undefined
$ make
$ src/test/fuzz/utxo_total_supply

@rajarshimaitra
Copy link
Contributor

@codeShark149 Does it work if you fuzz with libFuzzer instead?

@practicalswift yes libfuzzer can fuzz it.

@codeShark149 I fixed that error on macOS by turning AFL's forkserver off export AFL_NO_FORKSRV=1.

@Crypt-iQ thanks for suggesting but for some reason that did'nt work. The problem is simple though. The instrumented binary takes about 103 MB memory to run. So just had to change -m200 while running the fuzzer. Now its fuzzing with AFL.

Some note on execution: libfuzzer reported around 5 exec/sec. AFL reported 3.13/sec. Which is natural given the thing being tested.
Any idea on how to make this thing run faster? I will try parallel but i doubt its gonna complete in any reasonable time.

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 8b868f1

Tested with libFuzzer, seeing 6 executions/second on a 2 year old Intel Core i7-6500U CPU 2.50GHz × 4 running Debian Linux.


Edit: Really slowing down as it runs into the thousands of iterations; showing 0 exec/s and making an execution every few seconds.

Am doing further testing with the CVE fix reverted. I'm not sure this test is effective enough.

@fjahr
Copy link
Contributor

fjahr commented Jan 17, 2020

Tested ACK 8b868f1

Reviewed code and successfully ran with AFL.

@jonatack
Copy link
Member

jonatack commented Jan 17, 2020

Now running since 2 1l2 days, taking > 1 gb of RAM, and it's slowed to a crawl of < 10 executions per minute and still has not found the CVE. It also drains my laptop battery at least twice faster than normal.

#22184	REDUCE cov: 48346 ft: 261959 corp: 1540/3628Kb exec/s: 0 rss: 1020Mb L: 4077/4096 MS: 1 EraseBytes-
#22285	NEW    cov: 48346 ft: 261963 corp: 1541/3632Kb exec/s: 0 rss: 1020Mb L: 4096/4096 MS: 1 ChangeBit-
#22296	REDUCE cov: 48346 ft: 261963 corp: 1541/3632Kb exec/s: 0 rss: 1020Mb L: 838/4096 MS: 1 EraseBytes-
#22320	REDUCE cov: 48346 ft: 261963 corp: 1541/3632Kb exec/s: 0 rss: 1020Mb L: 4007/4096 MS: 4 ChangeBinInt-ChangeByte-ChangeBit-EraseBytes-
#22343	REDUCE cov: 48346 ft: 261963 corp: 1541/3632Kb exec/s: 0 rss: 1020Mb L: 1938/4096 MS: 3 InsertRepeatedBytes-ChangeByte-EraseBytes-
#22344	REDUCE cov: 48346 ft: 261963 corp: 1541/3632Kb exec/s: 0 rss: 1020Mb L: 169/4096 MS: 1 EraseBytes-
#22370	REDUCE cov: 48346 ft: 261963 corp: 1541/3632Kb exec/s: 0 rss: 1020Mb L: 3804/4096 MS: 1 CrossOver-

Maybe I've done something dumb in removing and rebuilding without the CVE fix:

diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp
@@ -36,12 +36,6 @@ bool CheckTransaction(const CTransaction& tx, TxValidationState& state)
     // Failure to run this check will result in either a crash or an inflation bug, depending on the implementation of
     // the underlying coins database.
-    std::set<COutPoint> vInOutPoints;
-    for (const auto& txin : tx.vin) {
-        if (!vInOutPoints.insert(txin.prevout).second)
-            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-duplicate");
-    }

I'm not sure why the test runs continually slower. Unless my experience is atypical, this seems to be a real handicap.

This also underscores how necessary it is to run the fuzz tests continuously for them to be most effective.

@maflcko
Copy link
Member Author

maflcko commented Jan 18, 2020

@jonatack The increase in memory usage and slow-down is clearly undesired. Which version of clang are you using? I presume 3.8? Could you try with https://packages.debian.org/stretch-backports/clang-6.0 , or even later, if possible?

Your patch to reintroduce the CVE looks correct.

With the current utils it is only possible to mine valid blocks. This
commit adds new util methods to mine invalid blocks.
@jamesob
Copy link
Contributor

jamesob commented May 2, 2023

Concept ACK, will review this week

Copy link
Member

@dergoegge dergoegge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK fa18fe3

I reintroduced CVE-2018-17144 and the fuzz target found the bug after ~1 day on 10 cores.

#2133   NEW    cov: 8553 ft: 28450 corp: 659/48Kb lim: 124 exec/s: 19 rss: 110Mb L: 69/124 MS: 2 PersAutoDict-ChangeASCIIInt- DE: "\377\377\377>"-
#2151   NEW    cov: 8553 ft: 28451 corp: 660/48Kb lim: 124 exec/s: 19 rss: 110Mb L: 93/124 MS: 3 CrossOver-EraseBytes-CopyPart-
#2153   NEW    cov: 8553 ft: 28452 corp: 661/48Kb lim: 124 exec/s: 19 rss: 110Mb L: 121/124 MS: 2 ChangeBinInt-ShuffleBytes-
#2158   NEW    cov: 8553 ft: 28463 corp: 662/49Kb lim: 124 exec/s: 19 rss: 110Mb L: 124/124 MS: 5 ShuffleBytes-ChangeBit-ChangeBinInt-ShuffleBytes-PersAutoDict- DE: "\002\000\000\000"-
#2173   NEW    cov: 8553 ft: 28464 corp: 663/49Kb lim: 124 exec/s: 19 rss: 110Mb L: 77/124 MS: 5 ChangeBinInt-CrossOver-ChangeBit-InsertRepeatedBytes-ChangeByte-
#2174   NEW    cov: 8553 ft: 28465 corp: 664/49Kb lim: 124 exec/s: 19 rss: 110Mb L: 99/124 MS: 1 ChangeBinInt-
fuzz: test/fuzz/utxo_total_supply.cpp:84: auto utxo_total_supply_fuzz_target(FuzzBufferType)::(anonymous class)::operator()() const: Assertion `circulation == utxo_stats.total_amount' failed.
==16109== ERROR: libFuzzer: deadly signal
    #0 0x55dc406d0314 in __sanitizer_print_stack_trace (/root/bitcoin/src/test/fuzz/fuzz+0x9c1314) (BuildId: 2dbb839c5abc29bd1bf44537252b5378dcc49df4)
    #1 0x55dc406a75d7 in fuzzer::PrintStackTrace() (/root/bitcoin/src/test/fuzz/fuzz+0x9985d7) (BuildId: 2dbb839c5abc29bd1bf44537252b5378dcc49df4)
    #2 0x55dc4068cc33 in fuzzer::Fuzzer::CrashCallback() (/root/bitcoin/src/test/fuzz/fuzz+0x97dc33) (BuildId: 2dbb839c5abc29bd1bf44537252b5378dcc49df4)
    #3 0x7fd78765472f  (/lib/x86_64-linux-gnu/libpthread.so.0+0x1272f) (BuildId: 1545d6e6c7da9e2e706253a30cbfe720b9101e62)

Minimized input in base64: zhoHDwcH+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4m5ubm/gI+Pj4+Pj4m5ub mwj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+A==

Copy link
Member

@dergoegge dergoegge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK fa2d8b6

Copy link
Contributor

@mzumsande mzumsande left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK fa2d8b6

I was also able to catch the reintroduced CVE-2018-17144 (with the previous commit fa18fe3, current one is still running).

I can't really see how this actually fuzzes BIP42. Wouldn't the fuzzer need to create millions of blocks to detect it, so if we'd reintroduce that bug, the fuzzer would never be able to find it in practice?

Finally, I also observed that the fuzzer gets slower with time and after a few hours creates inputs that will take several seconds to run. That seems unavoidable, because the chains that are created are getting longer, and each block must be mined at a small but non-zero difficulty. However, it means that we should probably keep an eye on the qa-corpus seeds once this gets merged.


if (duplicate_coinbase_height == ActiveHeight()) {
// we mined the duplicate coinbase
assert(current_block->vtx.at(0)->vin.at(0).scriptSig == duplicate_coinbase_script);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this check inside the was_valid branch?
As far as I understand it, the second block with the duplicate coinbase could be valid (if the coinbase from block 1 was spent), or BIP-30 invalid (otherwise) - but this should hold regardless.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, could move outside

@maflcko maflcko changed the title fuzz: BIP 42, BIP 30, CVE-2018-17144 fuzz: BIP 30, CVE-2018-17144 May 6, 2023
@maflcko
Copy link
Member Author

maflcko commented May 6, 2023

I can't really see how this actually fuzzes BIP42.

Yeah, it was mostly a joke. Now with LIMITED_WHILE this is no longer possible to fuzz at all.

@fanquake fanquake merged commit 322ec63 into bitcoin:master May 6, 2023
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request May 7, 2023
@maflcko
Copy link
Member Author

maflcko commented May 8, 2023

Finally, I also observed that the fuzzer gets slower with time and after a few hours creates inputs that will take several seconds to run. That seems unavoidable, because the chains that are created are getting longer, and each block must be mined at a small but non-zero difficulty. However, it means that we should probably keep an eye on the qa-corpus seeds once this gets merged.

I guess LIMITED_WHILE can be reduced so much that it can catch the CVE only. For BIP 42 and all other purposes, it might be better to add a new fuzz target for that specifically, maybe using a fuzzed assumeutxo state?

@maflcko maflcko deleted the 1908-fuzzVal branch May 8, 2023 12:09
fanquake added a commit to bitcoin-core/gui that referenced this pull request May 31, 2023
fafb4da fuzz: Avoid timeout in utxo_total_supply (MarcoFalke)

Pull request description:

  Looks like for high block counts it may be better to mock the chain, otherwise a high limit will lead to fuzz input bloat and timeouts, see bitcoin/bitcoin#17860 (comment).

  It can be checked that the fuzz target can still find the CVE, see bitcoin/bitcoin#17860 (review) with a diff of:

  ```diff
  diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp
  index f949655..6f4cfb5f51 100644
  --- a/src/consensus/tx_check.cpp
  +++ b/src/consensus/tx_check.cpp
  @@ -39,8 +39,6 @@ bool CheckTransaction(const CTransaction& tx, TxValidationState& state)
       // the underlying coins database.
       std::set<COutPoint> vInOutPoints;
       for (const auto& txin : tx.vin) {
  -        if (!vInOutPoints.insert(txin.prevout).second)
  -            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-duplicate");
       }

       if (tx.IsCoinBase())
  ```

  Also, fix a nit, see bitcoin/bitcoin#17860 (comment)

ACKs for top commit:
  dergoegge:
    ACK fafb4da

Tree-SHA512: a28fe9cd6ebb4c9bed5a5b35be76c1c436a87586c8fc3b3c4c8559a4a77ac08098324370da421d794c99579882c0872b6b29415de47ade6a05a08504a3d494c4
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request May 31, 2023
fafb4da fuzz: Avoid timeout in utxo_total_supply (MarcoFalke)

Pull request description:

  Looks like for high block counts it may be better to mock the chain, otherwise a high limit will lead to fuzz input bloat and timeouts, see bitcoin#17860 (comment).

  It can be checked that the fuzz target can still find the CVE, see bitcoin#17860 (review) with a diff of:

  ```diff
  diff --git a/src/consensus/tx_check.cpp b/src/consensus/tx_check.cpp
  index f949655..6f4cfb5f51 100644
  --- a/src/consensus/tx_check.cpp
  +++ b/src/consensus/tx_check.cpp
  @@ -39,8 +39,6 @@ bool CheckTransaction(const CTransaction& tx, TxValidationState& state)
       // the underlying coins database.
       std::set<COutPoint> vInOutPoints;
       for (const auto& txin : tx.vin) {
  -        if (!vInOutPoints.insert(txin.prevout).second)
  -            return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-inputs-duplicate");
       }

       if (tx.IsCoinBase())
  ```

  Also, fix a nit, see bitcoin#17860 (comment)

ACKs for top commit:
  dergoegge:
    ACK fafb4da

Tree-SHA512: a28fe9cd6ebb4c9bed5a5b35be76c1c436a87586c8fc3b3c4c8559a4a77ac08098324370da421d794c99579882c0872b6b29415de47ade6a05a08504a3d494c4
@bitcoin bitcoin locked and limited conversation to collaborators May 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.