CVE-2026-50185: Cmov Cmov/CmovEq On Aarch64 Can Produce Vulnerability

MODERATE
Advisory Identifier: GHSA-3rjw-m598-pq24
CWE-758
Published
Advisory Summary

CVE-2026-50185 is a MODERATE cmov/cmoveq on aarch64 can produce vulnerability (CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior) affecting Cmov libraries in the crates.io ecosystem. The issue involves cmov/cmoveq on aarch64 can produce wrong results if high-bits of registers are set. Remediation is available by upgrading affected packages to version 0.5.4.

CVE-2026-50185 at a Glance

CVE Identifier CVE-2026-50185
GHSA Identifier GHSA-3rjw-m598-pq24
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior
Affected Product Cmov
Package Ecosystem crates.io
Published Date
Last Updated

How to Fix CVE-2026-50185

To remediate this vulnerability, upgrade all affected Cmov dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

cmov fixed: 0.5.4
Upgrade cmov to 0.5.4
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for cmov

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-50185, inspect your project dependencies for Cmov:

inspect dependencies for cmov

Analyze the terminal output to locate any version of Cmov in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.5.4), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior, where the product uses an api function, data structure, or other entity in a way that relies on properties that are not always guaranteed to hold for that entity. In Cmov, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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](https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.register-operands.smaller-value) 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 << 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](https://github.com/RustCrypto/utils/blob/dad5e3b9e66d929e86144fe7c8f25371892e35f3/cmov/src/backends/aarch64.rs#L4-L19) and [here for CmovEq](https://github.com/RustCrypto/utils/blob/dad5e3b9e66d929e86144fe7c8f25371892e35f3/cmov/src/backends/aarch64.rs#L60-L72). The following function: ```rust #[unsafe(no_mangle)] pub fn cmovz_wrong_output(left: &mut i32, right: i32, condition: u32) { left.cmovz(&right, condition as u8); } ``` produces the assembly: ```asm 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` ```rust #[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: ```asm 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](https://github.com/RustCrypto/utils/blob/dad5e3b9e66d929e86144fe7c8f25371892e35f3/cmov/src/lib.rs#L119) 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) ``` ```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](https://github.com/RustCrypto/utils/pull/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!`.

CVE-2026-50185 Affected Cmov Versions

1 component(s)
Fixed Versions:
0.5.4
Product / Package Ecosystem Affected Versions Fixed Version
cmov
crates.io
≥ 0.1.1
< 0.5.4
0.5.4

References

Frequently Asked Questions

What is CVE-2026-50185?
CVE-2026-50185 is a security vulnerability in Cmov involving Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set under CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior.
Which Cmov versions are affected?
Affected versions include releases prior to 0.5.4 across affected packages: cmov.
What version fixes CVE-2026-50185?
Upgrading to 0.5.4 (or the latest patched release for your respective branch) resolves the vulnerability.
Is cmov affected?
Yes, cmov is affected in releases prior to 0.5.4. Upgrade your dependency to 0.5.4 or later.
What CWE is associated with CVE-2026-50185?
CVE-2026-50185 is classified under CWE-758: Reliance on Undefined, Unspecified, or Implementation-Defined Behavior (The product uses an API function, data structure, or other entity in a way that relies on properties that are not always guaranteed to hold for that entity.).
How can I check crates.io dependencies for CVE-2026-50185?
Run the dependency tree command: 'inspect dependencies for cmov'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-3rjw-m598-pq24",
  "modified" : "2026-07-02T17:18:11Z",
  "published" : "2026-07-02T17:18:11Z",
  "aliases" : [ "CVE-2026-50185" ],
  "summary" : "Cmov/CmovEq on aarch64 can produce wrong results if high-bits of registers are set ",
  "details" : "### Summary\n\nThe 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`. \n\n### Details\n\nThe Rust reference for inline assembly states that:\n> 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](https://doc.rust-lang.org/reference/inline-assembly.html#r-asm.register-operands.smaller-value)\n\nIf 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.\n\nIn other words, the following assert fails, even though `condition as u8` is zero:\n```rust\nlet condition: u32 = black_box(1 << 8);\nlet mut left = 1;\nlet right = 2;\nleft.cmovz(&right, condition as u8);\nassert_eq!(left, right);\n```\nBecause 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.\n\nThe problematic code is located in `cmov/src/backends/aarch64.rs` [here for Cmov](https://github.com/RustCrypto/utils/blob/dad5e3b9e66d929e86144fe7c8f25371892e35f3/cmov/src/backends/aarch64.rs#L4-L19) and [here for CmovEq](https://github.com/RustCrypto/utils/blob/dad5e3b9e66d929e86144fe7c8f25371892e35f3/cmov/src/backends/aarch64.rs#L60-L72).\n\nThe following function:\n```rust\n#[unsafe(no_mangle)]\npub fn cmovz_wrong_output(left: &mut i32, right: i32, condition: u32) {\n    left.cmovz(&right, condition as u8);\n}\n```\nproduces the assembly:\n```asm\ncmovz_wrong_output:\n\t.cfi_startproc\n\tldr w8, [x0]\n\t//APP\n\tcmp w2, #0\n\tcsel w8, w1, w8, eq\n\t//NO_APP\n\tstr w8, [x0]\n\tret\n```\n\nwhich compares the 32-bits of the condition value against 0, instead of the intended 8.\n\nSimilarly, the following function using `cmoveq`\n```rust\n#[unsafe(no_mangle)]\npub fn cmoveq_wrong_output(left: u32, right: u32, input: u8, output: &mut u8) {\n    (left as u16).cmoveq(&(right as u16), input, output);\n}\n```\ncompiles to:\n```asm\ncmoveq_wrong_output:\n\t.cfi_startproc\n\tldrb w8, [x3]\n\tand w9, w2, #0xff\n\t//APP\n\teor w10, w0, w1\n\tcmp w10, #0\n\tcsel w8, w9, w8, eq\n\t//NO_APP\n\tstrb w8, [x3]\n\tret\n```\nwhere 32 bits of left and right are compared instead of 16. The same happens for `i16`.\n\nFor CmovEq, it seems the `u8/i8` impls are not affected, as they are calling `u16::from` in [the implementation](https://github.com/RustCrypto/utils/blob/dad5e3b9e66d929e86144fe7c8f25371892e35f3/cmov/src/lib.rs#L119) which causes the upper bits to be masked out.\n\n### PoC\n\nThe following two test cases fail on `aarch64-unknown-linux-gnu` when compiled with `--release` (the cmovz one even in debug) emulated with qemu.\n\n```\n> rustc --version\nrustc 1.94.0 (4a4ef493e 2026-03-02)\n```\n\n```rust\n#[cfg(test)]\nmod tests {\n    use core::hint::black_box;\n\n    use cmov::{Cmov, CmovEq};\n\n    #[test]\n    fn cmovz_wrong_output() {\n        // The black box is necessary here, as otherwise the compiler will \n        // provide a constant 0 to the csel\n        let condition: u32 = black_box(1 << 8);\n        let mut left = 1;\n        let right = 2;\n        // I added this debug_assert as a sanity check, but funnily it causes\n        // the wrong cmov behavior in debug as well (as opposed to only in release mode without the debug_assert)\n        debug_assert_eq!(0, condition as u8);\n        left.cmovz(&right, condition as u8);\n        assert_eq!(left, right);\n    }\n\n    #[test]\n    fn cmoveq_wrong_output() {\n        let input = 1;\n        let mut output = 0;\n        let left: u32 = black_box(1 << 16);\n        let right: u32 = black_box(1 << 17);\n        // asserting in release mode here would hide the bug, the debug_assert_eq is\n        // a sanity check that these values SHOULD be equal\n        debug_assert_eq!(left as u16, right as u16);\n        (left as u16).cmoveq(&(right as u16), input, &mut output);\n        assert_eq!(input, output);\n    }\n}\n```\n\n### Impact\n\nUnder 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. \n\n### Additional Finding\n\nPR [#1299](https://github.com/RustCrypto/utils/pull/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!`.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "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"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "crates.io",
      "name" : "cmov"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0.1.1"
      }, {
        "fixed" : "0.5.4"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/RustCrypto/utils/security/advisories/GHSA-3rjw-m598-pq24"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/RustCrypto/utils"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-758" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-02T17:18:11Z",
    "nvd_published_at" : null
  }
}