You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This finding was identified during an agentic unsafe Rust code review performed by Gemini AI, followed by human review and verification.
The Issue
In subtle v2, Choice::from(u8) is a safe public constructor. The crate documentation explicitly states that internal debug_assert! invariant checks are stripped in release profile to avoid introducing data-dependent branches. Consequently, in release builds (or when debug assertions are disabled), safe downstream code can construct a Choice holding an arbitrary u8 value such as 2u8, violating the internal invariant self.0 \in {0, 1}.
When such an invalid Choice is passed into ConditionallySelectable::conditional_select for core::cmp::Ordering:
// SAFETY: `Ordering` is `#[repr(i8)]` and `ret` has been assigned to
// a value which was originally a valid `Ordering` then cast to `i8`
unsafe{*((&ret as*const_)as*const cmp::Ordering)}
}
}
Integer bitwise selection arithmetic computes an out-of-bounds i8 value (ret = -2i8). Dereferencing a raw pointer to -2 as *const cmp::Ordering constructs an invalid enum discriminant, triggering immediate Undefined Behavior under the Rust Reference.
Minimal Reproduction (Miri)
use std::cmp::Ordering;use subtle::{Choice,ConditionallySelectable};fnmain(){let invalid_choice = Choice::from(2u8);let _ub = Ordering::conditional_select(&Ordering::Equal,&Ordering::Less, invalid_choice);}
error: Undefined Behavior: constructing invalid value of type std::cmp::Ordering: at .<enum-tag>, encountered 0xfe, but expected a valid enum tag
--> /google/src/cloud/manishearth/verify-unsafe-rust-bugs/google3/third_party/rust/subtle/v2/src/lib.rs:564:18
|
564 | unsafe { *((&ret as *const _) as *const cmp::Ordering) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
= note: stack backtrace:
0: <std::cmp::Ordering as subtle::ConditionallySelectable>::conditional_select
at /google/src/cloud/manishearth/verify-unsafe-rust-bugs/google3/third_party/rust/subtle/v2/src/lib.rs:564:18: 564:63
1: main
at src/bin/repro1.rs:6:15: 6:94
Suggested Fix
Choice::from must structurally enforce its internal invariant (self.0 \in {0, 1}) across safe code boundaries without branching, for example by masking the input bitwise:
Alternatively, cmp::Ordering::conditional_select should avoid raw pointer casts and safely map the resulting integer back to Ordering.
Note
The full audit report below also contains additional minor findings (such as missing safety comments or undocumented FFI assumptions) that are probably worth fixing as well but not the primary goal of this issue.
Full Gemini Unsafe Code Audit Report
Unsafe Rust Review: subtle (v2)
Overall Safety Assessment
subtle is a low-level cryptographic utility library providing pure-Rust traits and wrapper types (Choice, CtOption, ConstantTimeEq, ConditionallySelectable, etc.) designed to execute in constant time to prevent timing side-channel attacks.
The crate has a very low density of unsafe code. Across the core library (src/lib.rs), there are only two unsafe blocks:
src/lib.rs:225 (black_box): Uses core::ptr::read_volatile(&input) as
an optimization barrier to prevent the compiler from recognizing bitwise masking patterns and optimizing them back into conditional branches.
src/lib.rs:564 (cmp::Ordering::conditional_select): Dereferences a raw
pointer cast *const cmp::Ordering from an i8 stack variable ret.
While the architecture is minimalist and primarily relies on safe bitwise operations, there is a critical soundness vulnerability at the boundary between Choice::from(u8) and cmp::Ordering::conditional_select. The crate intentionally strips internal debug_assert! invariant checks in release profile to avoid introducing data-dependent branches. Consequently, Choice::from(u8) allows safe downstream code to construct a Choice holding an arbitrary u8 value (violating the internal invariant self.0 \in {0, 1}). When such an invalid Choice is passed into cmp::Ordering::conditional_select, integer arithmetic produces an out-of-bounds discriminant (e.g., -2i8). Dereferencing a raw pointer to this value in the unsafe block constructs an invalid enum discriminant, triggering immediate Undefined Behavior.
Critical Findings
Soundness Vulnerability: Invalid Enum Discriminant Construction in cmp::Ordering::conditional_select 🔴 🚨
Location: src/lib.rs:556-566 (impl ConditionallySelectable for cmp::Ordering), specifically line 564.
Description: The trait method ConditionallySelectable::conditional_select is a safe function. For cmp::Ordering, it converts references a and b to i8, performs i8::conditional_select(&a, &b, choice), and then transmutes the resulting i8 (ret) back to cmp::Ordering via pointer dereference:
unsafe{*((&ret as*const_)as*const cmp::Ordering)}
The validity of dereferencing *const cmp::Ordering requires that ret holds a valid discriminant bit pattern for core::cmp::Ordering (-1, 0, or 1). However, Choice::from(input: u8) (src/lib.rs:236-245) is a safe public constructor. The crate documentation explicitly states that debug_assert! checks are stripped in release mode to avoid branching. In release mode, Choice::from(2u8) safely constructs a Choice with internal representation 2u8. When choice = Choice::from(2u8) is passed to Ordering::conditional_select(&Ordering::Equal, &Ordering::Less, choice):
- Inside `i8::conditional_select`: `mask = -(2i8) = -2i8` (`0xFE`).
- `a = 0` (`Equal`), `b = -1` (`Less`).
- `ret = a ^ (mask & (a ^ b)) = 0 ^ (-2 & -1) = -2` (`0xFE`).
- The `unsafe` block dereferences a raw pointer to `-2` as `*const
cmp::Ordering`. According to the Rust Reference, producing an integer discriminant that violates the valid range of discriminants for an enum type is immediate Undefined Behavior.
Proof of Concept: In safe Rust (compiled in release profile or with
debug assertions disabled):
Remediation: Choice must structurally enforce its internal invariant
(self.0 \in {0, 1}) across safe code boundaries without branching, e.g., by masking the input:
Alternatively, cmp::Ordering::conditional_select should avoid raw pointer casts and instead safely convert the resulting integer back to Ordering.
Fishy Findings
1. Invalid Claim in Safety Comment for cmp::Ordering::conditional_select 🟡 🤦
Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Incorrect Safety Documentation
Location: src/lib.rs:562-564
Description: The safety comment states: ret has been assigned to a value which was originally a valid Ordering then cast to i8. This claim is factually incorrect. ret is the computed output of i8::conditional_select(&a, &b, choice), which performs bitwise XOR, AND, and negation operations. While a and b were originally valid Ordering values cast to i8, ret itself is a newly computed bit pattern. The safety proof relies on unstated lemmas: (1) that choice.0 \in {0, 1}, and (2) that integer conditional selection over the domain {-1, 0, 1} is closed under valid Ordering discriminants. As demonstrated in the Critical Findings, lemma (1) does not hold for safe public callers in release profile.
2. Stability of read_volatile as an Optimization Barrier 🟡 🤸
Severity: 🟡 Low
Threat Vector: 🤸 Deliberate Contortion
Bug Type: Fragile Optimization Barrier
Location: src/lib.rs:222-234 (black_box)
Description: The fallback implementation of black_box uses core::ptr::read_volatile(&input) on a local stack variable to prevent compiler optimizations. While this is memory safe and sound, the authors correctly note in comments that relying on volatile reads of non-escaping stack variables as a cryptographic optimization barrier is fragile against aggressive compiler memory models. (In modern Rust versions with the core_hint_black_box feature, core::hint::black_box is used instead).
Missing Safety Comments
The core crate (src/lib.rs) contains safety comments on all unsafe blocks. However, three unsafe blocks in the fuzzer test suite lack // SAFETY: comments:
1. fuzz/fuzzers/conditional_assign_i8.rs:8 🟡 🤦
Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Missing Safety Comment
Proposed Proof Comment:
// SAFETY: `transmute::<u8, i8>` transmutes between primitive integer types// of identical size (1 byte) and alignment (1 byte), where all bit patterns// are valid initialized values.
2. fuzz/fuzzers/conditional_assign_u16.rs:14 🟡 🤦
Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Missing Safety Comment
Proposed Proof Comment:
// SAFETY: `transmute::<[u8; 2], u16>` transmutes a 2-byte array into a `u16`.// Both types have identical size (2 bytes) and all bit patterns are valid for `u16`.// Alignment requirements are satisfied because `transmute` takes the array by value.
3. fuzz/fuzzers/conditional_assign_i128.rs:14 🟡 🤦
Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Missing Safety Comment
Proposed Proof Comment:
// SAFETY: `transmute::<[u8; 16], i128>` transmutes a 16-byte array into an `i128`.// Both types have identical size (16 bytes) and all bit patterns are valid for `i128`.// Alignment requirements are satisfied because `transmute` takes the array by value.
Note
This finding was identified during an agentic unsafe Rust code review performed by Gemini AI, followed by human review and verification.
The Issue
In
subtlev2,Choice::from(u8)is a safe public constructor. The crate documentation explicitly states that internaldebug_assert!invariant checks are stripped in release profile to avoid introducing data-dependent branches. Consequently, in release builds (or when debug assertions are disabled), safe downstream code can construct aChoiceholding an arbitraryu8value such as2u8, violating the internal invariantself.0 \in {0, 1}.When such an invalid
Choiceis passed intoConditionallySelectable::conditional_selectforcore::cmp::Ordering:subtle/src/lib.rs
Lines 556 to 566 in 5457b54
Integer bitwise selection arithmetic computes an out-of-bounds
i8value (ret = -2i8). Dereferencing a raw pointer to-2as*const cmp::Orderingconstructs an invalid enum discriminant, triggering immediate Undefined Behavior under the Rust Reference.Minimal Reproduction (Miri)
Suggested Fix
Choice::frommust structurally enforce its internal invariant (self.0 \in {0, 1}) across safe code boundaries without branching, for example by masking the input bitwise:impl From<u8> for Choice { #[inline] fn from(input: u8) -> Choice { debug_assert!((input == 0u8) | (input == 1u8)); - Choice(input) + Choice(black_box(input & 1)) } }Alternatively,
cmp::Ordering::conditional_selectshould avoid raw pointer casts and safely map the resulting integer back toOrdering.Note
The full audit report below also contains additional minor findings (such as missing safety comments or undocumented FFI assumptions) that are probably worth fixing as well but not the primary goal of this issue.
Full Gemini Unsafe Code Audit Report
Unsafe Rust Review:
subtle(v2)Overall Safety Assessment
subtleis a low-level cryptographic utility library providing pure-Rust traits and wrapper types (Choice,CtOption,ConstantTimeEq,ConditionallySelectable, etc.) designed to execute in constant time to prevent timing side-channel attacks.The crate has a very low density of
unsafecode. Across the core library (src/lib.rs), there are only twounsafeblocks:src/lib.rs:225(black_box): Usescore::ptr::read_volatile(&input)asan optimization barrier to prevent the compiler from recognizing bitwise masking patterns and optimizing them back into conditional branches.
src/lib.rs:564(cmp::Ordering::conditional_select): Dereferences a rawpointer cast
*const cmp::Orderingfrom ani8stack variableret.While the architecture is minimalist and primarily relies on safe bitwise operations, there is a critical soundness vulnerability at the boundary between
Choice::from(u8)andcmp::Ordering::conditional_select. The crate intentionally strips internaldebug_assert!invariant checks in release profile to avoid introducing data-dependent branches. Consequently,Choice::from(u8)allows safe downstream code to construct aChoiceholding an arbitraryu8value (violating the internal invariantself.0 \in {0, 1}). When such an invalidChoiceis passed intocmp::Ordering::conditional_select, integer arithmetic produces an out-of-bounds discriminant (e.g.,-2i8). Dereferencing a raw pointer to this value in theunsafeblock constructs an invalid enum discriminant, triggering immediate Undefined Behavior.Critical Findings
Soundness Vulnerability: Invalid Enum Discriminant Construction in
cmp::Ordering::conditional_select🔴 🚨Severity: 🔴 High
Threat Vector: 🚨 Untrusted Input
Bug Type: Validity Invariant Violation, Invalid Enum Discriminant
Location:
src/lib.rs:556-566(impl ConditionallySelectable for cmp::Ordering), specifically line 564.Description: The trait method
ConditionallySelectable::conditional_selectis a safe function. Forcmp::Ordering, it converts referencesaandbtoi8, performsi8::conditional_select(&a, &b, choice), and then transmutes the resultingi8(ret) back tocmp::Orderingvia pointer dereference:The validity of dereferencing
*const cmp::Orderingrequires thatretholds a valid discriminant bit pattern forcore::cmp::Ordering(-1,0, or1). However,Choice::from(input: u8)(src/lib.rs:236-245) is a safe public constructor. The crate documentation explicitly states thatdebug_assert!checks are stripped in release mode to avoid branching. In release mode,Choice::from(2u8)safely constructs aChoicewith internal representation2u8. Whenchoice = Choice::from(2u8)is passed toOrdering::conditional_select(&Ordering::Equal, &Ordering::Less, choice):cmp::Ordering`. According to the Rust Reference, producing an integer discriminant that violates the valid range of discriminants for an enum type is immediate Undefined Behavior.
Proof of Concept: In safe Rust (compiled in release profile or with
debug assertions disabled):
Remediation:
Choicemust structurally enforce its internal invariant(
self.0 \in {0, 1}) across safe code boundaries without branching, e.g., by masking the input:Alternatively,
cmp::Ordering::conditional_selectshould avoid raw pointer casts and instead safely convert the resulting integer back toOrdering.Fishy Findings
1. Invalid Claim in Safety Comment for
cmp::Ordering::conditional_select🟡 🤦src/lib.rs:562-564ret has been assigned to a value which was originally a valid Ordering then cast to i8. This claim is factually incorrect.retis the computed output ofi8::conditional_select(&a, &b, choice), which performs bitwise XOR, AND, and negation operations. Whileaandbwere originally validOrderingvalues cast toi8,retitself is a newly computed bit pattern. The safety proof relies on unstated lemmas: (1) thatchoice.0 \in {0, 1}, and (2) that integer conditional selection over the domain{-1, 0, 1}is closed under validOrderingdiscriminants. As demonstrated in the Critical Findings, lemma (1) does not hold for safe public callers in release profile.2. Stability of
read_volatileas an Optimization Barrier 🟡 🤸src/lib.rs:222-234(black_box)black_boxusescore::ptr::read_volatile(&input)on a local stack variable to prevent compiler optimizations. While this is memory safe and sound, the authors correctly note in comments that relying on volatile reads of non-escaping stack variables as a cryptographic optimization barrier is fragile against aggressive compiler memory models. (In modern Rust versions with thecore_hint_black_boxfeature,core::hint::black_boxis used instead).Missing Safety Comments
The core crate (
src/lib.rs) contains safety comments on allunsafeblocks. However, threeunsafeblocks in the fuzzer test suite lack// SAFETY:comments:1.
fuzz/fuzzers/conditional_assign_i8.rs:8🟡 🤦Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Missing Safety Comment
Proposed Proof Comment:
2.
fuzz/fuzzers/conditional_assign_u16.rs:14🟡 🤦Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Missing Safety Comment
Proposed Proof Comment:
3.
fuzz/fuzzers/conditional_assign_i128.rs:14🟡 🤦Severity: 🟡 Low
Threat Vector: 🤦 Accidental Misuse
Bug Type: Missing Safety Comment
Proposed Proof Comment: