Commit fd9f30d1 authored by Helge Deller's avatar Helge Deller
Browse files

parisc: Avoid crash due to unaligned access in unwinder



Guenter Roeck reported this kernel crash on his emulated B160L machine:

Starting network: udhcpc: started, v1.36.1
 Backtrace:
  [<104320d4>] unwind_once+0x1c/0x5c
  [<10434a00>] walk_stackframe.isra.0+0x74/0xb8
  [<10434a6c>] arch_stack_walk+0x28/0x38
  [<104e5efc>] stack_trace_save+0x48/0x5c
  [<105d1bdc>] set_track_prepare+0x44/0x6c
  [<105d9c80>] ___slab_alloc+0xfc4/0x1024
  [<105d9d38>] __slab_alloc.isra.0+0x58/0x90
  [<105dc80c>] kmem_cache_alloc_noprof+0x2ac/0x4a0
  [<105b8e54>] __anon_vma_prepare+0x60/0x280
  [<105a823c>] __vmf_anon_prepare+0x68/0x94
  [<105a8b34>] do_wp_page+0x8cc/0xf10
  [<105aad88>] handle_mm_fault+0x6c0/0xf08
  [<10425568>] do_page_fault+0x110/0x440
  [<10427938>] handle_interruption+0x184/0x748
  [<11178398>] schedule+0x4c/0x190
  BUG: spinlock recursion on CPU#0, ifconfig/2420
  lock: terminate_lock.2+0x0/0x1c, .magic: dead4ead, .owner: ifconfig/2420, .owner_cpu: 0

While creating the stack trace, the unwinder uses the stack pointer to guess
the previous frame to read the previous stack pointer from memory.  The crash
happens, because the unwinder tries to read from unaligned memory and as such
triggers the unalignment trap handler which then leads to the spinlock
recursion and finally to a deadlock.

Fix it by checking the alignment before accessing the memory.

Reported-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarHelge Deller <deller@gmx.de>
Tested-by: default avatarGuenter Roeck <linux@roeck-us.net>
Cc: stable@vger.kernel.org # v6.12+
parent 6146a0f1
Loading
Loading
Loading
Loading
+10 −3
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@

#define KERNEL_START (KERNEL_BINARY_TEXT_START)

#define ALIGNMENT_OK(ptr, type) (((ptr) & (sizeof(type) - 1)) == 0)

extern struct unwind_table_entry __start___unwind[];
extern struct unwind_table_entry __stop___unwind[];

@@ -257,12 +259,15 @@ static int unwind_special(struct unwind_frame_info *info, unsigned long pc, int
	if (pc_is_kernel_fn(pc, _switch_to) ||
	    pc == (unsigned long)&_switch_to_ret) {
		info->prev_sp = info->sp - CALLEE_SAVE_FRAME_SIZE;
		if (ALIGNMENT_OK(info->prev_sp, long))
			info->prev_ip = *(unsigned long *)(info->prev_sp - RP_OFFSET);
		else
			info->prev_ip = info->prev_sp = 0;
		return 1;
	}

#ifdef CONFIG_IRQSTACKS
	if (pc == (unsigned long)&_call_on_stack) {
	if (pc == (unsigned long)&_call_on_stack && ALIGNMENT_OK(info->sp, long)) {
		info->prev_sp = *(unsigned long *)(info->sp - FRAME_SIZE - REG_SZ);
		info->prev_ip = *(unsigned long *)(info->sp - FRAME_SIZE - RP_OFFSET);
		return 1;
@@ -370,8 +375,10 @@ static void unwind_frame_regs(struct unwind_frame_info *info)
			info->prev_sp = info->sp - frame_size;
			if (e->Millicode)
				info->rp = info->r31;
			else if (rpoffset)
			else if (rpoffset && ALIGNMENT_OK(info->prev_sp, long))
				info->rp = *(unsigned long *)(info->prev_sp - rpoffset);
			else
				info->rp = 0;
			info->prev_ip = info->rp;
			info->rp = 0;
		}