When implementing a custom Mach exception handler, all recovery operations for SIGBUS/SIGSEGV except the first attempt will fail.

Recovery operations for signals SIGBUS/SIGSEGV fail when the process intercepts Mach exceptions. Only the first recovery attempt succeeds, and subsequent Signal notifications are no longer received within the process. I think this is a bug in XNU.

The test code main.c is:

If we comment out AddMachExceptionServer;, everything will return to normal.

#include <fcntl.h>
#include <mach/arm/kern_return.h>
#include <mach/kern_return.h>
#include <mach/mach.h>
#include <mach/message.h>
#include <mach/port.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_types/_mach_port_t.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

#pragma pack(4)
typedef struct {
  mach_msg_header_t header;
  mach_msg_body_t body;
  mach_msg_port_descriptor_t thread;
  mach_msg_port_descriptor_t task;
  NDR_record_t NDR;
  exception_type_t exception;
  mach_msg_type_number_t codeCount;
  integer_t code[2];
  /** Padding to avoid RCV_TOO_LARGE. */
  char padding[512];
} MachExceptionMessage;

typedef struct {
  mach_msg_header_t header;
  NDR_record_t NDR;
  kern_return_t returnCode;
} MachReplyMessage;
#pragma pack()

static jmp_buf jump_buffer;
static void sigbus_handler(int signo, siginfo_t *info, void *context) {
  printf("Caught SIGBUS at address: %p\n", info->si_addr);
  longjmp(jump_buffer, 1);
}
static void *RunExcServer(void *userdata) {
  kern_return_t kr = KERN_FAILURE;
  mach_port_t exception_port = MACH_PORT_NULL;
  kr = mach_port_allocate(mach_task_self_, MACH_PORT_RIGHT_RECEIVE,
                          &exception_port);
  if (kr != KERN_SUCCESS) {
    printf("mach_port_allocate: %s", mach_error_string(kr));
    return NULL;
  }

  kr = mach_port_insert_right(mach_task_self_, exception_port, exception_port,
                              MACH_MSG_TYPE_MAKE_SEND);
  if (kr != KERN_SUCCESS) {
    printf("mach_port_insert_right: %s", mach_error_string(kr));
    return NULL;
  }

  kr = task_set_exception_ports(
      mach_task_self_, EXC_MASK_ALL & ~(EXC_MASK_RPC_ALERT | EXC_MASK_GUARD),
      exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,THREAD_STATE_NONE);

if (kr != KERN_SUCCESS) {
    printf("task_set_exception_ports: %s", mach_error_string(kr));
    return NULL;
}
  MachExceptionMessage exceptionMessage = {{0}};
  MachReplyMessage replyMessage = {{0}};
  for (;;) {
    printf("Wating for message\n");
    // Wait for a message.
    kern_return_t kr = mach_msg(&exceptionMessage.header, MACH_RCV_MSG, 0,
                                sizeof(exceptionMessage), exception_port,
                                MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    if (kr == KERN_SUCCESS) {
      // Send a reply saying "I didn't handle this exception".
      replyMessage.header = exceptionMessage.header;
      replyMessage.NDR = exceptionMessage.NDR;
      replyMessage.returnCode = KERN_FAILURE;
      printf("Catch exception: %d codecnt:%d code[0]: %d, code[1]: %d\n",
             exceptionMessage.exception, exceptionMessage.codeCount,
             exceptionMessage.code[0], exceptionMessage.code[1]);

      mach_msg(&replyMessage.header, MACH_SEND_MSG, sizeof(replyMessage), 0,
               MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
    } else {
      printf("Mach error: %s\n", mach_error_string(kr));
    }
  }
  return NULL;
}

static bool AddMachExceptionServer(void) {
  int error;
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_t ptid = NULL;
  error = pthread_create(&ptid, &attr, &RunExcServer, NULL);
  if (error != 0) {
    pthread_attr_destroy(&attr);
    return false;
  }
  pthread_attr_destroy(&attr);
  return true;
}

int main(int argc, char *argv[]) {
  AddMachExceptionServer();
  struct sigaction sa;
  memset(&sa, 0, sizeof(sa));
  sa.sa_sigaction = sigbus_handler;
  sa.sa_flags = SA_SIGINFO;
// #if TARGET_OS_IPHONE
//   sigaction(SIGSEGV, &sa, NULL);
// #else
  sigaction(SIGBUS, &sa, NULL);
// #endif
  int i = 0;
  while (i++ < 3) {
    printf("\nProgram start %d\n", i);
    bzero(&jump_buffer, sizeof(jump_buffer));
    if (setjmp(jump_buffer) == 0) {
      int fd = open("tempfile", O_RDWR | O_CREAT | O_TRUNC, 0666);
      ftruncate(fd, 0);
      char *map =
          (char *)mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
      close(fd);
      unlink("tempfile");
      printf("About to write to mmap of size 0 — should trigger SIGBUS...\n");
      map[0] = 'X'; // ❌ triger a SIGBUS
      munmap(map, 4096);
    } else {
      printf("Recovered from SIGBUS via longjmp!\n");
    }
  }
  printf("_exit(0)\n");
  _exit(0);
  return 0;
}
Answered by DTS Engineer in 837135022
I think this is a bug in XNU.

If you’re goal is to report a bug, I recommend that you do it in Feedback Assistant. See Bug Reporting: How and Why? for detailed advice.

Please post your bug number.

IMPORTANT If this is a regression — that is, you had code that worked and it’s now failing on a new release of the OS — please make that clear in your bug.

Taking a step back, however, we should probably have a chat about what you’re doing with Mach exception handlers. Specifically, if you’re using them to implement your own crash reporter, then my advice is that you stop. See Implementing Your Own Crash Reporter for the details.

OTOH, if you’re doing something else then we should talk about what you’re doing. There are valid use cases for Mach exception handlers, but they are few and far betweeen.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I think this is a bug in XNU.

If you’re goal is to report a bug, I recommend that you do it in Feedback Assistant. See Bug Reporting: How and Why? for detailed advice.

Please post your bug number.

IMPORTANT If this is a regression — that is, you had code that worked and it’s now failing on a new release of the OS — please make that clear in your bug.

Taking a step back, however, we should probably have a chat about what you’re doing with Mach exception handlers. Specifically, if you’re using them to implement your own crash reporter, then my advice is that you stop. See Implementing Your Own Crash Reporter for the details.

OTOH, if you’re doing something else then we should talk about what you’re doing. There are valid use cases for Mach exception handlers, but they are few and far betweeen.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you. I have already reported a bug: FB17407067. This issue exists not only in macOS 14.6.1, 15.4.1, and iOS 18.3.1, but likely in more versions as well. We have implemented our own in-process crash reporter, which has caused the SIGBUS recovery mechanism of the V8 virtual machine to stop working properly, leading us to discover this issue.

When implementing a custom Mach exception handler, all recovery operations for SIGBUS/SIGSEGV except the first attempt will fail.
 
 
Q