Skip to content

Conversation

@practicalswift
Copy link
Contributor

@practicalswift practicalswift commented Jan 5, 2019

Avoid UB (member call on nullptr) when failing to read randomness in the startup process.

RandFailure is called by GetDevURandom, GetOSRand and GetRandBytes when failing to read randomness for some reason.

This is RandFailure:

[[noreturn]] static void RandFailure()
{
    LogPrintf("Failed to read randomness, aborting\n");
    std::abort();
}

This is LogPrintf:

template <typename... Args>
static inline void LogPrintf(const char* fmt, const Args&... args)
{
    if (g_logger->Enabled()) {
        …
    }
}

Compilers are allowed to optimise away calls to RandFailure that are guaranteed to take place when g_logger == nullptr. Please note that such optimisation would remove the crucial std::abort call in RandFailure.

Failing to read randomness before this patch (under UBSan):

$ src/bitcoind
logging.h:135:19: runtime error: member call on null pointer of type 'BCLog::Logger'
<undefined behaviour>

Failing to read randomness after this patch (under UBSan):

$ src/bitcoind
Failed to read randomness, aborting
$

@maflcko
Copy link
Member

maflcko commented Jan 5, 2019

I fail to see how g_logger could be a nullptr. It is well-defined for the whole duration of main.

About the randomness failure: We should probably not consume bytes before we did a RandomInit()?

Could you clarify how this is happening and maybe include steps to reproduce?

@practicalswift practicalswift changed the title Avoid UB (member call on nullptr) when failing to read randomness in the startup process Avoid UB (member call on nullptr) when failing to read randomness in the startup process when logging is not yet initialised Jan 5, 2019
@sipa
Copy link
Member

sipa commented Jan 5, 2019

@MarcoFalke After #14955 the initialization of the RNG happens on first use, even before calling RandomInit().

@practicalswift practicalswift changed the title Avoid UB (member call on nullptr) when failing to read randomness in the startup process when logging is not yet initialised Avoid UB (member call on nullptr) when failing to read randomness in the startup process Jan 5, 2019
@practicalswift
Copy link
Contributor Author

@MarcoFalke Consider the case when the PRNG has not been seeded with enough randomness to ensure an unpredictable byte sequence. RAND_bytes() will then return 0.

Give me a few minutes I'll get back with the exact steps to reproduce.

@practicalswift
Copy link
Contributor Author

practicalswift commented Jan 5, 2019

@MarcoFalke

Steps to reproduce:

