From 4224551e4845bfd5d8f2ddcb98dcdf308a3e1e47 Mon Sep 17 00:00:00 2001 From: Mike Miller Date: Sat, 26 Sep 2026 14:51:37 +0100 Subject: [PATCH] Don't fault on an unaligned lock cmpxchg8b x86 allows any alignment, and the i386 ABI aligns uint64_t struct fields to 4 bytes, but the aarch64 gadget sent every address that isn't 8-byte aligned to segfault_write without setting tlb->segfault_addr. The guest got a SIGSEGV or, if the stale address was mapped, retried forever. Python 3.14 (Alpine 3.24) hits this at startup. Do the unaligned case in C, through the TLB under a lock. Co-Authored-By: Claude Opus 5.5 --- asbestos/gadgets-aarch64/misc.S | 18 +++++++++++++++++- asbestos/helpers.c | 26 ++++++++++++++++++++++++++ tests/e2e/cmpxchg8b/cmpxchg8b.c | 32 ++++++++++++++++++++++++++++++++ tests/e2e/cmpxchg8b/expected.txt | 2 ++ tests/e2e/cmpxchg8b/test.sh | 3 +++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/cmpxchg8b/cmpxchg8b.c create mode 100644 tests/e2e/cmpxchg8b/expected.txt create mode 100644 tests/e2e/cmpxchg8b/test.sh diff --git a/asbestos/gadgets-aarch64/misc.S b/asbestos/gadgets-aarch64/misc.S index 7841c11e05..516b780a8b 100644 --- a/asbestos/gadgets-aarch64/misc.S +++ b/asbestos/gadgets-aarch64/misc.S @@ -128,7 +128,23 @@ gret 1 write_bullshit 64, atomic_cmpxchg8b -3: # All unaligned paths +3: # Unaligned. The exclusive load above needs 8-byte alignment, so do it in C. + str eax, [_cpu, CPU_eax] + str ebx, [_cpu, CPU_ebx] + str ecx, [_cpu, CPU_ecx] + str edx, [_cpu, CPU_edx] + save_c + mov x0, _cpu + sub x1, _tlb, TLB_entries + mov w2, _addr + bl NAME(helper_cmpxchg8b_unaligned) + mov x19, x0 + restore_c + cbnz x19, 4f + ldr eax, [_cpu, CPU_eax] + ldr edx, [_cpu, CPU_edx] + gret 1 +4: b segfault_write diff --git a/asbestos/helpers.c b/asbestos/helpers.c index 4c4a1bd08b..1ae1f6b02e 100644 --- a/asbestos/helpers.c +++ b/asbestos/helpers.c @@ -1,6 +1,8 @@ #include #include "emu/cpu.h" #include "emu/cpuid.h" +#include "emu/tlb.h" +#include "util/sync.h" void helper_cpuid(dword_t *a, dword_t *b, dword_t *c, dword_t *d) { do_cpuid(a, b, c, d); @@ -21,3 +23,27 @@ void helper_expand_flags(struct cpu_state *cpu) { void helper_collapse_flags(struct cpu_state *cpu) { collapse_flags(cpu); } + +// lock cmpxchg8b on an address that isn't 8-byte aligned, which x86 allows and +// the i386 ABI produces (it aligns 64-bit struct fields to 4 bytes). The +// gadget's exclusive load needs 8-byte alignment, so this does the exchange +// under a lock instead. Returns nonzero on a fault, with tlb->segfault_addr set. +static lock_t cmpxchg8b_lock = LOCK_INITIALIZER; +int helper_cmpxchg8b_unaligned(struct cpu_state *cpu, struct tlb *tlb, addr_t addr) { + uint64_t expected = ((uint64_t) cpu->edx << 32) | cpu->eax; + uint64_t desired = ((uint64_t) cpu->ecx << 32) | cpu->ebx; + uint64_t value; + bool ok; + lock(&cmpxchg8b_lock); + ok = tlb_read(tlb, addr, &value, sizeof(value)); + if (ok && value == expected) + ok = tlb_write(tlb, addr, &desired, sizeof(desired)); + unlock(&cmpxchg8b_lock); + if (!ok) + return 1; + cpu->eax = (dword_t) value; + cpu->edx = (dword_t) (value >> 32); + cpu->zf = value == expected; + cpu->zf_res = 0; + return 0; +} diff --git a/tests/e2e/cmpxchg8b/cmpxchg8b.c b/tests/e2e/cmpxchg8b/cmpxchg8b.c new file mode 100644 index 0000000000..419af1ebf5 --- /dev/null +++ b/tests/e2e/cmpxchg8b/cmpxchg8b.c @@ -0,0 +1,32 @@ +// lock cmpxchg8b on 64-bit values that are only 4-byte aligned, the way the +// i386 ABI lays out uint64_t struct fields: one mid-page and one straddling a +// page boundary, each incremented from several threads at once. +#include +#include +#include +#include + +#define THREADS 4 +#define ITERS 20000 + +static void *bump(void *p) { + for (int i = 0; i < ITERS; i++) + __atomic_fetch_add((uint64_t *) p, 1, __ATOMIC_SEQ_CST); + return NULL; +} + +static unsigned long long run(char *where) { + pthread_t threads[THREADS]; + for (int i = 0; i < THREADS; i++) + pthread_create(&threads[i], NULL, bump, where); + for (int i = 0; i < THREADS; i++) + pthread_join(threads[i], NULL); + return *(uint64_t *) where; +} + +int main(void) { + char *pages = mmap(NULL, 8192, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + printf("mid-page: %llu\n", run(pages + 100)); + printf("page boundary: %llu\n", run(pages + 4092)); + return 0; +} diff --git a/tests/e2e/cmpxchg8b/expected.txt b/tests/e2e/cmpxchg8b/expected.txt new file mode 100644 index 0000000000..ce29ffa8e8 --- /dev/null +++ b/tests/e2e/cmpxchg8b/expected.txt @@ -0,0 +1,2 @@ +mid-page: 80000 +page boundary: 80000 diff --git a/tests/e2e/cmpxchg8b/test.sh b/tests/e2e/cmpxchg8b/test.sh new file mode 100644 index 0000000000..4c9df4f2a6 --- /dev/null +++ b/tests/e2e/cmpxchg8b/test.sh @@ -0,0 +1,3 @@ +#!/bin/sh +gcc -O2 -pthread cmpxchg8b.c -o cmpxchg8b +./cmpxchg8b