Skip to content

NativeAOT (osx-arm64): process crashes at pc=0 on first thread-suspension signal when exec'd from a process that had an SA_SIGINFO handler #135011

Description

@matt-gribben

Area: NativeAOT runtime — dotnet/runtime, NativeAOT runtime, src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp (ActivationHandler, g_previousActivationHandler)

Summary

A NativeAOT app that is started via execve/posix_spawn from a process that had installed an
SA_SIGINFO handler for the activation signal crashes with SIGSEGV at pc=0 the first time the runtime
sends itself the activation signal (any GC thread suspension / hijack).

Why

On the macOS version below, execve resets a caught signal to SIG_DFL but keeps the SA_SIGINFO bit.
In the new image, sigaction(SIGUSR1, NULL, &old) reports {sa_handler = 0, sa_flags = SA_SIGINFO|SA_RESTART} (0x42).
The runtime saves that as the previous action at startup. ActivationHandler then chains to the previous
action; because SA_SIGINFO is set it tail-calls sa_sigaction (== 0) without first checking for
SIG_DFL/SIG_IGN. Result: pc=0, lr=_sigtramp+56, x0=30 (SIGUSR1), x8=0x42 (the flags byte just loaded).
The same pattern probably applies to other chained signals (SIGSEGV chaining).

Environment

  • macOS 27.0.1 (26A434), Darwin 27.0.0, Apple Silicon (Mac16,8)
  • .NET SDK 10.0.401, microsoft.netcore.app.runtime.nativeaot.osx-arm64 10.0.12
  • Not tested on other macOS versions. Linux clears sa_flags on exec, so this likely does not reproduce there.

Repro

  1. probe.c (below) prints sigaction state for SIGUSR1/SIGSEGV. cc probe.c -o probe
    • run directly: sig 30 handler=0x0 flags=0x0
  2. execer2.c (below) installs an SA_SIGINFO|SA_RESTART handler for SIGUSR1, then execve()s its argument.
    cc execer2.c -o execer2
    • ./execer2 1 ./probe -> sig 30 handler=0x0 flags=0x42 (plain parent ./execer2 0 ./probe -> flags=0x0)
  3. Publish the trivial NativeAOT app (sources below, mode idle) with
    dotnet publish -c Release -r osx-arm64 -o out, then:
    ./execer2 1 out/sigtest idle &   # pid P
    sleep 2; kill -USR1 P            # the process dies with SIGSEGV (exit 139), pc=0
    
    With ./execer2 0 ... the process survives. Observed 3/3 vs 0/3. Without the external kill it also
    crashes on its own once a GC suspension occurs.

Expected

The runtime should not call a NULL handler: check sa_sigaction/sa_handler for SIG_DFL/SIG_IGN
before chaining, regardless of SA_SIGINFO.

Notes

  • Possibly also an OS issue (exec keeping SA_SIGINFO on a SIG_DFL disposition); worth a separate Apple Feedback.
  • Our workaround in the app: spawn with POSIX_SPAWN_SETSIGDEF (+ SETEXEC for in-place replacement) so the
    kernel resets the dispositions and flags.

Sources

probe.c
#include <signal.h>
#include <stdio.h>
int main(void){ int sigs[]={SIGUSR1,SIGUSR2,SIGINT,SIGCHLD,SIGSEGV};
 for(int i=0;i<5;i++){ struct sigaction o; sigaction(sigs[i],NULL,&o);
  printf("sig %2d handler=%p flags=0x%x\n",sigs[i],(void*)o.sa_handler,o.sa_flags);} return 0;}
execer2.c
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/* usage: execer2 <0|1> path args... ; 1 = install SA_SIGINFO handler on SIGUSR1 first (like a .NET parent), then execve */
static void h(int s, siginfo_t*i, void*c){}
extern char **environ;
int main(int argc,char**argv){ if(argv[1][0]=='1'){ struct sigaction a; memset(&a,0,sizeof a); a.sa_sigaction=h; a.sa_flags=SA_SIGINFO|SA_RESTART; sigaction(SIGUSR1,&a,NULL);}
 execve(argv[2],argv+2,environ); perror("execve"); return 1;}
sigtest.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <PublishAot>true</PublishAot>
    <Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings>
    <InvariantGlobalization>true</InvariantGlobalization>
  </PropertyGroup>
</Project>
Program.cs
using System.Diagnostics;
var stop = false;
var mode = args.Length > 0 ? args[0] : "spawn";
for (int t = 0; t < 4; t++)
    new Thread(() => { var l = new List<byte[]>(); while (!stop) { l.Add(new byte[1000]); if (l.Count > 20000) l.Clear(); if (l.Count % 5000 == 0) GC.Collect(0); } }) { IsBackground = true }.Start();
if (mode == "reg")
    new Thread(() => { while (!stop) { using var r = System.Runtime.InteropServices.PosixSignalRegistration.Create(System.Runtime.InteropServices.PosixSignal.SIGWINCH, c => { }); } }) { IsBackground = true }.Start();
if (mode == "pool")
    new Thread(() => { while (!stop) { var ts = new Task[64]; for (int i = 0; i < ts.Length; i++) ts[i] = Task.Run(() => { Thread.Sleep(20); var x = new byte[100000]; GC.KeepAlive(x); }); Task.WaitAll(ts); ThreadPool.SetMinThreads(2, 2); } }) { IsBackground = true }.Start();
var sw = Stopwatch.StartNew();
int n = 0;
while (sw.Elapsed.TotalSeconds < 60)
{
    if (mode == "spawn")
    {
        using var p = Process.Start(new ProcessStartInfo("/usr/bin/true") { UseShellExecute = false })!;
        p.WaitForExit();
    }
    else Thread.Sleep(1);
    n++;
}
stop = true;
Console.WriteLine($"OK {n}");

Activity

  1. dotnet-policy-service commented on Oct 1, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @agocke, @dotnet/ilc-contrib
    See info in area-owners.md if you want to be subscribed.

  2. MihuBot commented on Oct 1, 2026

    @MihuBot
  3. matt-gribben commented on Oct 1, 2026

    @matt-gribben
    Author

    Closing as a duplicate of #132581, which was fixed by #132900 and backported to release/10.0 in #133177 (milestone 10.0.13). Sorry for the noise — I missed it before filing. Confirming from our side: same root cause, same symptom (pc=0 in the activation handler after exec with a stale SA_SIGINFO), reproduced on macOS 27.0.1 with the 10.0.12 NativeAOT runtime pack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions