arm/unwind: Check stack pointer boundaries before dereferencing

If the unwinder somehow ends up with a stack pointer that lies outside
the stack, then an attempt to dereference can lead to a fault, which
causes the kernel to panic again and unwind the stack, which leads to a
fault...

Add kstack_contains() checks at points where we dereference the stack
pointer.  This avoids the aforementioned infinite loop in one case I hit
where some OpenSSL assembly code apparently confuses the unwinder.

Reviewed by:	jhb
MFC after:	2 weeks
Sponsored by:	Klara, Inc.
Sponsored by:	Stormshield
Differential Revision:	https://reviews.freebsd.org/D41210
This commit is contained in:
Mark Johnston
2023-07-27 15:44:00 -04:00
parent 81a7ce4404
commit 1be56e0bb1
+14
View File
@@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$");
#include <sys/kernel.h>
#include <sys/linker.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/systm.h>
@@ -370,6 +371,7 @@ unwind_exec_read_byte(struct unwind_state *state)
static int
unwind_exec_insn(struct unwind_state *state)
{
struct thread *td = curthread;
unsigned int insn;
uint32_t *vsp = (uint32_t *)state->registers[SP];
int update_vsp = 0;
@@ -404,6 +406,10 @@ unwind_exec_insn(struct unwind_state *state)
/* Load the registers */
for (reg = 4; mask && reg < 16; mask >>= 1, reg++) {
if (mask & 1) {
if (!kstack_contains(td, (uintptr_t)vsp,
sizeof(*vsp)))
return 1;
state->registers[reg] = *vsp++;
state->update_mask |= 1 << reg;
@@ -430,6 +436,9 @@ unwind_exec_insn(struct unwind_state *state)
update_vsp = 1;
/* Pop the registers */
if (!kstack_contains(td, (uintptr_t)vsp,
sizeof(*vsp) * (4 + count)))
return 1;
for (reg = 4; reg <= 4 + count; reg++) {
state->registers[reg] = *vsp++;
state->update_mask |= 1 << reg;
@@ -437,6 +446,8 @@ unwind_exec_insn(struct unwind_state *state)
/* Check if we are in the pop r14 version */
if ((insn & INSN_POP_TYPE_MASK) != 0) {
if (!kstack_contains(td, (uintptr_t)vsp, sizeof(*vsp)))
return 1;
state->registers[14] = *vsp++;
}
@@ -457,6 +468,9 @@ unwind_exec_insn(struct unwind_state *state)
/* Load the registers */
for (reg = 0; mask && reg < 4; mask >>= 1, reg++) {
if (mask & 1) {
if (!kstack_contains(td, (uintptr_t)vsp,
sizeof(*vsp)))
return 1;
state->registers[reg] = *vsp++;
state->update_mask |= 1 << reg;
}