
Notas de engenharia reversa e um PoC autocontido para a race do cache de acesso do cliente NFS do macOS (CVE-2026-43687), com diff de desmontagem do kext e captura da race via dtrace.
Engenharia reversa independente da corrida (race) no cache de acesso do cliente NFS do macOS (CVE-2026-43687), além de um PoC funcional que dispara a corrida e a captura ao vivo com dtrace.
O bug é uma leitura não sincronizada do ponteiro do cache de acesso do
nfsnode em _nfs_vnop_access. Um servidor NFSv3 malicioso pode forçar
o cache de acesso a realocar enquanto outra thread o está lendo,
produzindo uma divulgação de memória do kernel que um servidor hostil
pode influenciar. O macOS 26.7 corrige isso inserindo um lck_rw_t em
nfsnode+0x158 e adquirindo-o de forma compartilhada em torno da
leitura do cache.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-43687 |
| Componente | com.apple.filesystems.nfs (_nfs_vnop_access) |
| Afetado | macOS Tahoe 26.6 e anteriores, iOS 26.x e anteriores |
| Corrigido em | macOS Tahoe 26.7, macOS Golden Gate 27, iOS 26.7, iOS 27 |
| Impacto do aviso | "Conectar-se a um servidor NFS malicioso pode divulgar memória do kernel." |
| CVSS v3.1 | 6.5 (Médio) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N |
| Reportado por | R4mbb da KRsecurity e Peter Malone (conforme o aviso da Apple) |
O aviso da Apple para o CVE-2026-43687 documenta o impacto e a versão da correção. Ele não documenta o mecanismo técnico:
nfsnode sofre a corridaaccess(2) em vez de stat(2)Nenhum writeup técnico público foi encontrado no momento da escrita. Este repositório preenche essa lacuna com uma análise de engenharia reversa independente do kext NFS entre 26.6 e 26.7, e um PoC funcional que reproduz a corrida em um alvo ao vivo.
Isto não é uma reivindicação de descoberta. O CVE foi reportado por R4mbb e Peter Malone e corrigido pela Apple. A contribuição aqui é a análise técnica e a reprodução.
_nfs_vnop_access no cliente NFS lê nfsnode+0x158 — o ponteiro para
o array de cache de acesso por UID — duas vezes dentro de uma única
chamada, sem manter nenhum 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
O escritor, _nfs_nget, realoca o array com kalloc_data e armazena o
resultado em +0x158 sempre que o cliente vê um novo UID vindo do
servidor:
fffffe000b52100c bl 0xfffffe000b6a1dd8 ; kalloc
fffffe000b521010 str x0, [x22, #0x158] ; cache ptr
fffffe000b521018 str w20, [x22, #0x160] ; cache count
Se outra thread alcançar _nfs_nget no mesmo nfsnode entre as duas
cargas do leitor, a segunda carga retorna o novo ponteiro enquanto a
primeira leitura — já usada para calcular um offset — baseou-se no
antigo. A desreferência subsequente lê do heap do kernel liberado.
A correção do 26.7:
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
e em _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
Mudança no layout da struct:
| Offset | macOS 26.6 | macOS 26.7 |
|---|---|---|
+0x158 | ponteiro do array de cache | lck_rw_t |
+0x160 | contagem do cache | (parte do lock) |
+0x168 | (outro) | ponteiro do array de cache |
+0x170 | (outro) | contagem do cache |
_nfs_nget e _nfs_vnop_access entre os
kexts NFS 26.6 e 26.7 — veja
docs/PATCH_DIFF.mdnfsnode+0x158 no 26.6lck_rw_t inserido em +0x158, ponteiro
do cache movido para +0x168, lck_rw_lock_shared adquirido em torno da leituraaccess(2) entra em _nfs_vnop_access; stat(2)
passa por _nfs_getattr e nunca alcança a função vulnerávelVeja docs/ANALYSIS.md para o writeup completo e
docs/ARTIFACTS.md para endereços e amostras de log.
poc.sh — um PoC de arquivo único e autocontido:
nfsd da Apple para liberar a porta 2049127.0.0.1 que rotaciona o
UID reportado em cada respostanoacaccess(2) via test -r /
test -wnfs_vnop_access, registrando qualquer chamada
em que nfsnode+0x158 muda no meio da chamada_nfs_vnop_access observando o array
mudar durante uma única chamadaA corrida é a pré-condição para a divulgação. Para transformar a corrida
em um vazamento real, um atacante precisaria observar o leitor usando o
ponteiro obsoleto e propagar esses bytes para algum lugar que ele possa
ler. No arm64e, o valor em nfsnode+0x158 é assinado com PAC e a chave
por boot não está disponível a partir do userland, então o dtrace sozinho
pode observar a corrida mas não pode decodificar o ponteiro. Veja a seção
"Caminhos testados e descartados" em docs/ANALYSIS.md.
O impacto demonstrado é a própria corrida — exatamente a janela que a correção de RW-lock do 26.7 fecha.
dtrace disponível (pode exigir ajuste de SIP em algumas instalações)python3, dscl, mountO PoC roda inteiramente no alvo. O servidor NFS malicioso se vincula a
127.0.0.1 e o mount é via loopback. Isso mantém o PoC autocontido e
reproduzível sem configuração de rede.
chmod +x poc.sh
sudo ./poc.sh
Ajuste opcional via variáveis de ambiente:
sudo HAMMER_COUNT=30 RUN_SECONDS=600 ./poc.sh
HAMMER_COUNT define o número de threads hammer por usuário (usa
quaisquer contas nfsuserNNN existentes, criando-as conforme
necessário). RUN_SECONDS define a janela do dtrace.