Every CVE Search/ Get API key

CVE-2026-50185

RustCrypto Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set

2.0 LowFix available

RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be rewritten as branches by the compiler. From 0.1.1 until 0.5.4, the aarch64 implementations of Cmov and CmovEq in cmov/src/backends/aarch64.rs assume high bits are zero-extended when loading values smaller than a register, so set high bits such as [8..] in a Cmov selector or [16..] of self or other in the u16 and i16 CmovEq implementations can cause left.cmovz(&right, condition) to produce incorrect output. This issue is fixed in version 0.5.4.

Attack profile

How easy it is to exploit

Attack vector
Local
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
None
Integrity
Low
Availability
None

Exploit likelihood

0.16%Unchanged since Jul 18, 2026
90%50%10%1%

What each source reports

CVE List Score used 2.0

RustCrypto Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set

RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be rewritten as branches by the compiler. From 0.1.1 until 0.5.4, the aarch64 implementations of Cmov and CmovEq in cmov/src/backends/aarch64.rs assume high bits are zero-extended when loading values smaller than a register, so set high bits such as [8..] in a Cmov selector or [16..] of self or other in the u16 and i16 CmovEq implementations can cause left.cmovz(&right, condition) to produce incorrect output. This issue is fixed in version 0.5.4.

Record
CVE-2026-50185
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-758
Updated
2026-11-02
EUVDEUVD-2026-45254 2.0

RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be rewritten as branches by the compiler. From 0.1.1 until 0.5.4, the aarch64 implementations of Cmov and CmovEq in cmov/src/backends/aarch64.rs assume high bits are zero-extended when loading values smaller than a register, so set high bits such as [8..] in a Cmov selector or [16..] of self or other in the u16 and i16 CmovEq implementations can cause left.cmovz(&right, condition) to produce incorrect output. This issue is fixed in version 0.5.4.

Record
EUVD-2026-45254
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-3rjw-m598-pq24 5.5

Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set

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:

let condition: u32 = black_box(1 << 8);
let mut left = 1;
let right = 2;
left.cmovz(&right, condition as u8);
assert_eq!(left, right);

Because the ninth bit is set in the original variable, this bit is also set when the truncated condition is loaded into the input register for the cmp, causing the csel to select the wrong value.

The problematic code is located in cmov/src/backends/aarch64.rs here for Cmov and here for CmovEq.

The following function:

#[unsafe(no_mangle)]
pub fn cmovz_wrong_output(left: &mut i32, right: i32, condition: u32) {
    left.cmovz(&right, condition as u8);
}

produces the assembly:

cmovz_wrong_output:
	.cfi_startproc
	ldr w8, [x0]
	//APP
	cmp w2, #0
	csel w8, w1, w8, eq
	//NO_APP
	str w8, [x0]
	ret

which compares the 32-bits of the condition value against 0, instead of the intended 8.

Similarly, the following function using cmoveq

#[unsafe(no_mangle)]
pub fn cmoveq_wrong_output(left: u32, right: u32, input: u8, output: &mut u8) {
    (left as u16).cmoveq(&(right as u16), input, output);
}

compiles to:

cmoveq_wrong_output:
	.cfi_startproc
	ldrb w8, [x3]
	and w9, w2, #0xff
	//APP
	eor w10, w0, w1
	cmp w10, #0
	csel w8, w9, w8, eq
	//NO_APP
	strb w8, [x3]
	ret

where 32 bits of left and right are compared instead of 16. The same happens for i16.

For CmovEq, it seems the u8/i8 impls are not affected, as they are calling u16::from in the implementation which causes the upper bits to be masked out.

PoC

The following two test cases fail on aarch64-unknown-linux-gnu when compiled with --release (the cmovz one even in debug) emulated with qemu.

> rustc --version
rustc 1.94.0 (4a4ef493e 2026-03-02)
#[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);
 …
Record
GHSA-3rjw-m598-pq24
CVSS 4.0
5.5 MediumCVSS: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
Weakness
CWE-758
Updated
2026-11-02
NVD 2.0

RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be rewritten as branches by the compiler. From 0.1.1 until 0.5.4, the aarch64 implementations of Cmov and CmovEq in cmov/src/backends/aarch64.rs assume high bits are zero-extended when loading values smaller than a register, so set high bits such as [8..] in a Cmov selector or [16..] of self or other in the u16 and i16 CmovEq implementations can cause left.cmovz(&right, condition) to produce incorrect output. This issue is fixed in version 0.5.4.

Record
CVE-2026-50185
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
3.3 LowCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Weakness
CWE-758
Updated
2026-11-02
OSV 2.0

RustCrypto Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set

RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be rewritten as branches by the compiler. From 0.1.1 until 0.5.4, the aarch64 implementations of Cmov and CmovEq in cmov/src/backends/aarch64.rs assume high bits are zero-extended when loading values smaller than a register, so set high bits such as [8..] in a Cmov selector or [16..] of self or other in the u16 and i16 CmovEq implementations can cause left.cmovz(&right, condition) to produce incorrect output. This issue is fixed in version 0.5.4.

Record
CVE-2026-50185
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-758
Updated
2026-11-02
OSVDEBIAN-CVE-2026-50185 3.3

RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be rewritten as branches by the compiler. From 0.1.1 until 0.5.4, the aarch64 implementations of Cmov and CmovEq in cmov/src/backends/aarch64.rs assume high bits are zero-extended when loading values smaller than a register, so set high bits such as [8..] in a Cmov selector or [16..] of self or other in the u16 and i16 CmovEq implementations can cause left.cmovz(&right, condition) to produce incorrect output. This issue is fixed in version 0.5.4.

Record
DEBIAN-CVE-2026-50185
CVSS 3.1
3.3 LowCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Updated
2026-11-02
OSVGHSA-3rjw-m598-pq24 5.5

Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set

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:

let condition: u32 = black_box(1 << 8);
let mut left = 1;
let right = 2;
left.cmovz(&right, condition as u8);
assert_eq!(left, right);

Because the ninth bit is set in the original variable, this bit is also set when the truncated condition is loaded into the input register for the cmp, causing the csel to select the wrong value.

The problematic code is located in cmov/src/backends/aarch64.rs here for Cmov and here for CmovEq.

The following function:

#[unsafe(no_mangle)]
pub fn cmovz_wrong_output(left: &mut i32, right: i32, condition: u32) {
    left.cmovz(&right, condition as u8);
}

produces the assembly:

cmovz_wrong_output:
	.cfi_startproc
	ldr w8, [x0]
	//APP
	cmp w2, #0
	csel w8, w1, w8, eq
	//NO_APP
	str w8, [x0]
	ret

which compares the 32-bits of the condition value against 0, instead of the intended 8.

Similarly, the following function using cmoveq

#[unsafe(no_mangle)]
pub fn cmoveq_wrong_output(left: u32, right: u32, input: u8, output: &mut u8) {
    (left as u16).cmoveq(&(right as u16), input, output);
}

compiles to:

cmoveq_wrong_output:
	.cfi_startproc
	ldrb w8, [x3]
	and w9, w2, #0xff
	//APP
	eor w10, w0, w1
	cmp w10, #0
	csel w8, w9, w8, eq
	//NO_APP
	strb w8, [x3]
	ret

where 32 bits of left and right are compared instead of 16. The same happens for i16.

For CmovEq, it seems the u8/i8 impls are not affected, as they are calling u16::from in the implementation which causes the upper bits to be masked out.

PoC

The following two test cases fail on aarch64-unknown-linux-gnu when compiled with --release (the cmovz one even in debug) emulated with qemu.

> rustc --version
rustc 1.94.0 (4a4ef493e 2026-03-02)
#[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);
 …
Record
GHSA-3rjw-m598-pq24
CVSS 4.0
5.5 MediumCVSS: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
Weakness
CWE-758
Updated
2026-11-02
OSVRHSA-2026:42923 5.3

Red Hat Security Advisory: Red Hat Hardened Images RPMs Security Update

Record
RHSA-2026:42923
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Updated
2026-11-02
OSVUBUNTU-CVE-2026-50185 2.0

RustCrypto CMOV provides conditional move CPU intrinsics which are guaranteed on major platforms to execute in constant-time and not be rewritten as branches by the compiler. From 0.1.1 until 0.5.4, the aarch64 implementations of Cmov and CmovEq in cmov/src/backends/aarch64.rs assume high bits are zero-extended when loading values smaller than a register, so set high bits such as [8..] in a Cmov selector or [16..] of self or other in the u16 and i16 CmovEq implementations can cause left.cmovz(&right, condition) to produce incorrect output. This issue is fixed in version 0.5.4.

Record
UBUNTU-CVE-2026-50185
CVSS 4.0
2.0 LowCVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N
CVSS 3.1
3.3 LowCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
cmov
crates.io
0.1.1 up to 0.5.4
0.5.4
rust-cmov
Debian:14
before 0.5.4-1, plus listed versions
0.5.3-1
0.5.4-1
cargo
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
clippy
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-analyzer
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-debugger-common
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-doc
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-gdb
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-lldb
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
Show 12 moreShow less
Affected packages
PackageAffectedFixed
rust-src
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-aarch64-unknown-none-softfloat
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-aarch64-unknown-uefi
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-i686-pc-windows-gnu
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-wasm32-unknown-unknown
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-wasm32-wasip1
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-x86_64-pc-windows-gnu
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-x86_64-unknown-none
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-std-static-x86_64-unknown-uefi
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rustfmt
Red Hat:hummingbird:1
From an advisory covering 2 CVEs
before 0:1.97.1-1.1.hum1
0:1.97.1-1.1.hum1
rust-cmov
Ubuntu:26.10
All versions, plus listed versions
0.5.3-10.5.4-1
None known

History

History
RecordedChangedSources
affected, facts
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-50185 is a low vulnerability (CVSS 2.0) in Rustcrypto Cmov, published July 2, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.16%, the 4th percentile. Fixed in cmov 0.5.4.

References

Show 17 moreShow less
This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open