net: fix use-after-free in tests#18376
Conversation
In PeerLogicValidation::PeerLogicValidation() we would schedule a lambda function to execute later, capturing the local variable `consensusParams` by reference. Presumably this was considered safe because `consensusParams` is a reference itself to a global variable which is not supposed to change, but it can in tests. Fixes bitcoin#18372
|
Configure and run as: I confirm the following:
So, this was introduced in #18289. I have no clue why travis was green for it. - scheduler.scheduleEvery(std::bind(&PeerLogicValidation::CheckForStaleTipAndEvictPeers, this, consensusParams), EXTRA_PEER_CHECK_INTERVAL * 1000);
+ scheduler.scheduleEvery([&] { this->CheckForStaleTipAndEvictPeers(consensusParams); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL});Indeed |
There was a problem hiding this comment.
ACK 7d8e1de
In PeerLogicValidation::PeerLogicValidation() we would schedule a lambda
function to execute later, capturing the local variable
consensusParamsby reference.Presumably this was considered safe because
consensusParamsis a
reference itself to a global variable which is not supposed to change,
but it can in tests.
Correct, it is a reference to a global, which I assumed to not change after initialization. This does not hold for unit test, for example the txvalidation one:
bitcoin/src/test/util/setup_common.cpp
Lines 160 to 166 in ce87d56
| // timer. | ||
| static_assert(EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL, "peer eviction timer should be less than stale tip check timer"); | ||
| scheduler.scheduleEvery([&] { this->CheckForStaleTipAndEvictPeers(consensusParams); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL}); | ||
| scheduler.scheduleEvery([this, consensusParams] { this->CheckForStaleTipAndEvictPeers(consensusParams); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL}); |
There was a problem hiding this comment.
I think this can be made = to just mirror what std::bind did. (this is a pointer, so it shouldn't matter whether it is copied or referenced, and for consensusParams we want it to be copied).
| scheduler.scheduleEvery([this, consensusParams] { this->CheckForStaleTipAndEvictPeers(consensusParams); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL}); | |
| scheduler.scheduleEvery([=] { this->CheckForStaleTipAndEvictPeers(consensusParams); }, std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL}); |
There was a problem hiding this comment.
The = is also what is recommended in the "Tutorial" (written by me, so count that against me):
Line 28 in 3a8d250
There was a problem hiding this comment.
Doh! I was about to apply this suggestion, but then realized that The implicit capture of *this when the capture default is = is deprecated. (since C++20) :-/ Indeed:
auto m() {
return [=](){
std::cout << "lambda: this=" << this << std::endl;
};
}Produces:
clang++10 -std=c++20 -Wdeprecated t.cc -o t
t.cc:96:49: warning: implicit capture of 'this' with a capture default of '=' is deprecated
[-Wdeprecated-this-capture]
std::cout << "lambda: this=" << this << std::endl;
^
t.cc:95:21: note: add an explicit capture of 'this' to capture '*this' by reference
return [=](){
^
, this
Better not add code that would have to be fixed later, so I would rather keep it as is.
What do you think?
PS this is always captured by reference and I think all of the following do the same wrt this: [=], [this], [&], std::bind
There was a problem hiding this comment.
In that case I'd prefer to use [=, this], but this might not compile either on our current C++ target? According to the page you linked:
[=, this] {}; // until C++20: Error: this when = is the default
// since C++20: OK, same as [=]So, given that we won't be switching to C++20 any time soon, I'd still prefer the [=]. Then it can be changed, if and when we switch to C++20?
Just a nit, though. ACK from me either way.
|
Concept ACK -- premature optimisation is the root of many lifetime issues :) Somewhat related: Investigate potential lifetime issues in cases where we are returning "const std::string&". This is a great talk about recurring C++ bugs at Facebook: this segment on lifetime issues due to premature optimization might have some relevance here :) |
|
@practicalswift I don't think it is helpful to call this premature optimization. This was not an optimization attempt. I think it was reasonable to keep it a reference and pass it along in the scheduler. And I assume the four people who reviewed my pull request agreed with me. That some unit tests spin up a PeerLogicValidation and then change the consensus parameters while running is documented as a temporary hack in the test code. And the oversight was that not everyone had this temporary hack in mind while writing/reviewing the code. |
|
ACK 7d8e1de |
|
ACK 7d8e1de
Point taken :) |
fad84b7 test: Activate segwit in TestChain100Setup (MarcoFalke) fa11ff2 test: Pass empty tx pool to block assembler (MarcoFalke) fa96574 test: Move doxygen comment to header (MarcoFalke) Pull request description: This fixes not only a TODO in the code, but also prevents a never ending source of uninitialized reads. E.g. * #18376 * #19704 (comment) * ... ACKs for top commit: jnewbery: utACK fad84b7 Tree-SHA512: 64cf16a59656d49e022b603f3b06441ceae35a33a4253b4382bc8a89a56e08ad5412c8fa734d0fc7b58586f40ea6d57b348a3b4838bc6890a41ae2ec3902e378
fad84b7 test: Activate segwit in TestChain100Setup (MarcoFalke) fa11ff2 test: Pass empty tx pool to block assembler (MarcoFalke) fa96574 test: Move doxygen comment to header (MarcoFalke) Pull request description: This fixes not only a TODO in the code, but also prevents a never ending source of uninitialized reads. E.g. * bitcoin#18376 * bitcoin#19704 (comment) * ... ACKs for top commit: jnewbery: utACK fad84b7 Tree-SHA512: 64cf16a59656d49e022b603f3b06441ceae35a33a4253b4382bc8a89a56e08ad5412c8fa734d0fc7b58586f40ea6d57b348a3b4838bc6890a41ae2ec3902e378
In PeerLogicValidation::PeerLogicValidation() we would schedule a lambda
function to execute later, capturing the local variable
consensusParamsby reference.Presumably this was considered safe because
consensusParamsis areference itself to a global variable which is not supposed to change,
but it can in tests.
Fixes #18372