Skip to content

Fix byte expansion in rand_rangeXX() #9056

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

Merged
merged 3 commits into from
Jul 20, 2022

Conversation

TimWolla
Copy link
Member

TimWolla added 2 commits July 20, 2022 00:27
The last generated size is in bytes, whereas the shift is in bits. Multiple the
generated size by 8 to correctly handle each byte once.
…geXX()

We need to loop until we accumulate sufficient bytes, instead of just checking
once. The version in the rejection loop was already correct.
@zeriyoshi
Copy link
Contributor

@TimWolla LGTM, Thank you!

@TimWolla TimWolla merged commit 804c3fc into php:master Jul 20, 2022
@TimWolla TimWolla deleted the random-rand-range-shift branch July 20, 2022 15:33
r = algo->generate(status);
result = (result << status->last_generated_size) | r;
result = (result << (8 * status->last_generated_size)) | r;
Copy link
Member

@iluuu1994 iluuu1994 Jul 21, 2022

Choose a reason for hiding this comment

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

This triggers undefined behavior when shifting:

If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined

This should either do % 32 on the rhs of << so that the shifted width doesn't exceed the integer width, or yield 0 if rhs >= 32 (that's what both gcc and clang seem to do with the UB right now).

For context:

https://github.com/php/php-src/runs/7440978954?check_suite_focus=true

Copy link
Contributor

Choose a reason for hiding this comment

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

xref #9083

TimWolla added a commit to TimWolla/php-src that referenced this pull request Jul 21, 2022
The previous shifting logic is problematic for two reasons:

1. It invokes undefined behavior when the `->last_generated_size` is at least
as large as the target integer in `result`, because the shift is larger than
the target integer. This was reported in phpGH-9083.

2. It expands the returned bytes in a big-endian fashion: Earlier bytes are
shifting into the most-significant position. As all the other logic in the
random extension treats byte-strings as little-endian numbers this is
inconsistent.

By fixing the second issue, we can implicitly fix the first one: Instead of
shifting the existing bits by the number of "newly added" bits, we shift the
newly added bits by the number of existing bits. As we stop requesting new bits
once the total_size reached the size of the target integer we can be sure to
never invoke undefined behavior during shifting.

The get_int_user.phpt test was adjusted to verify the little-endian behavior.
It generates a single byte per call and we expect the first byte generated to
appear at the start of the resulting number.

see phpGH-9056 for a previous fix in the same area.
Fixes phpGH-9083 which reports the undefined behavior.
Resolves phpGH-9085 which was an alternative attempt to fix phpGH-9083.
Girgias pushed a commit that referenced this pull request Jul 22, 2022
The previous shifting logic is problematic for two reasons:

1. It invokes undefined behavior when the `->last_generated_size` is at least
as large as the target integer in `result`, because the shift is larger than
the target integer. This was reported in GH-9083.

2. It expands the returned bytes in a big-endian fashion: Earlier bytes are
shifting into the most-significant position. As all the other logic in the
random extension treats byte-strings as little-endian numbers this is
inconsistent.

By fixing the second issue, we can implicitly fix the first one: Instead of
shifting the existing bits by the number of "newly added" bits, we shift the
newly added bits by the number of existing bits. As we stop requesting new bits
once the total_size reached the size of the target integer we can be sure to
never invoke undefined behavior during shifting.

The get_int_user.phpt test was adjusted to verify the little-endian behavior.
It generates a single byte per call and we expect the first byte generated to
appear at the start of the resulting number.

see GH-9056 for a previous fix in the same area.
Fixes GH-9083 which reports the undefined behavior.
Resolves GH-9085 which was an alternative attempt to fix GH-9083.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants