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.
| }); | ||
|
|
||
| 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 |
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.