GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-96735: Fix undefined behaviour in struct unpacking functions by mdickinson · Pull Request #96739 · python/cpython

Conversation

@mdickinson

@mdickinson mdickinson commented Sep 10, 2022 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

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.

  • Issue: Undefined behavior in struct.unpack #96735

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

matthiasgoergens reacted with heart emoji
All reactions
  • ❤️ 1 reaction
@mdickinson
Fix undefined behaviour in bu_ulonglong
22fdce6
@mdickinson mdickinson added needs backport to 3.10 needs backport to 3.11 only security fixes labels Sep 10, 2022
@bedevere-bot bedevere-bot added the awaiting core review label Sep 10, 2022
mdickinson added 3 commits September 10, 2022 16:58
@mdickinson
Add regression test
e12943b
@mdickinson
Also apply the fix to lu_longlong
8efefd8
@mdickinson
Merge branch 'main' into fix-struct-unpack-undefined-behaviour
cba4b8b
@mdickinson

This comment was marked as outdated.

Sign in to view
mdickinson added 4 commits September 10, 2022 17:27
@mdickinson
Fix similar undefined behaviour issues in bu_int and lu_int
0d49e73
@mdickinson
Merge remote-tracking branch 'mdickinson/fix-struct-unpack-undefined-…
bfd714f
…behaviour' into fix-struct-unpack-undefined-behaviour
@mdickinson
Remove redundant test
5aeef09
@mdickinson
Fix accumulator type
204e924
@mdickinson mdickinson changed the title gh-96735: Fix undefined behaviour in bu_ulonglong gh-96735: Fix undefined behaviour in struct unpacking functions Sep 10, 2022
@mdickinson

mdickinson commented Sep 10, 2022

Copy link
Copy Markdown
Member Author

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
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mdickinson
mdickinson requested a review from kumaraditya303 September 10, 2022 17:04
kumaraditya303
kumaraditya303 approved these changes Sep 10, 2022
View reviewed changes

@kumaraditya303 kumaraditya303 left a comment

Copy link
Copy Markdown
Contributor

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.

All reactions
@kumaraditya303

kumaraditya303 commented Sep 10, 2022

Copy link
Copy Markdown
Contributor

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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mdickinson added 2 commits September 13, 2022 20:04
@mdickinson
Branch-free sign-extension
994703e
@mdickinson
Fix unconventional spacing
1294440
@mdickinson mdickinson added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Sep 13, 2022
@bedevere-bot

bedevere-bot commented Sep 13, 2022

Copy link
Copy Markdown

🤖 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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Sep 13, 2022
@mdickinson

mdickinson commented Sep 13, 2022

Copy link
Copy Markdown
Member Author

@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
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mdickinson mdickinson mentioned this pull request Sep 14, 2022
gh-96678: A few asserts to demonstrate undefined behaviour matthiasgoergens/cpython#18
Open
@matthiasgoergens

matthiasgoergens commented Sep 14, 2022 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

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.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum gvanrossum mentioned this pull request Sep 18, 2022
PEP 7: Mention signed overflow explicitly python/peps#2796
Closed
This was referenced Sep 21, 2022
gh-96821: Add config option --with-strict-overflow #96823
Merged
Enable -fstrict-overflow #96821
Open
@mdickinson

mdickinson commented Sep 25, 2022

Copy link
Copy Markdown
Member Author

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
All reactions
  • 🚀 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mdickinson mdickinson removed the needs backport to 3.10 label Sep 25, 2022
@mdickinson mdickinson removed the needs backport to 3.11 only security fixes label Sep 25, 2022
@mdickinson

mdickinson commented Sep 25, 2022

Copy link
Copy Markdown
Member Author

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
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mdickinson
mdickinson merged commit f5f047a into python:main Sep 25, 2022
@bedevere-bot bedevere-bot removed the awaiting core review label Sep 25, 2022
@mdickinson
mdickinson deleted the fix-struct-unpack-undefined-behaviour branch September 25, 2022 09:55
@mdickinson mdickinson mentioned this pull request Sep 25, 2022
Undefined behavior in struct.unpack #96735
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@kumaraditya303 kumaraditya303 kumaraditya303 approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

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.

4 participants

@mdickinson @kumaraditya303 @bedevere-bot @matthiasgoergens
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.