Versions inspected
cpufeatures 0.2.17
- crates.io SHA-256:
59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280
- upstream commit recorded by the package:
9d92d5e95ab4c07c5d8bfd024bf2a17e96d20feb
cpufeatures 0.3.0
- crates.io SHA-256:
8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201
- upstream commit recorded by the package:
31e6ec3727f11f9f5e0c106e282014653a4d7bb7
While reviewing the x86 feature-detection implementation, I found two independent questions. I am reporting them together for context, but I would be happy to split them into separate issues if that is easier to review.
1. CPUID leaf 7 is queried without checking the maximum basic leaf
Summary
Both inspected versions query structured feature leaf 7 directly, without first checking the maximum basic CPUID leaf reported by CPUID(0).EAX.
This appears not to fail closed on processors or virtual CPUs whose maximum basic leaf is below 7.
Updating from 0.2.17 to 0.3.0 does not currently address this: 0.3.0 retains the direct leaf-7 query and additionally queries subleaf 7.1.
Source evidence
In 0.2.17, cpufeatures/src/x86.rs:47-58 constructs:
[cpuid(1), cpuid_count(7, 0)]
Source:
|
unsafe fn cpuid(leaf: u32) -> CpuidResult { |
|
__cpuid(leaf) |
|
} |
|
|
|
#[inline(never)] |
|
unsafe fn cpuid_count(leaf: u32, sub_leaf: u32) -> CpuidResult { |
|
__cpuid_count(leaf, sub_leaf) |
|
} |
|
|
|
let cr = unsafe { |
|
[cpuid(1), cpuid_count(7, 0)] |
|
}; |
In 0.3.0, cpufeatures/src/x86.rs:41-51 constructs:
[cpuid(1), cpuid_count(7, 0), cpuid_count(7, 1)]
Source:
|
unsafe fn cpuid(leaf: u32) -> CpuidResult { |
|
__cpuid(leaf) |
|
} |
|
|
|
unsafe fn cpuid_count(leaf: u32, sub_leaf: u32) -> CpuidResult { |
|
__cpuid_count(leaf, sub_leaf) |
|
} |
|
|
|
let cr = unsafe { |
|
[cpuid(1), cpuid_count(7, 0), cpuid_count(7, 1)] |
|
}; |
A package-wide search of the Rust sources in both releases found no call to __get_cpuid_max, no cpuid(0), and no equivalent maximum-basic-leaf guard.
The Rust core::arch::__cpuid_count documentation states that when the requested leaf is above the maximum supported basic or extended leaf, the result is the information for the highest supported basic information leaf, using the supplied subleaf:
https://doc.rust-lang.org/core/arch/x86_64/fn.__cpuid_count.html
Therefore an unsupported query for leaf 7 is not guaranteed to produce zero feature bits. Bits belonging to the highest supported basic leaf could instead be interpreted using the leaf-7 layout.
Observed behavior
The detector always obtains leaf 7 before evaluating the requested feature predicates. If the reported maximum basic leaf is less than 7, the returned registers may describe a different leaf, but the check! rules interpret them as leaf-7 feature flags.
Expected behavior
Unsupported structured-feature leaves and subleaves should be represented as zero / unavailable before their bits are interpreted.
Conceptually:
max_basic_leaf = CPUID(0).EAX
if max_basic_leaf >= 7:
leaf7_0 = CPUID(7, 0)
else:
leaf7_0 = zero / features unavailable
if max_basic_leaf >= 7 and leaf7_0 reports subleaf 1 as supported:
leaf7_1 = CPUID(7, 1)
else:
leaf7_1 = zero / features unavailable
For implementations where CPUID.7.0:EAX reports the maximum input subleaf, the second guard would require that value to be at least 1.
Potential impact
The generic failure mode is a false-positive CPU-feature result. A caller may then dispatch to an instruction that the CPU does not support. The expected failure would be an invalid-instruction exception or equivalent process failure.
I am not claiming a memory-safety vulnerability or assigning a security severity.
sha2 context
As one concrete consumer, sha2 0.10.9 does not dispatch its x86 SHA-256 backend based only on the "sha" bit. It requests the conjunction:
cpufeatures::new!(shani_cpuid, "sha", "sse2", "ssse3", "sse4.1");
Its SHA-512 x86 backend separately requests "avx2".
Therefore practical reachability for that consumer depends on the complete conjunction of required bits, not merely one misinterpreted leaf-7 bit. This conditions or reduces the practical false-positive surface for this specific consumer, but does not correct the crate-wide fail-open query policy.
2. AVX uses the XMM-only XCR0 category
This is independent of the maximum-basic-leaf issue above.
Source evidence
Both 0.2.17 and 0.3.0 define:
("avx", "xmm", 0, ecx, 28)
The "xmm" category calls __xgetbv! with mask 0b10, while the existing "ymm" category uses 0b110.
Before reading XCR0, __xgetbv! checks CPUID.1:ECX bits 26 and 27, i.e. XSAVE and OSXSAVE. The AVX row also checks CPUID.1:ECX.AVX bit 28. The remaining question is the XCR0 mask.
The usual AVX detection contract is:
CPUID.1:ECX.XSAVE == 1
CPUID.1:ECX.OSXSAVE == 1
XGETBV(XCR0) & 0b110 == 0b110
CPUID.1:ECX.AVX == 1
That requires both XMM and YMM state to be enabled by the operating system. Intel's published example likewise checks XCR0 & 6 == 6:
https://www.intel.com/content/dam/develop/external/us/en/documents/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family.pdf
The current AVX row checks only the XMM bit in XCR0. This appears to permit "avx" to report true when XMM state is enabled but YMM state is not.
Expected behavior / proposed direction
If there is no intentional special case, the AVX mapping appears to need the existing "ymm" category:
("avx", "ymm", 0, ecx, 28)
Tests should cover a state with XSAVE, OSXSAVE, and CPUID.AVX set, XMM enabled in XCR0, but YMM disabled, and require AVX detection to return false.
Potential impact
A false-positive AVX result could allow a consumer to execute AVX instructions without the required OS-managed YMM state, leading to an invalid-instruction fault or equivalent failure.
The current sha2 0.10.9 paths inspected here request the SHA/SSE conjunction and AVX2, not the standalone "avx" feature. Its AVX2 row already uses the "ymm" category. Thus this second finding is crate-wide and independent of the concrete sha2 instantiation above.
Limits of what has been demonstrated
For the leaf-7 observation, I have not identified a documented physical platform on which the exact bit conjunction used by the sha2 consumer above produces a false positive. This report does not claim that such a platform does not exist.
A virtualized environment or hypervisor which caps the visible maximum basic CPUID leaf is a plausible scenario for reproducing the generic fail-open behavior, but this is currently a hypothesis.
For the AVX XCR0 observation, I likewise do not yet have a physical or virtual-machine reproducer demonstrating a field failure. It is a source-level potentially incomplete check presented for maintainer confirmation.
Neither observation is being presented as a demonstrated memory-safety vulnerability, and no security severity is being assigned.
Questions for maintainers
- Is the unguarded leaf-7 access based on an intended minimum-CPU or minimum-virtual-CPU assumption? If so, should that assumption be documented?
- Would you accept a change that obtains
CPUID(0).EAX, zeros leaf-7 results below the supported maximum, and separately guards subleaf 7.1?
- For leaf 7.1, which cross-vendor subleaf-availability check do you prefer?
- Is the
"avx" → "xmm" mapping intentional? If so, what makes the XCR0 YMM-state bit unnecessary for this API's AVX meaning?
- Would mocked or synthetic CPUID-result tests be acceptable for both boundary conditions?
- Would you prefer these two independent observations as separate issues?
No patch is attached, and no physical reproducer is currently available.
Versions inspected
cpufeatures0.2.1759ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b02809d92d5e95ab4c07c5d8bfd024bf2a17e96d20febcpufeatures0.3.08b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d20131e6ec3727f11f9f5e0c106e282014653a4d7bb7While reviewing the x86 feature-detection implementation, I found two independent questions. I am reporting them together for context, but I would be happy to split them into separate issues if that is easier to review.
1. CPUID leaf 7 is queried without checking the maximum basic leaf
Summary
Both inspected versions query structured feature leaf 7 directly, without first checking the maximum basic CPUID leaf reported by
CPUID(0).EAX.This appears not to fail closed on processors or virtual CPUs whose maximum basic leaf is below 7.
Updating from 0.2.17 to 0.3.0 does not currently address this: 0.3.0 retains the direct leaf-7 query and additionally queries subleaf 7.1.
Source evidence
In 0.2.17,
cpufeatures/src/x86.rs:47-58constructs:Source:
utils/cpufeatures/src/x86.rs
Lines 47 to 58 in 9d92d5e
In 0.3.0,
cpufeatures/src/x86.rs:41-51constructs:Source:
utils/cpufeatures/src/x86.rs
Lines 41 to 51 in 31e6ec3
A package-wide search of the Rust sources in both releases found no call to
__get_cpuid_max, nocpuid(0), and no equivalent maximum-basic-leaf guard.The Rust
core::arch::__cpuid_countdocumentation states that when the requested leaf is above the maximum supported basic or extended leaf, the result is the information for the highest supported basic information leaf, using the supplied subleaf:https://doc.rust-lang.org/core/arch/x86_64/fn.__cpuid_count.html
Therefore an unsupported query for leaf 7 is not guaranteed to produce zero feature bits. Bits belonging to the highest supported basic leaf could instead be interpreted using the leaf-7 layout.
Observed behavior
The detector always obtains leaf 7 before evaluating the requested feature predicates. If the reported maximum basic leaf is less than 7, the returned registers may describe a different leaf, but the
check!rules interpret them as leaf-7 feature flags.Expected behavior
Unsupported structured-feature leaves and subleaves should be represented as zero / unavailable before their bits are interpreted.
Conceptually:
For implementations where
CPUID.7.0:EAXreports the maximum input subleaf, the second guard would require that value to be at least 1.Potential impact
The generic failure mode is a false-positive CPU-feature result. A caller may then dispatch to an instruction that the CPU does not support. The expected failure would be an invalid-instruction exception or equivalent process failure.
I am not claiming a memory-safety vulnerability or assigning a security severity.
sha2contextAs one concrete consumer,
sha20.10.9 does not dispatch its x86 SHA-256 backend based only on the"sha"bit. It requests the conjunction:Its SHA-512 x86 backend separately requests
"avx2".Therefore practical reachability for that consumer depends on the complete conjunction of required bits, not merely one misinterpreted leaf-7 bit. This conditions or reduces the practical false-positive surface for this specific consumer, but does not correct the crate-wide fail-open query policy.
2. AVX uses the XMM-only XCR0 category
This is independent of the maximum-basic-leaf issue above.
Source evidence
Both 0.2.17 and 0.3.0 define:
The
"xmm"category calls__xgetbv!with mask0b10, while the existing"ymm"category uses0b110.Before reading XCR0,
__xgetbv!checks CPUID.1:ECX bits 26 and 27, i.e. XSAVE and OSXSAVE. The AVX row also checks CPUID.1:ECX.AVX bit 28. The remaining question is the XCR0 mask.The usual AVX detection contract is:
That requires both XMM and YMM state to be enabled by the operating system. Intel's published example likewise checks
XCR0 & 6 == 6:https://www.intel.com/content/dam/develop/external/us/en/documents/how-to-detect-new-instruction-support-in-the-4th-generation-intel-core-processor-family.pdf
The current AVX row checks only the XMM bit in XCR0. This appears to permit
"avx"to report true when XMM state is enabled but YMM state is not.Expected behavior / proposed direction
If there is no intentional special case, the AVX mapping appears to need the existing
"ymm"category:Tests should cover a state with XSAVE, OSXSAVE, and CPUID.AVX set, XMM enabled in XCR0, but YMM disabled, and require AVX detection to return false.
Potential impact
A false-positive AVX result could allow a consumer to execute AVX instructions without the required OS-managed YMM state, leading to an invalid-instruction fault or equivalent failure.
The current
sha20.10.9 paths inspected here request the SHA/SSE conjunction and AVX2, not the standalone"avx"feature. Its AVX2 row already uses the"ymm"category. Thus this second finding is crate-wide and independent of the concretesha2instantiation above.Limits of what has been demonstrated
For the leaf-7 observation, I have not identified a documented physical platform on which the exact bit conjunction used by the
sha2consumer above produces a false positive. This report does not claim that such a platform does not exist.A virtualized environment or hypervisor which caps the visible maximum basic CPUID leaf is a plausible scenario for reproducing the generic fail-open behavior, but this is currently a hypothesis.
For the AVX XCR0 observation, I likewise do not yet have a physical or virtual-machine reproducer demonstrating a field failure. It is a source-level potentially incomplete check presented for maintainer confirmation.
Neither observation is being presented as a demonstrated memory-safety vulnerability, and no security severity is being assigned.
Questions for maintainers
CPUID(0).EAX, zeros leaf-7 results below the supported maximum, and separately guards subleaf 7.1?"avx"→"xmm"mapping intentional? If so, what makes the XCR0 YMM-state bit unnecessary for this API's AVX meaning?No patch is attached, and no physical reproducer is currently available.