Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
[rpc]Avoid possibility of NULL pointer dereference in getblockchaininfo(...) #10619
Conversation
pavlosantoniou
changed the title from
Avoid possibility of NULL pointer dereference in getblockchaininfo(...) to [rpc]Avoid possibility of NULL pointer dereference in getblockchaininfo(...)
Jun 17, 2017
|
Under which circumstances can Tip() be NULL? If it is NULL, it crashes anyway as there are many more instances of Tip() dereferences without NULL checks in the same function alone (i.e. for If it can be NULL, then we should define how the rpc output should look like in this case, and fix all instances / make the output consistent. If it can't be null, we should ensure that in the Tip() function. |
|
If Tip() never returns NULL, then checking Do you think this should become: Since Tip() can theoretically return NULL, I think that the best coding practice is to check for nullness somehow. |
In that case, yes. As I said, if tip actually returns null, the function will crash as it is today, so we should either fix all of that (and document/test it), or have tip() not return null (and work backwards to ensure correctness). |
|
I think that checking the return value of a function that returns a pointer for nullness is a good practice that will safeguard the code from dereferencing a null pointer either under the current or under any other future implementation of Tip(). Actually, I see that in other parts of the code the return value of Tip() is checked for nullness. |
|
C++ has the problem that pointers are used for two orthogonal reasons: 1) to avoid unneeded copies, and 2) to encode the possibility of NULL. If a function can't return NULL but still returns a pointer (for the first reason), I don't think callers need to check, as it complicates the code for no good reason. If someone changes the function to return null in the future, it's an API change (unfortunately not caught by the compiler) and they need to fix the callsites. There are probably edge cases in which Tip() returns NULL (on startup and shutdown, and maybe one can even mark the genesis block as invalid via an rpc command?). What about throwing an |
|
I have modified the commit for this PR. |
|
Thank you. utACK cf67a3c for the code. As for the concept, I'd rather have more people confirm if this makes sense. |
|
Concept ACK. In addition to the check you added I suggest adding an assertion before accessing CBlockIndex *block = chainActive.Tip();
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA))
block = block->pprev;
+ assert(block && "An empty blockchain should not be possible here.");
obj.push_back(Pair("pruneheight", block->nHeight));I'm assuming a state transition from Thanks for reporting this @pavlosantoniou. You beat me to reporting it! :-) I had this in my backlog of possible NULL pointer dereferences to report, but as I try to limit the number of open PR:s pertaining to the same class of issues I figured I'd await the merge of a similar issue I reported in #9549:
|
|
Thank you all for your comments, I have added the assertion in the commit for this PR. |
|
@pavlosantoniou Oh, I forgot to mention: I think you should restore the null check in the while loop too: - while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA))
+ while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) |
|
@practicalswift that shouldn't be necessary with the check before that the tip is not null? If you are afraid that the return value changes between two calls of Tip() in that function, the result should just be stored in a variable in the beginning and reused. |
| @@ -1165,6 +1165,11 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) | ||
| + HelpExampleRpc("getblockchaininfo", "") | ||
| ); | ||
| + if (!chainActive.Tip()) |
sipa
Jun 17, 2017
Owner
This can be an assert too; RPC isn't available before the InitBlockIndex call, after which chainActive cannot be empty.
fanquake
added the
RPC/REST/ZMQ
label
Jun 18, 2017
|
@pavlosantoniou I suggest using the following patch (while removing the other changes): diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index 8f7f768..baf8dae 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1165,6 +1165,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
+ HelpExampleRpc("getblockchaininfo", "")
);
+ assert(chainActive.Tip() && "An empty blockchain should not be possible here.");
LOCK(cs_main);
UniValue obj(UniValue::VOBJ);
@@ -1196,6 +1197,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA))
block = block->pprev;
+ assert(block && "An empty blockchain should not be possible here.");
obj.push_back(Pair("pruneheight", block->nHeight));
}
return obj; |
|
@practicalswift why are the asserts needed? I guess they don't hurt, on the other hand, those asserts check something trivial. @pavlosantoniou can you assign Tip() to one var and reuse it in the rest of the function? Then there should be no doubt about its value. |
|
@benma The asserts serve as documentation of an assumption that is not obvious from reading the source code. As an added bonus it guides static analysis tools such as |
|
@practicalswift thanks, I didn't know about this tool. How many warnings like this are there? |
|
@practicalswift Concerning your proposed patch, I agree with the addition of the two assertions. How about @benma 's proposal, to save the return value of |
|
In this code … CBlockIndex *block = chainActive.Tip();
while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA))
block = block->pprev;… then |
|
I have updated the commit with the suggested changes. |
| @@ -1196,6 +1197,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) | ||
| while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) | ||
| block = block->pprev; | ||
| + assert(chainActive.Tip() && "An empty blockchain should not be possible here."); |
|
Yes @benma you are right! Corrected |
|
ACK be42c0d |
|
utACK be42c0d |
|
@benma There are currently three null pointer dereference warnings reported by
|
|
There are a ton of places we assume chainActive.Tip() is non-NULL, and init makes sure there is something there. Adding assert()s everywhere we dereference chainActive.Tip() during normal runtime seems like an excersize in too many assert()s. |
|
@TheBlueMatt I think it makes sense to add an assertion in this case to guide |
|
utACK be42c0d (edit: just noticed I already ACK'd this commit before) @pavlosantoniou I feel like #10619 (comment) hasn't been addressed, really (i.e. why |
|
@benma Are you refering to the saving-and-reusing-block's-value part of the comment? |
|
Yes, with regards to the |
|
utACK be42c0d |
|
Agree with @TheBlueMatt. |
|
"Avoid possibility of NULL pointer dereference" is overly alarmist as this cannot happen in practice. I agree with @TheBlueMatt . We make sure that |
TheBlueMatt
reviewed
Jul 25, 2017
If it fixes an automated warning, I suppose I'm fine with this, though, really, the number of automated warning fixes is getting a bit tiring.
| @@ -1165,6 +1165,7 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) | ||
| + HelpExampleRpc("getblockchaininfo", "") | ||
| ); | ||
| + assert(chainActive.Tip() && "An empty blockchain should not be possible here."); |
|
Which automated warning, from what? |
|
@laanwj I was referring to the one at #10619 (comment) |
|
Just a bit of advice, I'd prefer that titles like "Avoid null deference" be reserved for cases where we believe one is actually possible. Among other reasons, commits like this in the history will suppress legitimate issue reports when someone sees a crash then "oh, I guess thats been fixed". :) In terms of guarding them with asserts, sure. That is a reasonable thing to do which we do elsewhere. But could PRs like this please get titles like "Add a null guard in function X" unless we strongly suspect there was a real issue? (and obviously, if it was reachable with a null here, an assert wouldn't be the right fix.) |
|
@sipa Agree about the wording! @laanwj I think the reason for the static analyzer warning in this case (and other cases reported as "possible null pointer dereference") is that a More specifically in this case:
In this case:
Both
So if we're sure that (In addition to adding a a Perhaps removing the This will silence the diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index d65e107..1915a45 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -1193,7 +1193,8 @@ UniValue getblockchaininfo(const JSONRPCRequest& request)
if (fPruneMode)
{
CBlockIndex *block = chainActive.Tip();
- while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA))
+ assert(block && "chainActive.Tip() != nullptr assumed");
+ while (block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA))
block = block->pprev;
obj.push_back(Pair("pruneheight", block->nHeight));Note that the |
pavlosantoniou commentedJun 17, 2017
•
edited
The variable
blockis initialized by the return value of Tip() which may be NULL.The while loop condition takes this into account and checks for nullness before dereferencing.
If Tip() returns null, the while loop is never executed and the null pointer is dereferenced right after (
block->nHeight)With this fix, there is a single check for nullness of
block.A
JSONRPCErroris thrown in this case.After that,
blockis only assigned non-null values.The code
block->nHeightis never executed if block is NULL.The while loop condition is also simplified.