fuzz: Fix misplaced SeedRand::ZEROS #31521

pull maflcko wants to merge 1 commits into bitcoin:master from maflcko:2412-fuzz-zero changing 2 files +4 −4
  1. maflcko commented at 8:14 am on December 18, 2024: member

    After commit fae63bf13033adec80c7e6d73144a21ea3cfbc6d this must be placed even before test_setup. This is nice, because it makes the usage consistently appear in the first line.

    The change is moving a SeedRandomForTest(SeedRand::ZEROS) to happen earlier. This is fine, because it will either have no effect, or make the code more deterministic, because after commit fae63bf, no other re-seeding other than ZEROS can happen in fuzz tests.

  2. DrahtBot commented at 8:14 am on December 18, 2024: contributor

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

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31521.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK marcofleon, brunoerg, hodlinator

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

  3. DrahtBot added the label Tests on Dec 18, 2024
  4. marcofleon approved
  5. marcofleon commented at 10:40 am on December 18, 2024: contributor
    ACK fa91ee10f1c1b9f393451eda5cbcd31609d3f103
  6. marcofleon commented at 11:00 am on December 18, 2024: contributor

    Also, if you’re already changing the message, small nit for this line:

    0"Without a solution, fuzz stability and determinism can lead \n"
    1"to non-reproducible bugs or inefficient fuzzing.\n\n"
    

    Do you mean “stability and determinism issues” or something similar (instability and non-determinism)? The sentence is a bit unclear to me, as fuzz stability and determinism is what we’re aiming for.

  7. fuzz: Fix misplaced SeedRand::ZEROS
    After commit fae63bf13033adec80c7e6d73144a21ea3cfbc6d this must be
    placed even before test_setup.
    fadd568931
  8. maflcko force-pushed on Dec 18, 2024
  9. maflcko commented at 11:11 am on December 18, 2024: member
    Thx, fixed typo!
  10. marcofleon commented at 11:23 am on December 18, 2024: contributor
    Re ACK fadd568931a2d21e0f80e1efaf2281f5164fa20e
  11. brunoerg approved
  12. brunoerg commented at 11:52 am on December 18, 2024: contributor
    code review ACK fadd568931a2d21e0f80e1efaf2281f5164fa20e
  13. ryanofsky commented at 7:43 pm on December 18, 2024: contributor

    After commit fae63bf this must be placed even before test_setup.

    I think I don’t understand what happens if SeedRandomStateForTest(SeedRand::ZEROS); is not called before the test setup. Is this causing the fuzz test to just crash on startup? Would be helpful to say what is happening in the PR description.

  14. maflcko commented at 8:47 am on December 19, 2024: member

    After commit fae63bf this must be placed even before test_setup.

    I think I don’t understand what happens if SeedRandomStateForTest(SeedRand::ZEROS); is not called before the test setup. Is this causing the fuzz test to just crash on startup? Would be helpful to say what is happening in the PR description.

    Good question! There is no crash and I doubt that this change makes any difference in practise, so I used that as an excuse to not write a full explanation and instead opted to go with the “lazy explanation” (see last point below), to minimize the review burden.

    Explanation of the background:

    • In all fuzz tests, during initialization, which happens globally once in the process before a fuzz input is executed, SeedRandomForTest(SeedRand::ZEROS) is called, which initializes the global PRNG with a constant state.
    • In this fuzz test (utxo_total_supply), for every iteration, test_setup is constructed. The test setup constructor calls SeedRandomForTest(SeedRand::FIXED_SEED) before commit fae63bf and nothing after.
    • (before fae63bf) Calling SeedRandomForTest(SeedRand::FIXED_SEED) is actually fine, because fuzz tests are not supposed to receive an env var from outside to affect the fixed seed. Thus, the global PRNG would have been re-initialized with some constant state.
    • (after fae63bf), no re-seed happens for fuzz tests, and the (dirty) global PRNG state is re-used.

    Explanation of the fix:

    • SeedRandomForTest(SeedRand::ZEROS) is now called before test_setup, in every iteration of the fuzz target. This means in practise, that a constant PRNG initial state is picked again, just like before commit fae63bf. Also any salted hashers that are in test_setup should be deterministic.

    Alternative explanation for the fix (lazy version):

    • The change is moving a SeedRandomForTest(SeedRand::ZEROS) to happen earlier. This is fine, because it will either have no effect, or make the code more deterministic, because after commit fae63bf, no other re-seeding other than ZEROS can happen in fuzz tests.
  15. maflcko commented at 10:12 am on December 19, 2024: member

    I’ve added the lazy explanation to the pull request description as well.

    If someone really wants to test the lengthy explanation, it is possible. I did that with FUZZ=utxo_total_supply ./bld-cmake/src/test/fuzz/fuzz -runs=2 and a small diff to peek into the global PRNG state. The expected result should be that a constant string is printed twice.

     0diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
     1index 3f8c6f41ba..771cf0e436 100644
     2--- a/src/test/util/setup_common.cpp
     3+++ b/src/test/util/setup_common.cpp
     4@@ -148,7 +148,8 @@ BasicTestingSetup::BasicTestingSetup(const ChainType chainType, TestOpts opts)
     5         // tests, such as the fuzz tests to run in several processes at the
     6         // same time, add a random element to the path. Keep it small enough to
     7         // avoid a MAX_PATH violation on Windows.
     8-        const auto rand{HexStr(g_rng_temp_path.randbytes(10))};
     9+        const auto rand{HexStr(m_rng.randbytes(10))};
    10+        std::cout << rand << std::endl;
    11         m_path_root = fs::temp_directory_path() / TEST_DIR_PATH_ELEMENT / test_name / rand;
    12         TryCreateDirectories(m_path_root);
    13     } else {
    
  16. hodlinator commented at 11:06 am on December 19, 2024: contributor

    I was worried that g_rng_temp_path had somehow stopped working again, but I see you needed to replace it with m_rng in your diff to make paths deterministic.

    It is a dilemma regarding the hash salts, one would like to exercise many values for them, but still get determinism. What would you think about having the fuzz input affect the random seed?

  17. maflcko commented at 11:39 am on December 19, 2024: member

    It is a dilemma regarding the hash salts, one would like to exercise many values for them, but still get determinism. What would you think about having the fuzz input affect the random seed?

    Certainly, this is possible and can be done on a case-by-case basis for fuzz targets where it would be useful.

  18. in src/test/fuzz/util/check_globals.cpp:28 in fadd568931
    23@@ -24,12 +24,12 @@ struct CheckGlobalsImpl {
    24                          "The current fuzz target used the global random state.\n\n"
    25 
    26                          "This is acceptable, but requires the fuzz target to call \n"
    27-                         "SeedRandomStateForTest(SeedRand::ZEROS) at the beginning \n"
    28-                         "of processing the fuzz input.\n\n"
    29+                         "SeedRandomStateForTest(SeedRand::ZEROS) in the first line \n"
    30+                         "of the FUZZ_TARGET function.\n\n"
    


    hodlinator commented at 12:37 pm on December 19, 2024:
    (nit: Slight preference for the old text that doesn’t dictate specific lines).

    maflcko commented at 1:34 pm on December 19, 2024:
    Will leave as-is for now, to not invalidate the 3 acks. Is there a reason why the first line could not be used for this with the current codebase in master?

    hodlinator commented at 2:05 pm on December 19, 2024:

    Was thinking there may creep up something else that also needs initializing really early and either is unaffected by randomness or somehow needs to affect randomness seeding (yes, contrived).

    Then there is the idea to have a mixin seeding the randomness internally, which would change this part regardless.

  19. hodlinator approved
  20. hodlinator commented at 1:27 pm on December 19, 2024: contributor

    ACK fadd568931a2d21e0f80e1efaf2281f5164fa20e

    Prior to commit fae63bf13033adec80c7e6d73144a21ea3cfbc6d (#31486):

    • utxo_total_supply fuzz target was initializing ChainTestingSetup which would call SeedRandomStateForTest(SeedRand::FIXED_SEED).
    • SeedRandomStateForTest(SeedRand::FIXED_SEED) would seed randomness using a value from a static variable initialized once during process startup.
    • This was making each fuzz target execution (input) use the same value.

    After fae63bf13033adec80c7e6d73144a21ea3cfbc6d:

    • Due to the added if constexpr (!G_FUZZING)-guard inside of the ChainTestingSetup-initialization, we were no longer resetting the PRNG for every fuzz input (aside from the explicit SeedRandomStateForTest(SeedRand::ZEROS)-calls in the fuzz targets).
    • This lead to PRNG state leakage from the previous fuzz input into the next input up until utxo_total_supply itself explicitly called SeedRandomStateForTest(SeedRand::ZEROS).
    • This PR (commit fadd568931a2d21e0f80e1efaf2281f5164fa20e) fixes that leakage by calling SeedRandomStateForTest(SeedRand::ZEROS) before ChainTestingSetup-initialization.

    Testing

    Added this to the utxo_total_supply target:

    0std::ofstream{"/tmp/fuzz_data", std::ios_base::app} << HexStr(FastRandomContext().rand256()) << std::endl;
    

    When placed before SeedRandomStateForTest(SeedRand::ZEROS), /tmp/fuzz_data would contain this for 4 runs:

    0b0fd14ff96a0bda154c329082c9c6533bb4c9473bf5dde138f82c9ac5553d958
    1daaa4b425bfa3725e2b0f59f0fabf4ee3387123c569425cbcf8808296acc6f4e
    2daaa4b425bfa3725e2b0f59f0fabf4ee3387123c569425cbcf8808296acc6f4e
    3daaa4b425bfa3725e2b0f59f0fabf4ee3387123c569425cbcf8808296acc6f4e
    

    - Meaning PRNG state would leak from the previous run, but it would be the same leaked state on the next run. This is not guaranteed if the fuzz target is complex enough though, as different fuzz inputs could lead to different amount of randomness being used.

    When placed after SeedRandomStateForTest(SeedRand::ZEROS), one gets the same on each run:

    0b0fd14ff96a0bda154c329082c9c6533bb4c9473bf5dde138f82c9ac5553d958
    1b0fd14ff96a0bda154c329082c9c6533bb4c9473bf5dde138f82c9ac5553d958
    2b0fd14ff96a0bda154c329082c9c6533bb4c9473bf5dde138f82c9ac5553d958
    3b0fd14ff96a0bda154c329082c9c6533bb4c9473bf5dde138f82c9ac5553d958
    

    Potential follow-up

    Conceptually I think it would be an improvement to seed randomness using fuzz input instead of ZEROS, but that should be done in another PR. It would probably lead to marginally more test coverage while still retaining determinism.

    The initialization-step would still use ZEROS I guess since it has no given fuzz input-data AFAIK.

  21. ryanofsky merged this on Dec 19, 2024
  22. ryanofsky closed this on Dec 19, 2024

  23. ryanofsky commented at 3:36 pm on December 19, 2024: contributor

    Thanks for the explanations. The reason I was confused earlier is that I mistakenly thought CheckGlobalsImpl would crash if a fuzz test ever used the global RNG without seeding it with zeroes first.

    But (unfortunately?) the checker doesn’t really check that. It only checks that if a fuzz test uses the RNG, then it must seed it with zeros at some point during the test. The checker doesn’t check that seeding happens before using the RNG.

    So this PR does fix some nondeterminism, but the nondeterminism was undetected by the checker, so it was not causing any crash.

  24. maflcko deleted the branch on Dec 19, 2024
  25. maflcko commented at 5:15 pm on December 19, 2024: member

    But (unfortunately?) the checker doesn’t really check that. It only checks that if a fuzz test uses the RNG, then it must seed it with zeros at some point during the test. The checker doesn’t check that seeding happens before using the RNG.

    Sure, makes sense to add this assert as well.

    Edit: done in https://github.com/bitcoin/bitcoin/pull/31548


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-12-21 12:12 UTC

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