x86: size CacheInfo::levels to hold all leaf 2 cache descriptors#462
x86: size CacheInfo::levels to hold all leaf 2 cache descriptors#462EylonKrause wants to merge 1 commit into
Conversation
gchatelet
left a comment
There was a problem hiding this comment.
Thx for the PR.
It might be simpler to just increase CPU_FEATURES_MAX_CACHE_LEVEL to 16 and call it a day.
| @@ -1399,6 +1399,21 @@ flags : fpu mmx sse sse2 pni ssse3 sse4_1 sse4_2 | |||
| } | |||
|
|
|||
| // https://www.felixcloutier.com/x86/cpuid#example-3-1--example-of-cache-and-tlb-interpretation | |||
There was a problem hiding this comment.
This line should stay next to its original test.
There was a problem hiding this comment.
This doesn't seem to be addressed. Can you move this line so its stays close to the original test below?
| }); | ||
|
|
||
| const auto info = GetX86CacheInfo(); | ||
| EXPECT_LE(info.size, CPU_FEATURES_MAX_CACHE_LEVEL); |
There was a problem hiding this comment.
We should test for equality since the purpose of this test is to saturate the array.
| // Regression test: a CPUID leaf 2 advertising more cache/TLB descriptors | ||
| // than CPU_FEATURES_MAX_CACHE_LEVEL must not overflow CacheInfo::levels. AL | ||
| // (the low byte of EAX) is the ignored count byte; the other 15 bytes here | ||
| // are non-zero descriptors, exceeding the 10-entry levels array. |
There was a problem hiding this comment.
AL (the low byte of EAX) is the ignored count byte; the other 15 bytes here are non-zero descriptors, exceeding the 10-entry levels array.
This part of the comment is not very clear. It would be best to explain what the crafted value does.
- Each byte is a descriptor so this is 16 descriptors total which is more than
CPU_FEATURES_MAX_CACHE_LEVEL - The bit 31 must be set to zero for the leaf to be taken into account
- The lower byte of EAX is the zero descriptor and so is not taken into account.
| cpu().SetLeaves({ | ||
| {{0x00000000, 0}, Leaf{0x00000002, 0x756E6547, 0x6C65746E, 0x49656E69}}, | ||
| {{0x00000001, 0}, Leaf{0x00000F0A, 0x00010808, 0x00000000, 0x3FEBFBFF}}, | ||
| {{0x00000002, 0}, Leaf{0x01010101, 0x01010101, 0x01010101, 0x01010101}}, |
There was a problem hiding this comment.
Add a comment below to indicate that we're forging 16 descriptors (one per byte). AL being discarded. so 15 descriptors.
bab8339 to
49a3939
Compare
|
Thanks @gchatelet — good call, that's cleaner. Done:
Built with |
|
Thx for the patch, ca you address the remaining comments? |
49a3939 to
871faf5
Compare
ParseLeaf2 appends a CacheLevelInfo for every non-zero descriptor byte in CPUID leaf 2 (up to 15, since the AL count byte is ignored) into the fixed CacheInfo::levels[CPU_FEATURES_MAX_CACHE_LEVEL] array. Sized at 10, a CPU reporting more than 10 descriptors overflowed the array; this is reachable from the public GetX86CacheInfo(). Raise CPU_FEATURES_MAX_CACHE_LEVEL from 10 to 16 so the array can hold every descriptor a single leaf 2 can report. This both prevents the overflow and avoids silently dropping cache levels. Add a regression test that feeds 15 descriptors and checks they are all recorded without overflow.
|
Thanks @gchatelet! Addressed the review:
On the red |
|
Disclosure: this contribution was authored with an AI coding assistant (Claude) and reviewed before submission. |
gchatelet
left a comment
There was a problem hiding this comment.
Sorry I forgot to send this comment.
| // Increase this value if more cache levels are needed. It must be large enough | ||
| // to hold every descriptor a single CPUID leaf 2 can report (up to 15, since | ||
| // the AL count byte is ignored) so that ParseLeaf2 cannot overflow | ||
| // CacheInfo::levels. |
There was a problem hiding this comment.
The "Size must at least 15" part is specific to x86, note that this data structure is cpu agnostic and can be filled for other CPUs. Can you update the comment to say that for x86 it needs to be at least 15?
Description
ParseLeaf2(legacy CPUID leaf-2 cache parsing insrc/impl_x86__base_implementation.inl) appends aCacheLevelInfotoCacheInfo::levels— a fixedCPU_FEATURES_MAX_CACHE_LEVEL(10) array — for every non-zero descriptor byte, but the loop is bounded only bysizeof(data) == 16and never checksinfo->sizeagainst the array capacity:A CPUID leaf 2 advertising more than 10 non-zero descriptors — up to 15 are representable (16 bytes minus the AL count byte) — writes past
levels[10], an out-of-bounds write into adjacent memory, reachable from publicGetX86CacheInfo()on hardware/hypervisor-controlled CPUID input.This is the same class of memory corruption fixed back in #190 for the leaf-4 /
0x8000001Dpath: the siblingParseCacheInfoalready guards its loop withinfo.size < CPU_FEATURES_MAX_CACHE_LEVEL. The legacy leaf-2 path (ParseLeaf2, added in #80) was left unbounded — this applies the same guard there.Changes & Impact
src/impl_x86__base_implementation.inl: add&& info->size < CPU_FEATURES_MAX_CACHE_LEVELto theParseLeaf2loop condition, mirroringParseCacheInfo. Behavior is byte-identical on real CPUs (which advertise far fewer than 10 leaf-2 descriptors); it only stops the overflow on pathological/hostile input.test/cpuinfo_x86_test.cc: addLeaf2_TooManyDescriptors_DoesNotOverflow, a regression test feeding a leaf-2 with 15 non-zero descriptors.Testing
Built
cpuinfo_x86_testwith-fsanitize=address,undefined(CMake + Ninja):AddressSanitizer: stack-buffer-overflow WRITE ... in ParseLeaf2 src/impl_x86__base_implementation.inl:1876.cpuinfo_x86_testsuite is green (69/69) under ASan/UBSan.