https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127667

            Bug ID: 127667
           Summary: GCC `__atomic_exchange_n` crashes (SIGSEGV) on
                    8-byte-aligned 16-byte `_BitInt` objects
           Product: gcc
           Version: 17.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: bic60176 at gmail dot com
  Target Milestone: ---

Summary

On x86-64, `_BitInt(N)` for `64 < N <= 128` is a 16-byte integral type whose
ABI
alignment is only 8.  `__atomic_exchange_n` on such an object is expanded to a
call to libatomic's `__atomic_exchange_16`, whose x86-64 IFUNC implementation
executes `lock cmpxchg16b` without checking the pointer's alignment.
`cmpxchg16b` raises `#GP` on an operand that is not 16-byte aligned, so any
exchange on a correctly-aligned-but-not-16-aligned object dies with SIGSEGV.

All the other 16-byte `__atomic` operations (`__atomic_load_n`,
`__atomic_store_n`, `__atomic_compare_exchange_n`, `__atomic_fetch_*`,
`__atomic_exchange`) crash the same way; `__atomic_exchange_n` is just the
entry point this was found through.


Reproducing the Bug

No pointer casts or attributes are needed — an ordinary struct is enough,
because `struct S` has alignment 8 and `val` sits at offset 8.

```c
/* gcc -O2 repro.c -o repro && ./repro   =>  Segmentation fault */
struct S { long lock; _BitInt(128) val; };
struct S s;

int main(void)
{
  return (int)__atomic_exchange_n(&s.val, (_BitInt(128))1, __ATOMIC_SEQ_CST);
}
```

An array of such structs shows it is address-dependent — element 0 succeeds,
element 1 crashes:

```c
#include <stdio.h>
struct S { _BitInt(128) v; long tag; };
struct S a[3];

int main(void)
{
  for (int i = 0; i < 3; i++) {
    printf("a[%d].v %% 16 = %d\n", i, (int)((unsigned long)&a[i].v % 16));
    fflush(stdout);
    __atomic_exchange_n(&a[i].v, (_BitInt(128))(i + 1), __ATOMIC_SEQ_CST);
  }
  return 0;
}
```

```
a[0].v % 16 = 0
a[1].v % 16 = 8
Segmentation fault
```

That the fault is inside libatomic is confirmed by interposing the symbol —
with a local `__atomic_exchange_16` the same program runs to completion:

```c
#include <stdio.h>
__int128 __atomic_exchange_16(volatile void *mem, __int128 val, int model) {
  puts("our __atomic_exchange_16 was called");
  __int128 old = *(volatile __int128 *)mem;
  *(volatile __int128 *)mem = val;
  return old;
}
struct S { long lock; _BitInt(128) val; };
struct S s = { 0, 42 };
int main(void) {
  _BitInt(128) old = __atomic_exchange_n(&s.val, (_BitInt(128))7,
__ATOMIC_SEQ_CST);
  printf("old=%d val=%d\n", (int)old, (int)s.val);   /* old=42 val=7 */
  return 0;
}
```

Fix

Make the x86-64 16-byte fast path conditional on the pointer being 16-byte
aligned, and let unaligned addresses fall through to libatomic's existing
lock-based implementation.  Because the branch is a pure function of the
address, all accesses to a given object consistently take the same path, so the
locking and lock-free variants never mix on one object.

The sketch below does that for `libat_exchange_16`; the same guard is needed in
`cas_n.c`, `load_n.c`, `store_n.c` and `fop_n.c` for `N == 16`, so in practice
it is better introduced as a shared `ALIGNED_16(ptr)` helper used by every
`#if N == 16 && IFUNC_ALT != 0` block.

```diff
--- a/libatomic/exch_n.c
+++ b/libatomic/exch_n.c
@@
 #if !DONE && defined(atomic_compare_exchange_n)
 UTYPE
 SIZE(libat_exchange) (UTYPE *mptr, UTYPE newval, int smodel)
 {
   UTYPE oldval;

+  /* The inline compare-and-swap used here (e.g. cmpxchg16b on x86-64) faults
+     on an operand that is not naturally aligned, but a 16-byte type may be
+     only 8-byte aligned -- _BitInt(65..128) on x86-64 is.  Use the protected
+     path for those.  */
+  if (!libat_aligned_p (mptr, N))
+    return SIZE(libat_exchange_locked) (mptr, newval, smodel);
+
   pre_barrier (smodel);

   oldval = *mptr;
   while (!atomic_compare_exchange_n (mptr, &oldval, newval, true,
                                     __ATOMIC_RELAXED, __ATOMIC_RELAXED))
     continue;

   post_barrier (smodel);

   return oldval;
 }

 #define DONE 1
 #endif /* atomic_compare_exchange_n */
```

where `libat_aligned_p` is

```diff
--- a/libatomic/libatomic_i.h
+++ b/libatomic/libatomic_i.h
@@
+static inline bool
+libat_aligned_p (void *ptr, size_t n)
+{
+  return ((uintptr_t) ptr & (n - 1)) == 0;
+}
```

Reply via email to