# Async-unsafe calls in signal handlers

Signal or alarm handlers calling malloc, syslog, or touching unlocked shared state

_Area: Memory corruption and native code. Seen in 2 disclosed reports; one confirmed bounty: $480; latest 2024-07-01. Updated 2026-11-08._

## How it works
A signal handler runs asynchronously: it can interrupt the main flow at any instruction, including in the middle of the allocator or a logging call. If the handler itself calls a function that is not async-signal-safe (malloc/free, syslog, stdio) or touches shared state without a lock, the interrupted code and the handler operate on the same data at once. The assumption that breaks is "this handler runs in a clean context" — it does not. The result is heap corruption, deadlock, or a jump out of normal control flow that skips locking entirely [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md)[E3](https://bugbountylibrary.com/r/h1%3A1990421.md).

## Where it shows up
- SIGALRM/alarm() timeout paths, especially login or handshake grace timers [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md)[E3](https://bugbountylibrary.com/r/h1%3A1990421.md)
- Handlers calling syslog(), printf-family, malloc/free, or anything that may allocate [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md)
- siglongjmp()/sigsetjmp() used to abort work from a handler, leaving locks held or globals half-written [E3](https://bugbountylibrary.com/r/h1%3A1990421.md)
- Global buffers shared across threads with no mutex, reachable from the handler [E3](https://bugbountylibrary.com/r/h1%3A1990421.md)
- Timeout code that was "fixed" before and later refactored [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md)

## How to review
Build the handler's call graph and check every callee against the async-signal-safe list; syslog and malloc are disqualifying [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md). For each global the handler or its callees touch, confirm either a lock that the handler also takes (and cannot deadlock on) or that the data is thread-local/atomic [E3](https://bugbountylibrary.com/r/h1%3A1990421.md). For siglongjmp paths, verify no lock is held at the jump point and no partially updated state survives. The invariant: a handler may only call async-signal-safe functions and only touch state that cannot be concurrently mutated.

## How finders spotted it
- Traced the SIGALRM handler's call graph and found it reached syslog(), then malloc()/free() in glibc; recognized it as a regression of an older CVE reintroduced by later changes [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md)
- Read the synchronous resolver's timeout code and noticed the global buffer had no mutex while alarm/siglongjmp could jump out mid-update [E3](https://bugbountylibrary.com/r/h1%3A1990421.md)
- Reasoned about callback ordering (object callbacks fire before process callbacks) and asked how fast a handle could be opened to beat the protection callback [E1](https://bugbountylibrary.com/r/blog%3Agithub-com-kkent030315-van1338.md)

## Variants seen
- Handler calls logging that allocates, corrupting the heap [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md)
- Handler jumps out of normal flow via siglongjmp, bypassing locks on a shared global [E3](https://bugbountylibrary.com/r/h1%3A1990421.md)
- Protection applied in a later callback than the one that could observe the object, leaving a race window [E1](https://bugbountylibrary.com/r/blog%3Agithub-com-kkent030315-van1338.md)

## Why fixes fail
E2 shows an earlier fix silently regressed by later refactoring, so the same class returned years later [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md).

## Impact and severity
E2: unauthenticated root RCE on 32-bit glibc Linux, critical. E3: crashes and misbehavior in multithreaded apps, moderate. E1: a full-access handle escaping access-mask stripping, high. Impact rises with reachability from unauthenticated input [E2](https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md) and with the privilege of the racing component [E1](https://bugbountylibrary.com/r/blog%3Agithub-com-kkent030315-van1338.md).

## Seen in real software
Open-source packages and projects, and products, where this pattern was found. If the target runs one, compare the version it runs with the fix.
- **OpenSSH** (sshd) (version not stated): RCE vulnerability in OpenSSH: everything you need to know (CVE-2024-6387). Report: https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md
- **curl**:  CVE-2023-28320 - siglongjmp race condition (CVE-2023-28320). Report: https://bugbountylibrary.com/r/h1%3A1990421.md

## Evidence
- E1: https://bugbountylibrary.com/r/blog%3Agithub-com-kkent030315-van1338.md
- E2: https://bugbountylibrary.com/r/blog%3Awiz-io-blog-cve-2024-6387-critical-rce-openssh.md
- E3: https://bugbountylibrary.com/r/h1%3A1990421.md
