GitHub Advisoryadvisory · crates.io

GHSA-3rjw-m598-pq24 Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set

MEDIUM Published 2026-07-02 Modified 2026-07-02 Official page ↗
Aliases
CVE-2026-50185
Ecosystem
crates.io
Packages
cmov
Severity
Moderate
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:P
Weaknesses
CWE-758
Reviewed
2026-07-02

Affected packages

• crates.io cmov: >= 0.1.1, < 0.5.4 → fixed in 0.5.4

Description

Summary The aarch64 implementations of `Cmov` and `CmovEq` seem to assume that the high bits when loading a value of size smaller than a register into a register are zero-extended. However, this is not the case and these bits are unspecified. This can result in a `left.cmovz(&right, condition)` not moving `right` into `left`, even if `condition == 0`. Details The Rust reference for inline assembly states that: > If a value is of a smaller size than the register it is allocated in then the upper bits of that register will have an undefined value for inputs [..]. Reference If the high bits `[8..]` of the selector loaded into a register in the `Cmov` implementation or the high bits `[16..]` of `self` or `other` for CmovEq (specifically the implementation for `u16` and `i16`) are set, the inline asm compares will produce a different result than the Rust code expects based on the narrow types. In other words, the following assert fails, even though `condition as u8` is zero: ```rust let condition: u32 = black_box(1 rustc --version rustc 1.94.0 (4a4ef493e 2026-03-02) ``` ```rust [cfg(test)] mod tests { use core::hint::black_box; use cmov::{Cmov, CmovEq}; #[test] fn cmovz_wrong_output() { // The black box is necessary here, as otherwise the compiler will // provide a constant 0 to the csel let condition: u32 = black_box(1 << 8); let mut left = 1; let right = 2; // I added this debug_assert as a sanity check, but funnily it causes // the wrong cmov behavior in debug as well (as opposed to only in release mode without the debug_assert) debug_assert_eq!(0, condition as u8); left.cmovz(&right, condition as u8); assert_eq!(left, right); } #[test] fn cmoveq_wrong_output() { let input = 1; let mut output = 0; let left: u32 = black_box(1 << 16); let right: u32 = black_box(1 << 17); // asserting in release mode here would hide the bug, the debug_assert_eq is // a sanity check that these values SHOULD be equal debug_assert_eq!(left as u16, right as u16); (left as u16).cmoveq(&(right as u16), input, &mut output); assert_eq!(input, output); } } ``` Impact Under specific circumstances, this issue can cause `Cmov/CmovEq` to produce incorrect output on `aarch64`. However, whether this bug can actually manifest depends on the surrounding code that calls the relevant impls. In the PoC, a narrowing cast is required, which masks out the set bits from Rust's point of view, but which are then used in the inline assembly. Additional Finding PR #1299 fixed a different bug in the aarch64 backend but introduced a small error. The `csel!` macro expect a `cmp` expression as its first argument which is never used. The compare is always `"cmp {0:w}, 0",` even for `csel64!`, which intends to use `cmp {0:x}, 0`. Given the bug described above, this oversight actually reduces its impact slightly, as only the bits in position`[8..32]` can cause issues, even for those impls that use `csel64!`.

Related

Alias · CVE (1)

  • CVE-2026-50185 RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be… vulnerability

Weakness · CWE (1)

  • CWE-758 Reliance on Undefined, Unspecified, or Implementation-Defined Behavior weakness

References