Index | Thread | Search

From:
Brad Smith <brad@comstyle.com>
Subject:
arm64: fix PT_GETFPREGS/PT_SETFPREGS for traced processes
To:
tech@openbsd.org
Cc:
kettenis@openbsd.org
Date:
Mon, 26 Oct 2026 05:33:40 -0400

Download raw body.

Thread
  • Brad Smith:

    arm64: fix PT_GETFPREGS/PT_SETFPREGS for traced processes

process_read_fpregs() and process_write_fpregs() call fpu_save() and
fpu_drop(), which operate on the FPU of the current CPU.  For ptrace(2)
that FPU belongs to the debugger, not the traced process, so
PT_GETFPREGS copied the debugger's live FP/SIMD registers into the
traced process and returned them, and PT_SETFPREGS discarded the
debugger's live FPU state.

The FPU state of a process that is not running is already saved in its
pcb by cpu_switchto(), so only save or drop the live state when the
process is curproc.

This affects any debugger on arm64 (egdb, lldb).  Simply reading an FP
register from the debugger corrupts the traced process's FP state, and
the debugger can lose its own callee-saved d8-d15.

Reproducer:

fp2.c:

#include <stdio.h>
int main(void) {
  unsigned char v8[16], v9[16];
  __asm__ volatile("movi v8.16b, #0x42\n\tmovi v9.16b, #0x17" ::: "v8", "v9");
  __asm__ volatile("nop");  /* break here */
  __asm__ volatile("str q8, [%0]\n\tstr q9, [%1]" :: "r"(v8), "r"(v9) : "memory");
  printf("process sees v8=0x%02x v9=0x%02x\n", v8[0], v9[0]);
  return 0;
}

$ cc -g -O0 -o fp2 fp2.c
$ ./fp2
process sees v8=0x42 v9=0x17

Before:

$ egdb -q -batch -ex 'break 5' -ex run -ex 'p/x $v8.b.u' -ex 'p/x $v9.b.u' \
    -ex 'set var $v9.b.u[0] = 0x55' -ex 'p/x $v9.b.u' -ex continue ./fp2
...
$1 = {0x0 <repeats 16 times>}
$2 = {0x0 <repeats 16 times>}
$3 = {0x0 <repeats 16 times>}
process sees v8=0x00 v9=0x00

After:

$1 = {0x42 <repeats 16 times>}
$2 = {0x17 <repeats 16 times>}
$3 = {0x55, 0x17 <repeats 15 times>}
process sees v8=0x42 v9=0x55


--- a/sys/arch/arm64/arm64/process_machdep.c
+++ b/sys/arch/arm64/arm64/process_machdep.c
@@ -70,7 +70,11 @@
 int
 process_read_fpregs(struct proc *p, struct fpreg *regs)
 {
-	if (p->p_addr->u_pcb.pcb_flags & PCB_FPU)
+	/*
+	 * The FPU state of a process that is not running was saved when
+	 * it was switched out; only curproc can have live state.
+	 */
+	if (p == curproc && (p->p_addr->u_pcb.pcb_flags & PCB_FPU))
 		fpu_save(p);
 
 	if (p->p_addr->u_pcb.pcb_flags & PCB_FPU)
@@ -105,7 +109,8 @@
 	p->p_addr->u_pcb.pcb_flags |= PCB_FPU;
 
 	/* drop FPU state */
-	fpu_drop();
+	if (p == curproc)
+		fpu_drop();
 
 	return(0);
 }