$ git clone https://github.com/bitcoin/bitcoin
$ cd bitcoin
$ ./autogen.sh
$ CC=clang CXX=clang++ ./configure --with-sanitizers=undefined
$ git apply
diff --git a/src/random.cpp b/src/random.cpp
index f8ffda136..a3f84798d 100644
--- a/src/random.cpp
+++ b/src/random.cpp
@@ -274,7 +274,7 @@ void GetOSRand(unsigned char *ent32)

 void GetRandBytes(unsigned char* buf, int num)
 {
-    if (RAND_bytes(buf, num) != 1) {
+    if (true) {
         RandFailure();
     }
 }
$ make
$ UBSAN_OPTIONS="halt_on_error=1:print_stacktrace=1" src/bitcoind
logging.h:135:19: runtime error: member call on null pointer of type 'BCLog::Logger'
    #0 0x564eac67cff2 in void LogPrintf<>(char const*) /.../bitcoin/src/./logging.h:135:19
    #1 0x564eac67c054 in RandFailure() /.../bitcoin/src/random.cpp:53:5
    #2 0x564eac67c07d in GetRandBytes(unsigned char*, int) /.../bitcoin/src/random.cpp:278:9
    #3 0x564eabc15be7 in (anonymous namespace)::CSignatureCache::CSignatureCache() /.../bitcoin/src/script/sigcache.cpp:35:9
    #4 0x564eabc15be7 in __cxx_global_var_init.8 /.../bitcoin/src/script/sigcache.cpp:68
    #5 0x564eabc15be7 in _GLOBAL__sub_I_sigcache.cpp /.../bitcoin/src/script/sigcache.cpp
    #6 0x564eac8125cc in __libc_csu_init (/.../bitcoin/src/bitcoind+0x22185cc)
    #7 0x7f522eaa8b27 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:266
    #8 0x564eabc201d9 in _start (/.../bitcoin/src/bitcoind+0x16261d9)

Please note: before main.

That answers the raised questions?

@practicalswift
Copy link
Contributor Author

practicalswift commented Jan 6, 2019

@MarcoFalke @sipa These are places I've identified (so far) where randomness is requested before main:

  1. CSignatureCache ctor (src/script/sigcache.cpp):
    Calls GetRandBytes(nonce.begin(), 32)

  2. SaltedTxidHasher ctor (src/txmempool.cpp):
    Calls GetRand(std::numeric_limits<uint64_t>::{min,max}()) which calls GetRandBytes(…)

  3. static uint256 scriptExecutionCacheNonce(GetRandHash()); (src/validation.cpp):
    Calls GetRandHash() which calls GetRandBytes((…)

@maflcko
Copy link
Member

maflcko commented Jan 6, 2019

It seems odd that we need those globals (e.g. the mempool) before main even starts. They should probably be initialized later.

@practicalswift
Copy link
Contributor Author

@MarcoFalke That would be nice. And LogPrintf(…) should be robust and handle also callers who happen to call it before main, right?

@sipa
Copy link
Member

sipa commented Jan 6, 2019

There are generally two ways of dealing with initialization order of globals. One is to make all globals unique_ptrs or otherwise non-instantiated at startup and explicitly create all. Another approach is using std::call_once or static locals like #14955 does.

Since C++11 the second can be done with pretty low overhead and without too big hoops, so that's my preference over explicit initialization as it's less fragile.

@practicalswift
Copy link
Contributor Author

@sipa Do you agree that LogPrintf(…) should be robust and handle also callers who happen to call it before main?

@gmaxwell
Copy link
Contributor

gmaxwell commented Jan 6, 2019

What is even the point of g_logger in the first place? It seems to serve no purpose except creating UB when logging is called by global constructors. I propose instead PR#12954 be reverted: It served no obvious purpose and instead introduced a potentially serious misbehaviour.

@practicalswift
Copy link
Contributor Author

@MarcoFalke Could this one get a release milestone? :-)

@DrahtBot
Copy link
Contributor

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

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #15266 (memory: Construct globals on first use 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.

@practicalswift
Copy link
Contributor Author

Closing in favour of @MarcoFalke's better fix in #15266. Thanks!

maflcko pushed a commit to maflcko/bitcoin-core that referenced this pull request Feb 4, 2019
77777c5 log: Construct global logger on first use (MarcoFalke)

Pull request description:

  The (de)initialization order is not well defined in C++, so generally it is not safe to use globals as the (de/con)structor of one global could use the (de/con)structor of another global before/after it has been (con/de)structed.

  Specifically this fixes:
  * `g_logger` might not be initialized on the first use, so do that. (Fixes bitcoin#15111)

Tree-SHA512: eb9c22f4baf31ebc5b0b9ee6a51d1354bae1f0df186cc0ce818b4483c7b5a7f90268d2b549ee96b4c57f8ef36ab239dc6497f74f3e2ef166038f7437c368297d
@practicalswift practicalswift deleted the random-ub branch April 10, 2021 19:37
vijaydasmp pushed a commit to vijaydasmp/dash that referenced this pull request Sep 12, 2021
77777c5 log: Construct global logger on first use (MarcoFalke)

Pull request description:

  The (de)initialization order is not well defined in C++, so generally it is not safe to use globals as the (de/con)structor of one global could use the (de/con)structor of another global before/after it has been (con/de)structed.

  Specifically this fixes:
  * `g_logger` might not be initialized on the first use, so do that. (Fixes bitcoin#15111)

Tree-SHA512: eb9c22f4baf31ebc5b0b9ee6a51d1354bae1f0df186cc0ce818b4483c7b5a7f90268d2b549ee96b4c57f8ef36ab239dc6497f74f3e2ef166038f7437c368297d
vijaydasmp pushed a commit to vijaydasmp/dash that referenced this pull request Sep 16, 2021
77777c5 log: Construct global logger on first use (MarcoFalke)

Pull request description:

  The (de)initialization order is not well defined in C++, so generally it is not safe to use globals as the (de/con)structor of one global could use the (de/con)structor of another global before/after it has been (con/de)structed.

  Specifically this fixes:
  * `g_logger` might not be initialized on the first use, so do that. (Fixes bitcoin#15111)

Tree-SHA512: eb9c22f4baf31ebc5b0b9ee6a51d1354bae1f0df186cc0ce818b4483c7b5a7f90268d2b549ee96b4c57f8ef36ab239dc6497f74f3e2ef166038f7437c368297d
gades pushed a commit to cosanta/cosanta-core that referenced this pull request Apr 30, 2022
77777c5 log: Construct global logger on first use (MarcoFalke)

Pull request description:

  The (de)initialization order is not well defined in C++, so generally it is not safe to use globals as the (de/con)structor of one global could use the (de/con)structor of another global before/after it has been (con/de)structed.

  Specifically this fixes:
  * `g_logger` might not be initialized on the first use, so do that. (Fixes bitcoin#15111)

Tree-SHA512: eb9c22f4baf31ebc5b0b9ee6a51d1354bae1f0df186cc0ce818b4483c7b5a7f90268d2b549ee96b4c57f8ef36ab239dc6497f74f3e2ef166038f7437c368297d
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 18, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants