Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cve-2026-43687 — Reverse engineering notes and a self-contained PoC for the macOS NFS client access-cache race (CVE-2026-43687), with kext disassembly diff and dtrace race capture. | Kitploit
Tools/GitHubGitHub/jvidhan/cve-2026-43687
Memory ForensicsVulnerability AnalysisExploitationReverse EngineeringBinary AnalysisPapers & ResearchLearning & Education
GitHubjvidhan/cve-2026-43687

cve-2026-43687

Reverse engineering notes and a self-contained PoC for the macOS NFS client access-cache race (CVE-2026-43687), with kext disassembly diff and dtrace race capture.

View Repository
2822 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-43687 — Reverse engineering notes and reproduction

Independent reverse engineering of the macOS NFS client access-cache race (CVE-2026-43687), plus a working PoC that triggers the race and captures it live with dtrace.

The bug is an unsynchronized read of the nfsnode access-cache pointer in _nfs_vnop_access. A malicious NFSv3 server can drive the access cache to reallocate while another thread is reading it, producing a kernel memory disclosure that a hostile server can influence. macOS 26.7 fixes this by inserting an lck_rw_t at nfsnode+0x158 and taking it shared around the cache read.


CVE at a glance

FieldValue
CVECVE-2026-43687
Componentcom.apple.filesystems.nfs (_nfs_vnop_access)
AffectedmacOS Tahoe 26.6 and earlier, iOS 26.x and earlier
Patched inmacOS Tahoe 26.7, macOS Golden Gate 27, iOS 26.7, iOS 27
Advisory impact"Connecting to a malicious NFS server may disclose kernel memory."
CVSS v3.16.5 (Medium) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
Reported byR4mbb of KRsecurity and Peter Malone (per Apple's advisory)

Why this writeup exists

Apple's advisory for CVE-2026-43687 documents the impact and the patch version. It does not document the technical mechanism:

  • Which field in the nfsnode is raced
  • Where the second read happens
  • Why the fix inserts a lock at that specific offset
  • Why the PoC must use access(2) rather than stat(2)
  • Why some NFSv3 reply shapes silently break the mount even when the wire format is nearly correct

No public technical writeup was found at the time of writing. This repository fills that gap with an independent reverse engineering analysis of the NFS kext between 26.6 and 26.7, and a working PoC that reproduces the race on a live target.

This is not a discovery claim. The CVE was reported by R4mbb and Peter Malone and patched by Apple. The contribution here is the technical analysis and the reproduction.


Summary of the vulnerability

_nfs_vnop_access in the NFS client reads nfsnode+0x158 — the pointer to the per-UID access-cache array — twice inside a single call, without holding any lock:

; macOS 26.6, com.apple.filesystems.nfs
fffffe000b52def4   ldr  w8,  [x20, #0x160]     ; count
fffffe000b52def8   cmp  w23, w8
fffffe000b52defc   b.ge ...
fffffe000b52df00   ldr  x8,  [x20, #0x158]     ; cache ptr (read #1)
...
fffffe000b52df98   ldr  x8,  [x20, #0x158]     ; cache ptr (read #2)
fffffe000b52dfb0   ldr  w21, [x9]              ; dereference

The writer, _nfs_nget, reallocates the array with kalloc_data and stores the result at +0x158 whenever the client sees a new UID from the server:

fffffe000b52100c   bl   0xfffffe000b6a1dd8    ; kalloc
fffffe000b521010   str  x0,  [x22, #0x158]    ; cache ptr
fffffe000b521018   str  w20, [x22, #0x160]    ; cache count

If another thread reaches _nfs_nget on the same nfsnode between the reader's two loads, the second load returns the new pointer while the first read — already used to compute an offset — was based on the old one. The subsequent dereference reads from freed kernel heap.

The 26.7 fix:

fffffe000b9e3064   str  x0,  [x22, #0x168]    ; cache ptr moved
fffffe000b9e306c   str  w28, [x22, #0x170]    ; cache count moved
fffffe000b9e307c   add  x0,  x22, #0x158      ; lock slot
fffffe000b9e3084   bl   _lck_rw_init          ; init RW lock

and in _nfs_vnop_access:

fffffe000b9f0200   add  x0,  x20, #0x158
fffffe000b9f0204   bl   _lck_rw_lock_shared    ; take the lock
fffffe000b9f0208   ldr  x9,  [x20, #0x168]    ; cache pointer
...
fffffe000b9f0230   bl   _lck_rw_unlock_shared  ; release the lock

Struct layout change:

OffsetmacOS 26.6macOS 26.7
+0x158cache array pointerlck_rw_t
+0x160cache count(part of lock)
+0x168(other)cache array pointer
+0x170(other)cache count

What this repository contains

Reverse engineering

  • Disassembly diff of _nfs_nget and _nfs_vnop_access between the 26.6 and 26.7 NFS kexts — see docs/PATCH_DIFF.md
  • Identification of the raced field: nfsnode+0x158 in 26.6
  • Identification of the fix: lck_rw_t inserted at +0x158, cache pointer moved to +0x168, lck_rw_lock_shared taken around the read
  • Syscall analysis: access(2) enters _nfs_vnop_access; stat(2) goes through _nfs_getattr and never reaches the vulnerable function
  • Runtime confirmation of the race with dtrace, capturing the cache pointer changing mid-call

See docs/ANALYSIS.md for the full writeup and docs/ARTIFACTS.md for addresses and log samples.

Reproduction

  • poc.sh — a single-file, self-contained PoC:
    1. Stops Apple's nfsd to free port 2049
    2. Starts a malicious NFSv3 server on 127.0.0.1 that rotates the reported UID in every reply
    3. Mounts the export on a fresh mountpoint with noac
    4. Spawns a per-user hammer issuing access(2) via test -r / test -w
    5. Attaches a dtrace probe to nfs_vnop_access, recording any call where nfsnode+0x158 changes mid-call
    6. Prints a summary with the race count

What this PoC demonstrates

  • A malicious NFSv3 server rotating the UID it reports on every reply
  • The victim's kernel reallocating the access-cache array in response
  • The unsynchronized read in _nfs_vnop_access observing the array change during a single call
  • Live capture of that interleaving via dtrace

What this PoC does NOT demonstrate

  • A byte-level kernel memory leak visible to the attacker
  • Code execution, privilege escalation, or a shell on the victim

The race is the precondition for the disclosure. To turn the race into an actual leak, an attacker would need to observe the reader using the stale pointer and propagate those bytes somewhere they can read. On arm64e, the value at nfsnode+0x158 is PAC-signed and the per-boot key is not available from userland, so dtrace alone can observe the race but cannot decode the pointer. See the "Paths tested and ruled out" section in docs/ANALYSIS.md.

The demonstrated impact is the race itself — the exact window that the 26.7 RW-lock fix closes.


Requirements

Target (victim) host

  • macOS 26.6 or earlier (vulnerable kernel)
  • dtrace available (may require SIP adjustment on some installs)
  • python3, dscl, mount
  • Root

No separate attacker host needed

The PoC runs entirely on the target. The malicious NFS server binds to 127.0.0.1 and the mount is over loopback. This keeps the PoC self-contained and reproducible without network configuration.


Usage

chmod +x poc.sh
sudo ./poc.sh

Optional tuning via environment variables:

sudo HAMMER_COUNT=30 RUN_SECONDS=600 ./poc.sh

HAMMER_COUNT sets the number of per-user hammer threads (uses whatever nfsuserNNN accounts exist, creating them as needed). RUN_SECONDS sets the dtrace window.

Expected output

[*] ensuring nfsuser accounts exist (UID 201..240)
    nfsuser accounts available: 12
[*] starting evil NFS server on 127.0.0.1:2049
[*] mounting /Users/Shared/nfs_test (with noac)
    mount check: hello.txt statable
[*] spawning up to 12 per-user threads + 1 root loop
    started 12 user threads + 1 root loop
[*] running dtrace for 180s — looking for RACE lines
[*] stopping dtrace
[*] cleaning up

=================== SUMMARY ===================
RACE events caught: 16
Download Tool