Skip to content

Consistently bounds-check vin/vout access #13268

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

Closed
wants to merge 3 commits into from

Conversation

Empact
Copy link
Contributor

@Empact Empact commented May 18, 2018

An out of range transaction output is invalid / an error condition, so let's
blow up rather than skip the output or default.

I audited indexing into vin/vout looking for unchecked or quietly handled bounds errors and converted them to throw on out of bounds, because noisy failure is better than silent failure.

Disclaimer: I'm not that familiar with the role that out of bounds outputs and inputs might play in the network, apart from the SIGHASH_SINGLE case commented below which was established in the test. Please do educate me if I'm misguided on this. If there are more "valid but invalid" cases we should get them under test.

@sdaftuar
Copy link
Member

I'd suggest dropping the third commit, which at first glance would seem to introduce a number of vulnerabilities. I agree though that it would be useful to add tests that would fail if that third commit were introduced.

@Empact
Copy link
Contributor Author

Empact commented May 18, 2018

@sdaftuar thanks, by vulnerabilities, I suppose you mean there is a risk of maliciously-crafted transactions bringing down the recipient node by triggering the throws, is that right? I hadn't been considering the changes through that lens, but I can take a second look with that in mind.

@Empact
Copy link
Contributor Author

Empact commented May 18, 2018

@sdaftuar I removed the third commit. Seems the remaining cases should be safe given they're already asserted against or otherwise assumed to be safe, such that the invalid values are not contended with at all.

@Empact Empact changed the title Consistently bounds-check vin/vout access, and throw on out-of-bounds access Consistently bounds-check vin/vout access May 18, 2018
@theuni
Copy link
Member

theuni commented May 18, 2018

Concept ACK on clarifying bounds, but I really don't like the use of exceptions replacing explicit prior checks. I understand that they accomplish the same thing, but IMO it is far less safe because it equates checks that are allowed to fail with those that logically never should. It also turns potential compile-time checks into guaranteed runtime ones.

Lastly, it really harms code clarity. This one for example, is a significant regression IMO.

@ryanofsky
Copy link
Contributor

it equates checks that are allowed to fail with those that logically never should

According to http://en.cppreference.com/w/cpp/error/logic_error, this exception type "reports errors that are a consequence of faulty logic within the program such as violating logical preconditions or class invariants", so it doesn't seem switching from operator[] to at() in c++ should be taken to indicate any change in preconditions or intent.

But there may still be practical reasons to favor throwing exceptions explicitly or using assert() instead of relying on at(). For example, Matt thought it was important to assert instead of raising an exception here: #11640 (comment) to prevent errors from being caught in generic catch blocks.

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 174de7e8fd4b4e52714029b6f6dd27c1ed5d080e. Change looks correct, but I think it would be safer to only add exceptions in places that currently trigger out of bounds memory accesses, not in places that currently trigger asserts, since assert behavior is probably preferable in most of these cases.

@sipa
Copy link
Member

sipa commented May 18, 2018

it doesn't seem switching from operator[] to at() in c++ should be taken to indicate any change in preconditions or intent.

But there may still be practical reasons to favor throwing exceptions explicitly or using assert() instead of relying on at().

This seems to only be a philosophical distinction to me. Yes, maybe std::logic_error isn't designed to caught by normal program code, and can thus be treated similarly to an assertion failure - but that's not the case in our code. We have 54 catch (const std::exception& e) cases in our codebase, some of which will happily continue after catching an out of bounds error.

Perhaps we should work on making those catch statements be more specific and guarantee they can't catch std::logic_errors? Without that, I'm very scared about replacing asserts with exceptions.

@Empact
Copy link
Contributor Author

Empact commented May 18, 2018

Ok my earlier message (now removed) related to a whitespace error - we only have 6 catch( blocks without a space separating the catch and parens, but we have 54 const std::exception catches and 71 if you include .... So the behavior change concern is fair. I'll drop the first commit as well.

relevant regex: catch ?\([^)]*(std::exception&|\.\.\.)

@Empact Empact force-pushed the vout-bounds-check branch from 174de7e to ff3f8c1 Compare May 18, 2018 19:45
@Empact
Copy link
Contributor Author

Empact commented May 18, 2018

Checks are now strictly additional.

@Empact Empact force-pushed the vout-bounds-check branch from ff3f8c1 to a1e930f Compare May 18, 2018 20:00
@Empact
Copy link
Contributor Author

Empact commented May 18, 2018

Removed a check relative to CreateTransaction nOutput where the value was locally guaranteed to be valid.

@DrahtBot
Copy link
Contributor

The last travis run for this pull request was 65 days ago and is thus outdated. To trigger a fresh travis build, this pull request should be closed and re-opened.

Empact added 3 commits July 29, 2018 20:15
This adds bounds checking to any access where bounds checking was
not already locally ensured.

Most of the remaining unchecked access is in the context of a loop over
valid indexes, so already safe.
* CWallet::GetDebit, SignTransaction, IsMine, IsAllFromMe
* CCoinsViewMemPool::GetCoin

An out of range transaction output is invalid / an error condition, so let's
blow up rather than skip the output or default.

Comment the exception: that SIGHASH single with out of bounds index is valid

Notes:
* `at` does range checking.
* `COutpoint.n` is not guaranteed to be valid - e.g.its default value
is -1.
@DrahtBot
Copy link
Contributor

Needs rebase

@bitcoin bitcoin locked as resolved and limited conversation to collaborators Dec 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants