-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #12272 (removeContradiction() Avoid use-after-free on multiple remove) #5707
Conversation
Thanks for your contribution. Please add the POC code to the unit tests. Based on the code this should most likely go into |
bc63dbc
to
770d4b6
Compare
I wonder if the better fix would be to use |
dirkmueller@67f3f77 is doing that. seems slightly faster also. |
I cannot reproduce the ASAN failure locally with either the unit test or via an external file. I tried GCC and Clang. |
yes, it is very tricky to trigger. Like the original reporter, with the given reproducer it only triggers on GCC 7.x. the issue is only happening when the remove() finds more than one element, and the first element remove triggers a resize and reallocate down. when it doesn't happen (as is the case with any other implementation) there is no ASAN violation logged. That's why I originally didn't add the testcase to the PR because it's not gonna trigger reliable. Still I think the deep copy avoids the issue as does a conversion of the code from remove() to erase() does. |
(an easy way to trigger is by running it inside an opensuse leap 15.4 container - can provide instructions if wanted) |
GCC 7 is available on ubuntu 20.04 which is one of my local systems I will give it a spin tomorrow. |
I can reproduce the issue with GCC 7 and valgrind:
|
I am not strongly against the unit test.. however it's not by design to handle garbage code in valueflow etc.. at some random point in time in the future cppcheck might reject this code and say there is syntax error... and then this test will not test if there is a regression in the valueflow anymore.. if we really wanted to unit test that there is not this bug in removeContradiction() I guess we could manually call removeContradiction() in the test with certain input data however I do feel that is unfortunate since the function has internal linkage. |
would it make sense to create a unit test that creates a token "&" or whatever it is.. and then call the Token::addValue with whatever is problematic? |
I'm actually not in favor of a unit test for this, but it was requested in the review.
Right. I think in the current state the unit test is fair. it won't help but it will also not harm as there is a comment that the intention is "don't crash". I expect it to get deleted whenever cppcheck starts raising an error on the invalid code. If we're nervous that somebody will revert the patch accidentally and reintroduce the issue, then the only path forward is to rewrite the code to not use |
Any fix should have an accompanied test case. That was not the case several years ago which is why there have been several regressions across the code. If there is no test case to trigger this then there should be no need for the change at all. Even if there is currently only one known configuration that causes this there's still a infinite amount of unknown ones which might also encounter this. Also the underlying code for handling this might change in the future and that might introduce a different issue which would be uncovered by the test. |
I think the fix itself sounds good. Thanks for your explanation that makes sense to me. I don't think it's a good idea to test this with garbage code.. it's not guaranteed that removeContradiction() will be called when the input is garbage. how can I reproduce this with a token? Something like:
Can you give me feedback ..? I can add the unit test if you want. |
@dirkmueller One thing that looks suspicious to me in removeContradiction is:
if removePointValue removes "x" from values then "x" will obviously be invalid. But the loop continues and "x" is used again.. I am not sure how a range-for-loop behaves if elements in the container are removed during the looping. Is it defined that it's safe at all.. and is it defined that in the next loop the next element is accessed or will 1 item be skipped? I have implemented a possible test case after logging what removeContradiction calls for the garbage code. Please feel free to try out if it can reproduce the crash for you. Add it in testtoken.cpp:
|
I think we will always keep the test code, but change the assertion so it checks that a syntax error is reported. So it will no longer test that we don't have regressions in the removeContradiction. This is why I am against a garbage code test. |
770d4b6
to
944e838
Compare
I would be interested to reproduce this. Can I reproduce it in a docker container? I tried this:
output from valgrind:
|
The crash is within the same single remove() invocation.
it sets bail to true which returns true immediately. Unless I'm missing what you're referring to.
It is generally unsafe to continue in a range-for-loop if an element of the iterated container is removed. That's where erase(), which advances the iterator, is used for. I made a variant with that under #5707 (comment)
Thank you for helping with the test case. actually, while retesting, I noticed that no testcase is needed at all, as the existing testcases trigger this issue in multiple places. I'll push an updated patch shortly after finishing testing. |
good 👍 |
yes, you can.
|
944e838
to
8437a2f
Compare
thanks, I copy pasted your commands and it reproduce the error. |
…move) As reported in https://sourceforge.net/p/cppcheck/discussion/general/thread/fa43fb8ab1/ removeContradiction() minValue/maxValue.remove(..) can access free'd memory as it removes all matching values by iterating over the complete list. Switch to use .erase() and using iterators to avoid that. Signed-off-by: Dirk Müller <[email protected]>
8437a2f
to
f161fa7
Compare
it's really sneaky.. to my bare eyes the preprocessor code looks safe. |
Thanks! So somehow it would be good to start using opensuse in our testing also so we can detect regressions. |
As reported in https://sourceforge.net/p/cppcheck/discussion/general/thread/fa43fb8ab1/ removeContradiction() minValue/maxValue.remove(..) can access free'd memory as it removes all matching values by iterating over the complete list. Creating a full copy instead of a reference avoids this issue.