fuzz: reset the mockable steady clock between iterations #35478

pull HowHsu wants to merge 1 commits into bitcoin:master from HowHsu:fuzz-reset-mockable-steady-clock changing 1 files +1 −0
  1. HowHsu commented at 8:25 AM on June 7, 2026: contributor

    Fix the issue mentioned by #29018 (comment) And this is my investigation on it: #29018 (comment)

    CheckGlobalsImpl's constructor runs at the start of every fuzz iteration and already resets the global RNG flags and the mockable NodeClock (SetMockTime(0s)), but it never reset the mockable steady clock. A value written to g_mock_steady_time by one input therefore leaks into the next iteration.

    The most common source is FuzzedSock's constructor, which calls SetMockTime(INITIAL_MOCK_TIME) (through ElapseTime(0s)) and never clears it: once any input constructs a FuzzedSock, the steady clock stays mocked for every subsequent iteration in the same process. This is one of the global-state leaks tracked in #29018.

    Fix

    Reset MockableSteadyClock symmetrically with NodeClock:

     g_used_system_time = false;
     SetMockTime(0s);
    +MockableSteadyClock::ClearMockTime();
    

    Besides removing the leak, this puts the steady clock under the same discipline as the system clock: a target that reads MockableSteadyClock::now() without first mocking it (via FuzzedSock, SteadyClockContext, …) is now caught by the existing g_used_system_time check at the end of the iteration, instead of silently reusing a value left over from a previous input.

    Clearing in ~FuzzedSock() would be wrong: several FuzzedSocks can be alive simultaneously (e.g. process_messages adds 1–3 peers), so clearing in one destructor would corrupt the mock observed by the others. Resetting at the iteration boundary keeps it decoupled from socket lifetimes.

    Testing

    Verified with the global-state-detector approach from #29018 (snapshotting/diffing the writable globals around each iteration):

    • Before: a single empty input to process_message reports g_mock_steady_time changing 00 → 01 (0INITIAL_MOCK_TIME).
    • After: that report is gone; the only remaining diffs are the benign one-time initialization of ConsumeTime's function-local statics.

    p2p_headers_presync (uses SteadyClockContext) and pcp_request_port_map (uses FuzzedSock) still run to succeeded without aborting, confirming existing steady-clock readers are unaffected.

    This leak is invisible to coverage-based checks such as deterministic-fuzz-coverage, because g_mock_steady_time is only consumed through coarse time comparisons (e.g. the 250 ms presync rate-limiter): a changed value doesn't change the executed branches, so only a memory-diffing detector can see it.

  2. fuzz: reset the mockable steady clock between iterations
    CheckGlobalsImpl's constructor runs at the start of every fuzz iteration
    and already resets the global RNG flags and the mockable NodeClock via
    SetMockTime(0s), but it never reset the mockable steady clock. A value
    written to g_mock_steady_time by one input therefore leaked into the
    next one. For example, FuzzedSock's constructor calls
    SetMockTime(INITIAL_MOCK_TIME) and never clears it, so the mocked steady
    time stays set for all subsequent iterations.
    
    Reset MockableSteadyClock symmetrically with NodeClock so each input
    starts from an unmocked steady clock. This also brings the steady clock
    under the same discipline as the system clock: a target that reads
    MockableSteadyClock::now() without first mocking it is now caught by the
    existing g_used_system_time check instead of silently reusing a leaked
    value.
    19b32a2e18
  3. DrahtBot added the label Fuzzing on Jun 7, 2026
  4. DrahtBot commented at 8:25 AM on June 7, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK maflcko, marcofleon

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. sedited requested review from marcofleon on Jun 7, 2026
  6. maflcko commented at 7:29 AM on June 10, 2026: member

    lgtm ACK 19b32a2e18017c3bf8ea12d25d48b17347f490b6

    The passing CI shows that this is not an issue, but it can't hurt to be consistent and fix this, so that future issues are detected.

    Thx.

  7. maflcko commented at 7:49 AM on June 10, 2026: member

    Going further, and thinking that FuzzedSock is a bit tedious to review to confirm it doesn't leak state between iterations, I think it can be simplified:

    <details><summary>a diff</summary>

    diff --git a/src/test/fuzz/util/net.cpp b/src/test/fuzz/util/net.cpp
    index ba17e4e936..c826ecca7d 100644
    --- a/src/test/fuzz/util/net.cpp
    +++ b/src/test/fuzz/util/net.cpp
    @@ -116,6 +116,4 @@ FuzzedSock::FuzzedSock(FuzzedDataProvider& fuzzed_data_provider)
           m_fuzzed_data_provider{fuzzed_data_provider},
    -      m_selectable{fuzzed_data_provider.ConsumeBool()},
    -      m_time{MockableSteadyClock::INITIAL_MOCK_TIME}
    +      m_selectable{fuzzed_data_provider.ConsumeBool()}
     {
    -    ElapseTime(std::chrono::seconds(0)); // start mocking the steady clock.
     }
    @@ -131,8 +129,2 @@ FuzzedSock::~FuzzedSock()
     
    -void FuzzedSock::ElapseTime(std::chrono::milliseconds duration) const
    -{
    -    m_time += duration;
    -    MockableSteadyClock::SetMockTime(m_time);
    -}
    -
     FuzzedSock& FuzzedSock::operator=(Sock&& other)
    @@ -430,3 +422,3 @@ bool FuzzedSock::Wait(std::chrono::milliseconds timeout, Event requested, Event*
         }
    -    ElapseTime(timeout);
    +    m_clock += timeout;
         return true;
    @@ -443,3 +435,3 @@ bool FuzzedSock::WaitMany(std::chrono::milliseconds timeout, EventsPerSock& even
         }
    -    ElapseTime(timeout);
    +    m_clock += timeout;
         return true;
    diff --git a/src/test/fuzz/util/net.h b/src/test/fuzz/util/net.h
    index 3639381162..752a776ede 100644
    --- a/src/test/fuzz/util/net.h
    +++ b/src/test/fuzz/util/net.h
    @@ -19,2 +19,3 @@
     #include <test/util/net.h>
    +#include <test/util/time.h>
     #include <util/asmap.h>
    @@ -182,8 +183,3 @@ class FuzzedSock : public Sock
          */
    -    mutable std::chrono::milliseconds m_time;
    -
    -    /**
    -     * Set the value of the mocked steady clock such as that many ms have passed.
    -     */
    -    void ElapseTime(std::chrono::milliseconds duration) const;
    +    mutable SteadyClockContext m_clock{};
     
    

    </details>

    The diff compiles, but I haven't run it. However, it should be fine and using the SteadyClockContext has benefits:

    • The ElapseTime helper can fully be dropped and replaced by operator+=()
    • Manual calls to SetMockTime, references to the magic INITIAL_MOCK_TIME go away
    • The missing MockableSteadyClock::ClearMockTime() in the destructor is added (implicitly)
  8. HowHsu commented at 9:38 AM on June 10, 2026: contributor

    Wouldn't this cause issues when multiple FuzzedSock instances exist and one of them gets destructed, thereby resetting g_mock_steady_time for all of them?

  9. HowHsu commented at 9:50 AM on June 10, 2026: contributor

    Wouldn't this cause issues when multiple FuzzedSock instances exist and one of them gets destructed, thereby resetting g_mock_steady_time for all of them?

    Maybe I'm missing something, but after re-checking the current code, I noticed that FuzzedSock resets g_mock_steady_time to INITIAL_MOCK_TIME in its constructor. This doesn't seem harmful for the current fuzz targets, but is that intentional/by design?

  10. maflcko commented at 10:14 AM on June 10, 2026: member

    Well, that is the 4th benefit of my diff:

    • It is not possible to have multiple concurrent FuzzedSock at the same time, as they would otherwise race the single global mock time against each other. So it is only possible to have single one and my diff also enforces that after LimitOne (35a814a045fb1b90801ab8781db5da7c39099ea3)

    If there was a use-case for several FuzzedSock, the mutable SteadyClockContext m_clock{}; field should be turned into a reference, and a clock should be provided from the outside test case into the FuzzedSock ctor.

  11. HowHsu commented at 11:35 AM on June 10, 2026: contributor

    If there was a use-case for several FuzzedSock, the mutable SteadyClockContext m_clock{}; field should be turned into a reference, and a clock should be provided from the outside test case into the FuzzedSock ctor.

    Yes, there was: https://github.com/bitcoin/bitcoin/blob/master/src/test/fuzz/cmpctblock.cpp#L192 I'm gonna send a seperate PR for this

  12. marcofleon commented at 2:13 PM on June 10, 2026: contributor

    Nice catch, ACK 19b32a2e18017c3bf8ea12d25d48b17347f490b6

  13. fanquake merged this on Jun 10, 2026
  14. fanquake closed this on Jun 10, 2026

  15. maflcko commented at 7:58 AM on June 11, 2026: member

    I'm gonna send a seperate PR for this

    Thx. If you want you can also address a different nit in a different commit from #35497 (review), since it fits in the topic.


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: 2026-06-11 10:51 UTC

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