Skip to content
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 bounds checking pattern in parser #539

Merged
merged 3 commits into from
Sep 22, 2020
Merged

Fix bounds checking pattern in parser #539

merged 3 commits into from
Sep 22, 2020

Conversation

chfast
Copy link
Collaborator

@chfast chfast commented Sep 21, 2020

The previous pattern pos + size < end was incorrect leading to
possible undefined behavior and potential addition overflow allowing
the check bypass. The correct check is to compute the remaining size
and compare with the declared size. This way invalid pointer comparison
is replaced with correct pointer subtraction (pos and end are from the
same memory buffer) and integer comparison: (end - pos) < size.

This will be additionally check with extended ASan options in #469.

@codecov
Copy link

codecov bot commented Sep 21, 2020

Codecov Report

Merging #539 into master will increase coverage by 0.00%.
The diff coverage is 100.00%.

@@           Coverage Diff           @@
##           master     #539   +/-   ##
=======================================
  Coverage   98.73%   98.73%           
=======================================
  Files          58       58           
  Lines        8681     8703   +22     
=======================================
+ Hits         8571     8593   +22     
  Misses        110      110           

@chfast
Copy link
Collaborator Author

chfast commented Sep 22, 2020

Good refactoring/optimization opportunity after is to pass remaining_size instead of end pointer to parsing function.

@chfast chfast added the bug Something isn't working label Sep 22, 2020
@@ -29,8 +29,8 @@ inline parser_result<uint8_t> parse_byte(const uint8_t* pos, const uint8_t* end)
template <typename T>
inline parser_result<T> parse_value(const uint8_t* pos, const uint8_t* end)
{
constexpr auto size = sizeof(T);
if (pos + size > end)
static constexpr int size = sizeof(T);
Copy link
Collaborator

Choose a reason for hiding this comment

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

why static?

Copy link
Collaborator

Choose a reason for hiding this comment

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

And why int, isn't it then signed-unsigned comparison below?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Pointer subtraction returns signed integer. I will use ptrdiff_t for clarity if easily available.
For static I don't remember, I will remove it.

@@ -105,11 +105,12 @@ bool utf8_validate(const uint8_t* pos, const uint8_t* end) noexcept
else
return false;

assert(required_bytes > 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could put this line after the comment below

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh yeah, good point.

@gumb0
Copy link
Collaborator

gumb0 commented Sep 22, 2020

possible undefined behavior

when would it be UB?

@chfast
Copy link
Collaborator Author

chfast commented Sep 22, 2020

when would it be UB?

The adversarial element is size (it comes from the outside).

The first issue is in pos + size expression. Information about "pointer and integer addition":

If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

And later this provokes another issue in pointer comparison (pos + size) < end:

When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object.

In practice, this is a security bug because in case there is a way to have a wasm module with size big enough to cause pos + size overflow the bounds check will be bypassed. Maybe not practical on 64-bit systems as I believe size is limited to 32-bits. Still worth exploring in context of other wasm binary readers.

Use smaller (often exact size) buffers for test cases of input bounds
checking in parser. This way we can detect incorrect pointer comparisons
with ASan's pointer-compare and pointer-subtract instrumentation.
The previous pattern `pos + size < end` was incorrect leading to
possible undefined behavior and potential addition overflow allowing
the check bypass. The correct check is to compute the remaining size
and compare with the declared size. This way invalid pointer comparison
is replaced with correct pointer subtraction (pos and end are from the
same memory buffer) and integer comparison: `(end - pos) < size`.
See previous commit for explanation.
@chfast chfast merged commit dc8ecf3 into master Sep 22, 2020
@chfast chfast deleted the fix_parser_eof branch September 22, 2020 19:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants