A null on the way back

BSD traceroute on macOS crashes when ECN mode meets the ICMP protocol path. A disclosure note on NC-01 and why Apple got this one exactly right.


My father typed in BASIC programmes from magazines on his Commodore PET and sometimes they didn't run. Not because of typos — though there were those too — but because the listings themselves had errors. The magazine, the typesetter, the author: any of them could introduce a missing variable, an uninitialised value, a reference to something that wasn't there. The machine would dutifully attempt to dereference it and stop. The null pointer problem is not new. It predates object-oriented programming, TLS, and macOS by decades. It is as old as the idea of a pointer itself.

What surprised me when I found NC-01 was not that the bug existed. It was that it had survived this long in a tool that ships on every Mac, largely unchanged from its BSD lineage. traceroute is one of those utilities that nobody rewrites, because it works well enough, most of the time, for the combinations of flags that people actually use.

The finding

Technical Finding — NC-01

Component: /usr/sbin/traceroute, BSD traceroute, macOS 15 / 26

CWE: CWE-476 — NULL Pointer Dereference

Trigger: traceroute -E -P icmp -e <target>

Effect: The traceroute process crashes. No cross-process effect. No privilege escalation. No data exposure.

Status: Disclosed to Apple. Apple responded: “Local DoS by user themselves; not in scope.” Closure accepted.

The -E flag enables ECN (Explicit Congestion Notification) mode. The -P icmp flag selects ICMP as the probe protocol rather than the default UDP. These flags are individually supported. Together, they reach a code path in the ICMP Echo Reply handler that attempts to process ECN-related fields which were never allocated — because the ICMP path does not initialise them in the same way as the UDP path. The process dereferences a NULL pointer and dies.

The crash is deterministic. Run the command, receive a reply from the target, crash. Every time. On a loopback address, the reply arrives before any packet leaves the machine and the crash occurs immediately.

Proof of concept

nc01-traceroute-null-deref.sh Run on your own machine only
#!/bin/bash
# traceroute NULL dereference — NC-01
# Crashes the traceroute process only. No privilege escalation.
# No cross-process effect. Affects the user running the command.
# Apple confirmed: "local DoS by user themselves; not in scope"

echo "=== Reproducing traceroute NULL dereference (NC-01) ==="
echo "Target: 127.0.0.1 (loopback)"
echo "The crash occurs when the ICMP Echo Reply is received"
echo "and the ECN fields that were not initialised are dereferenced."
echo ""

traceroute -E -P icmp -e 127.0.0.1 2>&1
EXIT=$?

echo ""
echo "Exit code: $EXIT"
if [ "$EXIT" -ne 0 ]; then
  echo "Non-zero exit: crash occurred as expected."
fi
echo ""
echo "This crashes only the traceroute process you are running."
echo "No other process is affected."
echo "Apple's 'not in scope' assessment is correct."

The investigation

The BSD traceroute source has a well-understood structure: a probe loop that sends packets and reads replies, with separate handling for different protocol modes. The UDP path builds a full packet context including fields that will be examined on reply. The ICMP path is handled differently — it uses raw sockets rather than the UDP send-and-receive mechanism — and several of the ancillary fields that ECN mode expects to read on the reply are never populated for ICMP probes.

The specific failure is in the reply-processing logic. When -E is set, the reply handler reaches for ECN-related state that the ICMP initialisation path never set up. The pointer is NULL. The dereference is fatal to the process.

This is a latent initialisation gap rather than a logic error. The two features — ECN mode and ICMP probes — were presumably added at different times, and the intersection was never tested. Nothing in the code looks like deliberate unsafe behaviour; it looks like two features that never met in a test suite.

Apple's response and honest assessment

Apple's response was clear and prompt: this is a local denial of service caused by the user themselves. The user running the command crashes their own traceroute process. There is no privilege escalation, no cross-process effect, no data exposed that was not already accessible to the running user.

Opinion

Apple is correct. I am glad they said so plainly rather than filing it without comment. There is a version of this finding that could be framed more dramatically — automated monitoring tools sometimes run traceroute with elevated privileges, and a repeatable NULL dereference in a privileged monitoring context would be a different matter — but that version requires a specific deployment scenario that I cannot demonstrate on standard macOS. On any standard system, this is exactly what Apple said: a user crashes a tool they are running themselves. That is not a security issue under any reasonable threat model. The code-quality observation stands — the initialisation gap is real, has survived multiple macOS releases, and is worth a patch — but Apple's decision not to treat it as a security finding is defensible. The threshold is reasonable.

The bug is a small, clean illustration of what happens when two independent features share a code path that neither expected the other to reach. My father's PET would have understood the outcome perfectly: you asked for something that wasn't there, and you got nothing back.


Disclosure note

This finding was submitted to Apple through the Apple Security Research Framework. Apple confirmed closure with the assessment that this is a local user DoS, not in scope for the security bounty programme. That assessment is accepted. The PoC above requires your own machine and crashes only the traceroute process you are running. It does not affect any other process or any remote system.

Are you there? The ICMP echo went out. Something came back. The process was not ready for it.