GeezerVison is showing github.com — gh-96735: Fix undefined behaviour in struct unpacking functions by mdickinson · Pull Request #96739 · python/cpython
Conversation
mdickinson
commented
Sep 10, 2022
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
This PR fixes undefined behaviour in the struct module unpacking support functions bu_longlong, lu_longlong, bu_int and lu_int; thanks to @kumaraditya303 for finding these.
The fix is to accumulate the bytes in an unsigned integer type instead of a signed integer type, then to convert to the appropriate signed type. In cases where the width matches, that conversion will typically be compiled away to a no-op.
(Evidence from Godbolt: https://godbolt.org/z/5zvxodj64 .)
To make the conversions efficient, I've specialised the relevant functions for their output size: for bu_longlong and lu_longlong, this only entails checking that the output size is indeed 8. But bu_int and lu_int were used for format sizes 2 and 4 - I've split those into two separate functions each.
No tests, because all of the affected cases are already exercised by the test suite.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
matthiasgoergens reacted with heart emoji
This comment was marked as outdated.
…behaviour' into fix-struct-unpack-undefined-behaviour
mdickinson
changed the title
gh-96735: Fix undefined behaviour in bu_ulonglong
gh-96735: Fix undefined behaviour in struct unpacking functions
Sep 10, 2022
|
It's very tempting to convert all of these functions to use fixed-width integer types (uint16, uint32, uint64), but that seems like too invasive a change for a bugfix PR. It's something we could possibly do for 3.12 only.
|
erlend-aasland and matthiasgoergens reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, I tested this and indeed it fixes the UB.
Thanks for fixing the other ones too, just noticed that those were also affected.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
It's very tempting to convert all of these functions to use fixed-width integer types (uint16, uint32, uint64), but that seems like too invasive a change for a bugfix PR. It's something we could possibly do for 3.12 only.
I agree, it would be better but best left for 3.12 only.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
🤖 New build scheduled with the buildbot fleet by @mdickinson for commit 1294440 🤖
If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
@kumaraditya303 I've updated this PR to be more efficient, while still avoiding undefined behaviour and implementation-defined behaviour (specifically, the conversion from an unsigned type to the corresponding signed type when the value being converted is not representable in the signed type; cf. C99 §6.3.1.3p3).
The sign-extension is now branch free (and should compile to a no-op in the case that the C type being used has exactly the correct width), and the conversion from unsigned to signed should always compile to a no-op on any semi-reasonable compiler.
Godbolt example for the case where sign extension is needed: https://godbolt.org/z/e8roKrn6r
Godbolt example for the case where no sign extension is needed: https://godbolt.org/z/bPhbsT9Kn
|
kumaraditya303 reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
We are currently compiling all non-pydebug builds with -fwrapv (at least on gcc and clang). That means the shenanigans going on in _struct.c are actually perfectly defined behaviour.
I don't think we will turn off -fwrapv for 3.11. So there's no need for a stopgap solution.
I'd say: just do the Right Thing for 3.12 in main, and don't worry about 3.11. Especially if the Right Thing is very tempting to do anyway.
See also #96821 and #96678 (comment)
As a minimal change for 3.11, I would suggest enabling -fwrapv even when giving --with-pydebug.
That being said, I'm not opposed to this PR. It's a great start to getting everything safe for -fstrict-overflow.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Sep 21, 2022
I'd say: just do the Right Thing for 3.12 in main, and don't worry about 3.11. Especially if the Right Thing is very tempting to do anyway.
Agreed; I think I'll merge this for 3.12 only.
|
matthiasgoergens reacted with rocket emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Some not very rigorous timings, on macOS/Intel, non-optimised non-debug build. (This PR is not primarily about performance, but it would be unfortunate if it caused a significant performance regression.)
On this branch:
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('<hhh'); v=b'\x1b\xe0\xcfKSh'" "s.unpack(v)"
5000000 loops, best of 5: 92.6 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('>hhh'); v=b'\x1b\xe0\xcfKSh'" "s.unpack(v)"
5000000 loops, best of 5: 94.3 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('<iii'); v=b'\x1b\xe0\xcfKSh'*2" "s.unpack(v)"
5000000 loops, best of 5: 99.1 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('>iii'); v=b'\x1b\xe0\xcfKSh'*2" "s.unpack(v)"
5000000 loops, best of 5: 98.3 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('<qqq'); v=b'\x1b\xe0\xcfKSh'*4" "s.unpack(v)"
2000000 loops, best of 5: 99.1 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('>qqq'); v=b'\x1b\xe0\xcfKSh'*4" "s.unpack(v)"
2000000 loops, best of 5: 102 nsec per loop
On the main branch at commit 6281aff:
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('<hhh'); v=b'\x1b\xe0\xcfKSh'" "s.unpack(v)"
5000000 loops, best of 5: 94.9 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('>hhh'); v=b'\x1b\xe0\xcfKSh'" "s.unpack(v)"
5000000 loops, best of 5: 98.2 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('<iii'); v=b'\x1b\xe0\xcfKSh'*2" "s.unpack(v)"
2000000 loops, best of 5: 99.1 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('>iii'); v=b'\x1b\xe0\xcfKSh'*2" "s.unpack(v)"
2000000 loops, best of 5: 104 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('<qqq'); v=b'\x1b\xe0\xcfKSh'*4" "s.unpack(v)"
2000000 loops, best of 5: 101 nsec per loop
mdickinson@lovelace cpython % ./python.exe -m timeit -s "import struct; s = struct.Struct('>qqq'); v=b'\x1b\xe0\xcfKSh'*4" "s.unpack(v)"
2000000 loops, best of 5: 112 nsec per loop
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|