Uuid::new_v7 mishandles usable_counter_bits at 0 and 126,127,128 #871
Labels
No labels
breaking
bug
dependencies
good first issue
help wanted
unstable
No milestone
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-uuid-rs/uuid#871
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Uuid::new_v7usesu128::overflowing_shr/overflowing_shlto merge the caller-supplied v7 counter into the UUID tail. Those operations mask shift counts modulo 128, so the edge widths are handled incorrectly.This shows up in two ways:
usable_counter_bits = 0: the counter should contribute no bits, but a non-zerocounteris ORed into the UUID tail.usable_counter_bits in 126..=128: widths above 12 are internally bumped by+2to skip the RFC 4122 variant bits, so the high-end range reaches shift counts128..=130. Those wrap modulo 128, so random bits are not fully cleared and repeated calls with the same full-width counter can still differ.Reproduction
Uuid::new_v7currently does:Because
overflowing_shr/overflowing_shlmask the shift amount modulo 128:usable_counter_bits = 0ends up doingcounter.overflowing_shl(128), which behaves like a shift by 0, so the counter is ORed into the tail instead of ignored.usable_counter_bits = 126..=128becomes an internal width of128..=130after the variant-field adjustment, so the random mask does not become zero and random bits leak into supposedly full-width counters.Suggested fix
Handle the edge cases without relying on overflowing shifts. For example:
Thanks for the report @meng-xu-cs!
It would be good to get this fixed up. I’m on vacation right now but can look at any PRs or take a look when I’m back in a week.
We should try improve our test infrastructure around
new_v7so we can more easily assert the behavior of combining user-specified counters with internally generated random data. A simple#[cfg(test)]thread local boxed function to return a byte slice should do it.