Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Native.register (direct mapping) corrupts/crashes on stack-spilled Structure.ByValue arguments on macOS arm64

Aperta
#1,736 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
45/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
c, java
Ambito
tooling

Direzione di ricerca

Riproduci il failure con repro.c e Repro.java su macOS arm64, quindi traccia Native_registerMethod fino al percorso ffi_call di libffi incluso per gli argomenti Structure.ByValue riversati nello stack. Il lavoro è completo quando la chiamata termina senza corruzione o SIGSEGV e stampa i valori previsti di capacity e len per tutti gli 11 argomenti, incluso il puntatore CallStatus finale.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

[!NOTE]
AI was used to analyze this bug and create a minimal test case.

Environment
  • JNA version: 5.18.1 (also reproduces on 5.19.1, the latest release as of this report)
  • OS: macOS 26.5.2 (25F84), Darwin 25.5.0
  • Arch: aarch64 (Apple Silicon, Mac15,11)
  • JDK: OpenJDK 17.0.16 (Homebrew build) — also reproduces on OpenJDK 26.0.1
  • Invocation style: Native.register (direct mapping), not Native.load/interface mapping
Summary

A native function called via Native.register that takes several Structure.ByValue arguments (each a 24-byte struct: two long fields + one pointer field — an AAPCS64 MEMORY-class aggregate, passed by hidden reference) receives corrupted/garbage data in the arguments that spill past the 8 available integer/pointer argument registers. In our reduced repro this manifests as a hard SIGSEGV inside JNA's bundled libffi's ffi_call, invoked from Java_com_sun_jna_Native_registerMethod. The same call works correctly on x86_64 Linux with an identical argument count and struct shape.

We hit this indirectly through mozilla/uniffi-rs, whose generated Kotlin bindings use Native.register and represent every non-primitive argument (records, options, strings) as a RustBuffer.ByValue struct — { capacity: u64, len: u64, data: *mut u8 }, i.e. exactly the 24-byte shape below. A UniFFI-exported Rust function/constructor with 9+ such arguments reliably crashes the JVM on Apple Silicon macOS while working fine on Linux. This report reduces that down to a minimal, uniffi-independent, pure C + JNA repro.

Reproduction

repro.c — compile with clang -dynamiclib -o libjnarepro.dylib repro.c:

#include <stdint.h>
#include <string.h>
#include <stdio.h>

typedef struct {
    uint64_t capacity;
    uint64_t len;
    void *data;
} Buf; /* 24 bytes: MEMORY-class aggregate on AAPCS64, passed by hidden pointer */

typedef struct {
    int8_t code;
    Buf error_buf;
} CallStatus;

uint64_t dump11(Buf a1, Buf a2, Buf a3, Buf a4, Buf a5, Buf a6, Buf a7, Buf a8,
                 Buf a9, Buf a10, Buf a11, CallStatus *out_status) {
    Buf *args[11] = {&a1,&a2,&a3,&a4,&a5,&a6,&a7,&a8,&a9,&a10,&a11};
    for (int i = 0; i < 11; i++) {
        fprintf(stdout, "arg%d capacity=%llu len=%llu%s\n", i + 1,
                (unsigned long long)args[i]->capacity,
                (unsigned long long)args[i]->len,
                args[i]->capacity == (uint64_t)(i + 1) ? "" : "  <-- CORRUPTED");
    }
    memset(out_status, 0, sizeof(*out_status));
    return 0;
}

Repro.java — compile/run with the JNA jar on the classpath, -Djna.library.path=. pointing at the directory containing libjnarepro.dylib:

import com.sun.jna.Native;
import com.sun.jna.Structure;

public class Repro {
    @Structure.FieldOrder({"capacity", "len", "data"})
    public static class Buf extends Structure {
        public long capacity;
        public long len;
        public com.sun.jna.Pointer data;

        public static class ByValue extends Buf implements Structure.ByValue {}

        static ByValue of(long tag) {
            ByValue b = new ByValue();
            b.capacity = tag;
            b.len = tag;
            b.data = null;
            return b;
        }
    }

    @Structure.FieldOrder({"code", "error_buf"})
    public static class CallStatus extends Structure {
        public byte code;
        public Buf error_buf;
    }

    static class DirectLib {
        static {
            Native.register(DirectLib.class, "jnarepro");
        }
        static native long dump11(Buf.ByValue a1, Buf.ByValue a2, Buf.ByValue a3, Buf.ByValue a4,
                                   Buf.ByValue a5, Buf.ByValue a6, Buf.ByValue a7, Buf.ByValue a8,
                                   Buf.ByValue a9, Buf.ByValue a10, Buf.ByValue a11, CallStatus outStatus);
    }

    public static void main(String[] args) {
        System.out.println("jna.version = " + Native.VERSION + "  os.arch=" + System.getProperty("os.arch"));
        CallStatus s = new CallStatus();
        DirectLib.dump11(
                Buf.of(1), Buf.of(2), Buf.of(3), Buf.of(4), Buf.of(5),
                Buf.of(6), Buf.of(7), Buf.of(8), Buf.of(9), Buf.of(10), Buf.of(11), s);
    }
}

Each Buf.of(tag) sets capacity = len = tag, so a correct call prints arg1 capacity=1 ... arg11 capacity=11 with no CORRUPTED markers.

Actual output (macOS arm64)
jna.version = 5.18.1  os.arch=aarch64
arg1 capacity=1 len=1
arg2 capacity=2 len=2
arg3 capacity=3 len=3
arg4 capacity=4 len=4
arg5 capacity=5 len=5
arg6 capacity=6 len=6
arg7 capacity=7 len=7
arg8 capacity=8 len=8
arg9 capacity=9 len=9
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00000001021244f8, pid=41852, tid=5123
# ...
# Problematic frame:
# C  [libjnarepro.dylib+0x4f8]  dump11+0xe8

Native frames from the hs_err file:

C  [libjnarepro.dylib+0x4f8]              dump11+0xe8
C  [jna...tmp+0x1004c]                    ffi_prep_closure_loc+0x1970
C  [jna...tmp+0xe64c]                     ffi_call+0x584
C  [jna...tmp+0xae3c]                     Java_com_sun_jna_Native_registerMethod+0xe8c
C  [jna...tmp+0xeb48]                     ffi_prep_closure_loc+0x46c
C  [jna...tmp+0x101d4]                    ffi_prep_closure_loc+0x1af8
j  Repro$DirectLib.dump11(...)J+0
j  Repro.main([Ljava/lang/String;)V+108

Arguments 1–9 (which fit in the 8 available AAPCS64 integer/pointer argument registers plus one that's actually classified as taken by the hidden return-value/indirect-result register — the exact boundary varies slightly by call shape) print correctly. The crash occurs while the callee reads argument 10 — the second argument that had to spill onto the stack — meaning the pointer JNA/libffi placed on the outgoing call's stack for that argument is invalid. The fault is inside JNA's own bundled libffi's ffi_call, not in the generated dump11 code or in Java-level marshalling.

Reducing further:

  • With the trailing CallStatus * (by-reference) argument removed entirely — just 9 or 11 Structure.ByValue args and nothing else — we saw no corruption at all in our testing; every argument came back with its expected tag. The corruption/crash only appeared once a trailing struct-by-reference out-parameter was added after the Structure.ByValue run, which is the shape every uniffi-generated call actually uses (a trailing RustCallStatus* out-param). That suggests the bug is specifically in how libffi computes/advances the stack offset once a run of stack-spilled Structure.ByValue arguments is followed by a differently-marshalled (by-reference) argument, not simply "many Structure.ByValue args."
  • An identical call shape using 11 plain long arguments instead of Structure.ByValue args (still followed by the trailing by-reference CallStatus) does not crash or corrupt.
  • I have not yet run this exact minimal repro on x86_64 Linux myself, but the originating uniffi-rs-generated code (same argument count and struct shape, called the same way) passes reliably in our Linux x86_64 CI and only fails on macOS arm64 — happy to run the minimal repro on Linux too if that's useful before this is investigated.
Expected behavior

Structure.ByValue arguments passed via Native.register should marshal correctly regardless of how many of them are needed to spill from registers onto the outgoing call's stack, on all supported architectures including macOS arm64.

Notes / related issues

This looks related in spirit to #1259 ("Passing structs by value on the stack is incorrect on arm64"), but is not obviously the same bug: #1259's repro struct is 8 bytes (a single int64_t field), which AAPCS64 classifies as a register-class/INTEGER aggregate passed directly in one register or one stack slot. The struct here is 24 bytes, which AAPCS64 always classifies as MEMORY-class and passes by hidden pointer regardless of position — a different code path in libffi's arm64 support. #1259 was reported fixed upstream as a side effect of an unrelated libffi refactor; this issue reproduces on the current release (5.19.1), so if it is the same underlying class of bug, that fix does not cover this code path.

It may also be related to the struct-by-value/callback crashes on Darwin arm64 acknowledged in #1238 and tracked in #1323 (partially addressed for direct callbacks in 5.11.0 per the changelog), though those predate the current libffi bundled in 5.18.1+ and it's unclear whether they cover plain (non-callback) direct-mapped function arguments.

Lingua principale
Java
Stelle
8.9k
Fork
1.7k
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di java-native-access/jna

Tutte le issue di java-native-access/jna

Issue simili

Altre issue su Java

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.