GHSA-2gqc-6j2q-83qp
RustCrypto Utilities cmov: `thumbv6m-none-eabi` compiler emits non-constant time assembly when using `cmovnz`
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Blast Radius
cmovReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects crates.io packages — download data is not available via public APIs for these ecosystems.
Description
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):
.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.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🦀crates.io | cmov | all versions | 0.4.4 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for cmov. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update cmov to 0.4.4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-2gqc-6j2q-83qp is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-2gqc-6j2q-83qp is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-2gqc-6j2q-83qp. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-2gqc-6j2q-83qp in your dependencies?
O3 detects GHSA-2gqc-6j2q-83qp across crates.io dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.