Query Regarding Stack-Out-of-Bounds Error
Dear Linux Kernel Community, I am writing to seek clarification on a potential stack-out-of-bounds issue observed during the calls to for_each_set_bit function or find_first_bit function, specifically in the following code snippet: static struct cmd_info *find_cmd_entry_any_ring(struct intel_gvt *gvt, unsigned int opcode, int rings) { struct cmd_info *info = NULL; unsigned int ring; ... for_each_set_bit(ring, (unsigned long *)&rings, I915_NUM_ENGINES) { In the above code, a 32-bit integer pointer (rings) is being cast to a 64-bit unsigned long pointer, which leads to an extra 4 bytes being accessed. This raises a concern regarding a stack-out-of-bounds bug. My specific query is: While it is logically understandable that a write operation involving these extra 4 bytes could cause a kernel crash, in this case, it is a read operation that is occurring. Would such a read operation still be capable of crashing the kernel or resulting in unpredictable system behaviour? If so, could you please explain how that would happen? I appreciate your insights and look forward to your guidance on this issue. -- Thanks, Sekhar
On Mon, 26 Aug 2024 18:04:39 +0530, Muni Sekhar said:
static struct cmd_info *find_cmd_entry_any_ring(struct intel_gvt *gvt, unsigned int opcode, int rings) { struct cmd_info *info = NULL; unsigned int ring; ... for_each_set_bit(ring, (unsigned long *)&rings, I915_NUM_ENGINES) {
In the above code, a 32-bit integer pointer (rings) is being cast to a 64-bit unsigned long pointer, which leads to an extra 4 bytes being accessed. This raises a concern regarding a stack-out-of-bounds bug.
My specific query is: While it is logically understandable that a write operation involving these extra 4 bytes could cause a kernel crash, in this case, it is a read operation that is occurring.
Note that 'ring' is located in the stack frame for the current function. So to complete the analysis - is there any way that the stack frame can be located in such a way that 'ring' is the *very last* 4 bytes on a page, and the next page *isn't* allocated, *and* I915_NUM_ENGINES is big enough to cause the loop to walk off the end? For bonus points, part 1: Does the answer depend on whether the architecture has stacks that grow up, or grow down in address? For bonus points, part 2: can this function be called quickly enough, and enough times, that it can be abused to do something interesting to L1/L2 cache and speculative execution, because some systems will fetch not only the bytes needed, but as much as 64 or 128 bytes of cache line? Can you name 3 security bugs that abused this sort of thing? Free hint: There's a bit of interesting code in kernel/exit.c that tells you if your system has gotten close to running out of kernel stack. [/usr/src/linux-next] dmesg | grep 'greatest stack' [ 1.093400] [ T40] pgdatinit0 (40) used greatest stack depth: 13920 bytes left [ 3.832907] [ T82] modprobe (82) used greatest stack depth: 8 bytes left Hmm... wonder how that modprobe managed *that* :)
On Tue, Aug 27, 2024 at 1:16 AM Valdis Klētnieks <valdis.kletnieks@vt.edu> wrote:
On Mon, 26 Aug 2024 18:04:39 +0530, Muni Sekhar said:
static struct cmd_info *find_cmd_entry_any_ring(struct intel_gvt *gvt, unsigned int opcode, int rings) { struct cmd_info *info = NULL; unsigned int ring; ... for_each_set_bit(ring, (unsigned long *)&rings, I915_NUM_ENGINES) {
In the above code, a 32-bit integer pointer (rings) is being cast to a 64-bit unsigned long pointer, which leads to an extra 4 bytes being accessed. This raises a concern regarding a stack-out-of-bounds bug.
My specific query is: While it is logically understandable that a write operation involving these extra 4 bytes could cause a kernel crash, in this case, it is a read operation that is occurring.
Note that 'ring' is located in the stack frame for the current function. So to complete the analysis - is there any way that the stack frame can be located in such a way that 'ring' is the *very last* 4 bytes on a page, and the next page *isn't* allocated, *and* I915_NUM_ENGINES is big enough to cause the loop to walk off the end?
For bonus points, part 1: Does the answer depend on whether the architecture has stacks that grow up, or grow down in address?
Stack Frame Example |---------------------------| | Return Address | |---------------------------| | Saved Frame Pointer | |---------------------------| | Parameter: gvt | |---------------------------| | Parameter: opcode | |---------------------------| | Parameter: rings | |---------------------------| | Local Variable: info | |---------------------------| | Local Variable: ring | |---------------------------| If the stack grows downwards, the previous 32 bits (4 bytes) before rings will be read as part of the 64-bit value. If the stack grows upwards, the next 32 bits (4 bytes) after rings will be read as part of the 64-bit value. Consider that the stack grows upwards and the "ring" variable is located at the very end of a stack page. What should be the value of I915_NUM_ENGINES to trigger the Stack-Out-of-Bounds crash?
For bonus points, part 2: can this function be called quickly enough, and enough times, that it can be abused to do something interesting to L1/L2 cache and speculative execution, because some systems will fetch not only the bytes needed, but as much as 64 or 128 bytes of cache line? Can you name 3 security bugs that abused this sort of thing?
Free hint: There's a bit of interesting code in kernel/exit.c that tells you if your system has gotten close to running out of kernel stack.
[/usr/src/linux-next] dmesg | grep 'greatest stack' [ 1.093400] [ T40] pgdatinit0 (40) used greatest stack depth: 13920 bytes left [ 3.832907] [ T82] modprobe (82) used greatest stack depth: 8 bytes left
Hmm... wonder how that modprobe managed *that* :)
-- Thanks, Sekhar
On Tue, Aug 27, 2024 at 1:16 AM Valdis Klētnieks <valdis.kletnieks@vt.edu> wrote:
On Mon, 26 Aug 2024 18:04:39 +0530, Muni Sekhar said:
static struct cmd_info *find_cmd_entry_any_ring(struct intel_gvt *gvt, unsigned int opcode, int rings) { struct cmd_info *info = NULL; unsigned int ring; ... for_each_set_bit(ring, (unsigned long *)&rings, I915_NUM_ENGINES) {
In the above code, a 32-bit integer pointer (rings) is being cast to a 64-bit unsigned long pointer, which leads to an extra 4 bytes being accessed. This raises a concern regarding a stack-out-of-bounds bug.
My specific query is: While it is logically understandable that a write operation involving these extra 4 bytes could cause a kernel crash, in this case, it is a read operation that is occurring.
Note that 'ring' is located in the stack frame for the current function. So to complete the analysis - is there any way that the stack frame can be located in such a way that 'ring' is the *very last* 4 bytes on a page, and the next page *isn't* allocated, *and* I915_NUM_ENGINES is big enough to cause the loop to walk off the end?
For bonus points, part 1: Does the answer depend on whether the architecture has stacks that grow up, or grow down in address?
Here’s an example stack frame for context: |---------------------------| | Return Address | |---------------------------| | Saved Frame Pointer | |---------------------------| | Parameter: gvt | |---------------------------| | Parameter: opcode | |---------------------------| | Parameter: rings | |---------------------------| | Local Variable: info | |---------------------------| | Local Variable: ring | |---------------------------| Stack Growth Downwards: If the stack grows downward in address space, is there any scenario where the stack frame could be positioned such that the return address is located at the very last 4 bytes of a page, the next page isn't allocated, and I915_NUM_ENGINES is large enough to cause the loop to walk off the end? If this happens, would it result in a stack-out-of-bounds error leading to a kernel crash? Stack Growth Upwards: Conversely, if the stack grows upward, could the stack frame be positioned in such a way that the 'ring' variable is at the very last 4 bytes of a page, and the next page isn’t allocated, with I915_NUM_ENGINES being large enough to cause the loop to walk off the end? Would this also result in a stack-out-of-bounds error causing a kernel crash? I'm curious as to why the likelihood of this error seems to depend on whether the architecture has stacks that grow up or down in address space. In both scenarios stack-out-of-bounds error behaves the same, right?
For bonus points, part 2: can this function be called quickly enough, and enough times, that it can be abused to do something interesting to L1/L2 cache and speculative execution, because some systems will fetch not only the bytes needed, but as much as 64 or 128 bytes of cache line? Can you name 3 security bugs that abused this sort of thing?
Where should I look to find more details about these security bugs? I appreciate your insights on these questions.
Free hint: There's a bit of interesting code in kernel/exit.c that tells you if your system has gotten close to running out of kernel stack.
[/usr/src/linux-next] dmesg | grep 'greatest stack' [ 1.093400] [ T40] pgdatinit0 (40) used greatest stack depth: 13920 bytes left [ 3.832907] [ T82] modprobe (82) used greatest stack depth: 8 bytes left
Hmm... wonder how that modprobe managed *that* :)
-- Thanks, Sekhar
participants (2)
-
Muni Sekhar -
Valdis Klētnieks