Skip to content

ipaddress, random, rainbowio: conversions that give the wrong value - #11378

Merged
dhalbert merged 1 commit into
adafruit:mainfrom
peterbay:conversions-that-give-wrong-values
Sep 14, 2026
Merged

dhalbert merged 1 commit into
adafruit:mainfrom
peterbay:conversions-that-give-wrong-values

Conversation

@peterbay

Copy link
Copy Markdown

Code written by Claude Code, guided and corrected by @peterbay.

The problem

Six conversions that produce a wrong value rather than an error: an address parsed from the wrong bits, an address built from uninitialised memory, a colour that comes out negative, and a seeded generator that is not reproducible.

The changes

  • ipaddress never range-checked the octets. Each was shifted into place and OR-ed in, so anything above 255 carried into its neighbour: ip_address("300.1.1.1") gave 44.1.1.1 and ip_address("1.2.3.256") gave 1.2.3.0.

  • An octet too long for a small int was read as one anyway. mp_parse_num_integer returns a big-int object for a long enough literal, and MP_OBJ_SMALL_INT_VALUE on one of those reinterprets the object word: ip_address("99999999999.1.1.1") parsed as 56.201.1.17 on one run here and 144.201.1.17 on another, the top octet being whatever the object landed at.

  • IPv4Address used its buffer pointer whether or not it had been set. Anything that is not an int, a string or a buffer fell through the last else with buf still NULL and value never written, and the address was then built from four bytes read at address zero — IPv4Address(object()) gave 0.4.0.32. It raises now.

  • The dot count was only bounded from above. Three parts were rejected only because the third slot of period_index stays zero and the parse of a nonsensical length throws; requiring exactly three says what is meant.

  • rainbowio.colorwheel did nothing useful for a negative position. The range reduction cast the quotient to uint32_t, so colorwheel(-1) returned -768 and colorwheel(-100) returned -11264, neither of which is a colour. The reduction is signed now, with one addition to bring the result back into [0, 256).

  • random.seed(0xEDA4BABA) did not seed. The generator used its own initial pad value as the mark for "never seeded", so seeding with that exact number sent it to the hardware source instead and the sequence was not reproducible. A flag records it.

  • random.randint(a, b) overflowed for the largest b. It calls randrange(a, b + 1, 1), and for b at the top of mp_int_t that addition overflows, so the span was computed from a negative bound. It raises for that one value now. If you would rather it computed the range correctly instead of refusing it, say so and I will do that — refusing seemed better than returning a number drawn from a broken span, which is what it does today.

Testing

Seeed XIAO nRF52840 Sense, on two builds differing only by these changes. ipaddress follows CIRCUITPY_WIFI and this port has none, so both builds set CIRCUITPY_IPADDRESS=1.

before after
ip_address("300.1.1.1") 44.1.1.1 ValueError
ip_address("1.2.3.256") 1.2.3.0 ValueError
ip_address("99999999999.1.1.1") 56.201.1.17, and 144.201.1.17 on a later run ValueError
IPv4Address(object()) 0.4.0.32 ValueError: Invalid address
IPv4Address(b"\x01\x02\x03") ValueError, unchanged ValueError
colorwheel(-1) -768 16711680
colorwheel(-100) -11264 10965
colorwheel(300) 8094720, unchanged 8094720
seed(0xEDA4BABA) twice, then six draws not reproducible reproducible
seed(12345) twice reproducible, unchanged reproducible
randint(0, 2**31-1) returns a number from an overflowed span ValueError

Two of the changes do not show on this port. Requiring exactly three dots only makes explicit what the parse already rejected by accident. And mp_obj_get_int_maybe writing an mp_int_t through a uint32_t * is the same width here; it is a defect only on a 64-bit build.

No new translatable strings.

ipaddress never range-checked its octets, so anything above 255 was shifted
into place and carried into its neighbour: "300.1.1.1" parsed as 44.1.1.1 and
"1.2.3.256" as 1.2.3.0. An octet too long for a small int came back as a
big-int object and MP_OBJ_SMALL_INT_VALUE reinterpreted the object word;
"99999999999.1.1.1" parsed as 56.201.1.17. IPv4Address used its buffer
pointer whether or not the last branch had set it, so anything that is not an
int, a string or a buffer built the address from four bytes read at address
zero. The dot count is required to be exactly three rather than merely no
more than three.

rainbowio.colorwheel reduced its argument through a cast to uint32_t, which
does nothing useful for a negative position: colorwheel(-1) returned -768.

random used the generator's initial pad value as the mark for "never seeded",
so random.seed(0xEDA4BABA) went to the hardware source instead and the
sequence was not reproducible. randint(a, b) calls randrange(a, b + 1, 1),
and for the largest representable b that addition overflows.
@peterbay

Copy link
Copy Markdown
Author

Testing and diagnostic script.
conversions_that_give_wrong_values.py

@dhalbert dhalbert left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks - these all make sense.

@dhalbert
dhalbert merged commit 66c3941 into adafruit:main Sep 14, 2026
685 of 687 checks passed
tannewt pushed a commit that referenced this pull request Sep 22, 2026
#11378 made `randint(a, b)` raise `ValueError` when `b` is the largest
`mp_int_t`, to avoid the `b + 1` overflow in `randrange(a, b + 1, 1)`.
On 32-bit ports that is `0x7FFFFFFF`, which the WIZnet DHCP library
passes, so `randint(1, 0x7FFFFFFF)` stopped working (#11441).

Add `shared_modules_random_randint()`, which computes the span
`b - a + 1` in unsigned arithmetic so it cannot overflow for any
`a <= b`, and use it from the binding instead of forming `b + 1`.

Fixes #11441

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants