CVE-2026-23519
RustCrypto cmov: thumbv6m-none-eabi compiler emits non-constant time assembly when using cmovnz
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. Prior to 0.4.4, the thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). This vulnerability is fixed in 0.4.4.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- High
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
8.9
RustCrypto cmov: thumbv6m-none-eabi compiler emits non-constant time assembly when using cmovnz
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. Prior to 0.4.4, the thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). This vulnerability is fixed in 0.4.4.
- Record
CVE-2026-23519- CVSS 4.0
- 8.9 High
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Weakness
- CWE-208
- Updated
- 2026-11-02
EUVDEUVD-2026-2731
8.9
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. Prior to 0.4.4, the thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). This vulnerability is fixed in 0.4.4.
- Record
EUVD-2026-2731- CVSS 4.0
- 8.9 High
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-2gqc-6j2q-83qp
8.9
RustCrypto Utilities cmov: `thumbv6m-none-eabi` compiler emits non-constant time assembly when using `cmovnz`
Summary
thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). I did not found any other target with the same behaviour but I did not go through all targets supported by Rust.
Details
It seems that, during mask computation, an LLVM optimisation pass is detecting that bitnz is returning 0 or 1, that can be interpreted as a boolean. This intermediate value is not masked by a call to black_box and thus the subsequent .wrapping_sub(1) can be interpreted as a conditional bitwise conditional not.
PoC
This is an attempt at having a minimal faulty code. In a library crate with an up-to-date cmov as only dependency, the content of src/lib.rs is:
#![no_std]
use cmov::Cmov;
#[inline(never)]
pub fn test_ct_cmov(a: &mut u8, b: u8, c: u8) {
a.cmovnz(&b, c);
}
The resulting assembly emitted (shown using cargo asm --release --target thumbv6m-none-eabi that uses cargo-show-asm):
<details> <summary>Collapsed assembly</summary>
.section .text.not_ct::test_ct_cmov,"ax",%progbits
.globl not_ct::test_ct_cmov
.p2align 1
.type not_ct::test_ct_cmov,%function
.code 16
.thumb_func
not_ct::test_ct_cmov:
.fnstart
.cfi_sections .debug_frame
.cfi_startproc
.save {r7, lr}
push {r7, lr}
.cfi_def_cfa_offset 8
.cfi_offset lr, -4
.cfi_offset r7, -8
.setfp r7, sp
add r7, sp, #0
.cfi_def_cfa_register r7
.pad #8
sub sp, #8
movs r3, #0
lsls r2, r2, #24
bne .LBB0_2
mvns r3, r3
.LBB0_2:
ldrb r2, [r0]
str r3, [sp, #4]
str r3, [sp]
mov r3, sp
@APP
@NO_APP
ldr r3, [sp]
bics r1, r3
ands r2, r3
adds r1, r2, r1
strb r1, [r0]
add sp, #8
pop {r7, pc}
</details>
The non-constant time assembly is:
bne .LBB0_2
mvns r3, r3
.LBB0_2:
Impact
The exact impact is unclear, especially since cmov clearly warns users that the portable version is best-effort.
- Record
GHSA-2gqc-6j2q-83qp- CVSS 4.0
- 8.9 High
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Weakness
- CWE-203, CWE-208
- Updated
- 2026-11-02
NVD
8.9
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. Prior to 0.4.4, the thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). This vulnerability is fixed in 0.4.4.
- Record
CVE-2026-23519- CVSS 4.0
- 8.9 High
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/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
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-208, CWE-203
- Updated
- 2026-11-02
OSV
8.9
RustCrypto cmov: thumbv6m-none-eabi compiler emits non-constant time assembly when using cmovnz
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. Prior to 0.4.4, the thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). This vulnerability is fixed in 0.4.4.
- Record
CVE-2026-23519- CVSS 4.0
- 8.9 High
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Weakness
- CWE-208
- Updated
- 2026-11-02
OSVGHSA-2gqc-6j2q-83qp
8.9
RustCrypto Utilities cmov: `thumbv6m-none-eabi` compiler emits non-constant time assembly when using `cmovnz`
Summary
thumbv6m-none-eabi (Cortex M0, M0+ and M1) compiler emits non-constant time assembly when using cmovnz (portable version). I did not found any other target with the same behaviour but I did not go through all targets supported by Rust.
Details
It seems that, during mask computation, an LLVM optimisation pass is detecting that bitnz is returning 0 or 1, that can be interpreted as a boolean. This intermediate value is not masked by a call to black_box and thus the subsequent .wrapping_sub(1) can be interpreted as a conditional bitwise conditional not.
PoC
This is an attempt at having a minimal faulty code. In a library crate with an up-to-date cmov as only dependency, the content of src/lib.rs is:
#![no_std]
use cmov::Cmov;
#[inline(never)]
pub fn test_ct_cmov(a: &mut u8, b: u8, c: u8) {
a.cmovnz(&b, c);
}
The resulting assembly emitted (shown using cargo asm --release --target thumbv6m-none-eabi that uses cargo-show-asm):
<details> <summary>Collapsed assembly</summary>
.section .text.not_ct::test_ct_cmov,"ax",%progbits
.globl not_ct::test_ct_cmov
.p2align 1
.type not_ct::test_ct_cmov,%function
.code 16
.thumb_func
not_ct::test_ct_cmov:
.fnstart
.cfi_sections .debug_frame
.cfi_startproc
.save {r7, lr}
push {r7, lr}
.cfi_def_cfa_offset 8
.cfi_offset lr, -4
.cfi_offset r7, -8
.setfp r7, sp
add r7, sp, #0
.cfi_def_cfa_register r7
.pad #8
sub sp, #8
movs r3, #0
lsls r2, r2, #24
bne .LBB0_2
mvns r3, r3
.LBB0_2:
ldrb r2, [r0]
str r3, [sp, #4]
str r3, [sp]
mov r3, sp
@APP
@NO_APP
ldr r3, [sp]
bics r1, r3
ands r2, r3
adds r1, r2, r1
strb r1, [r0]
add sp, #8
pop {r7, pc}
</details>
The non-constant time assembly is:
bne .LBB0_2
mvns r3, r3
.LBB0_2:
Impact
The exact impact is unclear, especially since cmov clearly warns users that the portable version is best-effort.
- Record
GHSA-2gqc-6j2q-83qp- CVSS 4.0
- 8.9 High
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Weakness
- CWE-203, CWE-208
- Updated
- 2026-11-02
OSVRUSTSEC-2026-0003
8.9
Non-constant-time code generation on ARM32 targets
Summary
While the cmov crate has a special backend for aarch64 which uses special
CSEL instructions, on 32-bit ARM it uses a portable pure Rust fallback
implementation. This implementation uses a combination of bitwise arithmetic
and core::hint::black_box to attempt to coerce constant-time code generation
out of the optimizer, but the implementation in v0.4.3 and earlier failed to
do this on 32-bit ARM targets.
Impact
Branch instructions inserted by the LLVM optimizer on 32-bit targets can be
leveraged using various microarchitectural sidechannels like cache timing
attacks to learn secret information that cmov is designed to protect.
Details
The following assembly was emitted when using Cmov::cmovnz, a function which
implements a conditional move when a provided value is non-zero:
bne .LBB0_2
mvns r3, r3
This includes a branch instruction bne: Branch if Not Equal.
PoC
The following code reproduces the issue:
#![no_std]
use cmov::Cmov;
#[inline(never)]
pub fn test_ct_cmov(a: &mut u8, b: u8, c: u8) {
a.cmovnz(&b, c);
}
Resolution
cmov v0.4.4 includes a portable black_box-based tactical mitigation for the
issue which coerced the compiler into producing the expected codegen, and
additionally v0.4.5 added an asm! reimplementation of the problematic mask
generation function for ARM32 targets which should guarantee that particular
function never contains a branch on such targets.
- Record
RUSTSEC-2026-0003- CVSS 4.0
- 8.9 High
CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N - Updated
- 2026-11-02
Affected products
Affected packages
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-23519 is a high vulnerability (CVSS 8.9) in Rustcrypto Cmov, published January 14, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.56%, the 46th percentile. Fixed in cmov 0.4.4.
References
github.com/RustCrypto/utils/security/advisories/GHSA-2gqc-6j2q-83qp
github.com/RustCrypto/utils/commit/55977257e7c82a309d5e8abfdd380a774f0f9778
rustsec.org/advisories/RUSTSEC-2026-0003.html
nvd.nist.gov/vuln/detail/CVE-2026-23519
github.com/advisories/GHSA-2gqc-6j2q-83qp
github.com/CVEProject/cvelistV5/tree/main/cves/2026/23xxx/CVE-2026-23519.json
github.com/RustCrypto/utils
crates.io/crates/cmov