
Notas de ingeniería inversa y un PoC autocontenido para la carrera de caché de acceso del cliente NFS de macOS (CVE-2026-43687), con diff de desensamblado del kext y captura de la carrera con dtrace.
Ingeniería inversa independiente de la condición de carrera de la caché de acceso del cliente NFS de macOS (CVE-2026-43687), además de un PoC funcional que desencadena la carrera y la captura en vivo con dtrace.
El fallo es una lectura no sincronizada del puntero de la caché de acceso de nfsnode en _nfs_vnop_access. Un servidor NFSv3 malicioso puede provocar que la caché de acceso se reasigne mientras otro hilo la está leyendo, produciendo una divulgación de memoria del kernel que un servidor hostil puede influir. macOS 26.7 corrige esto insertando un lck_rw_t en nfsnode+0x158 y tomándolo en modo compartido alrededor de la lectura de la caché.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-43687 |
| Componente | com.apple.filesystems.nfs (_nfs_vnop_access) |
| Afecta a | macOS Tahoe 26.6 y anteriores, iOS 26.x y anteriores |
| Corregido en | macOS Tahoe 26.7, macOS Golden Gate 27, iOS 26.7, iOS 27 |
| Impacto según el aviso | "Conectarse a un servidor NFS malicioso puede divulgar memoria del kernel." |
| CVSS v3.1 | 6.5 (Medio) — AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N |
| Reportado por | R4mbb de KRsecurity y Peter Malone (según el aviso de Apple) |
El aviso de Apple para CVE-2026-43687 documenta el impacto y la versión del parche. No documenta el mecanismo técnico:
nfsnode sufre la carreraaccess(2) en lugar de stat(2)No se encontró ningún análisis técnico público en el momento de escribir esto. Este repositorio llena ese vacío con un análisis de ingeniería inversa independiente del kext de NFS entre 26.6 y 26.7, y un PoC funcional que reproduce la carrera en un objetivo en vivo.
Esto no es una reclamación de descubrimiento. El CVE fue reportado por R4mbb y Peter Malone y parcheado por Apple. La contribución aquí es el análisis técnico y la reproducción.
_nfs_vnop_access en el cliente NFS lee nfsnode+0x158 — el puntero al array de caché de acceso por UID — dos veces dentro de una sola llamada, sin mantener ningún 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
El escritor, _nfs_nget, reasigna el array con kalloc_data y almacena el resultado en +0x158 cada vez que el cliente ve un nuevo UID del servidor:
fffffe000b52100c bl 0xfffffe000b6a1dd8 ; kalloc
fffffe000b521010 str x0, [x22, #0x158] ; cache ptr
fffffe000b521018 str w20, [x22, #0x160] ; cache count
Si otro hilo alcanza _nfs_nget en el mismo nfsnode entre las dos cargas del lector, la segunda carga devuelve el nuevo puntero mientras que la primera lectura — ya usada para calcular un desplazamiento — se basó en el antiguo. La desreferencia posterior lee del heap del kernel liberado.
La corrección de 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
y en _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
Cambio en el diseño de la estructura:
| Desplazamiento | macOS 26.6 | macOS 26.7 |
|---|---|---|
+0x158 | puntero al array de caché | lck_rw_t |
+0x160 | contador de caché | (parte del lock) |
+0x168 | (otro) | puntero al array de caché |
+0x170 | (otro) | contador de caché |
_nfs_nget y _nfs_vnop_access entre los kexts de NFS 26.6 y 26.7 — ver docs/PATCH_DIFF.mdnfsnode+0x158 en 26.6lck_rw_t insertado en +0x158, puntero de caché movido a +0x168, lck_rw_lock_shared tomado alrededor de la lecturaaccess(2) entra en _nfs_vnop_access; stat(2) pasa por _nfs_getattr y nunca alcanza la función vulnerableVer docs/ANALYSIS.md para el análisis completo y docs/ARTIFACTS.md para direcciones y muestras de logs.
poc.sh — un PoC de un solo archivo y autocontenido:
nfsd de Apple para liberar el puerto 2049127.0.0.1 que rota el UID reportado en cada respuestanoacaccess(2) mediante test -r / test -wnfs_vnop_access, registrando cualquier llamada donde nfsnode+0x158 cambia a mitad de llamada_nfs_vnop_access observando el cambio del array durante una sola llamadaLa carrera es la precondición para la divulgación. Para convertir la carrera en una fuga real, un atacante necesitaría observar al lector usando el puntero obsoleto y propagar esos bytes a algún lugar donde pueda leerlos. En arm64e, el valor en nfsnode+0x158 está firmado con PAC y la clave por arranque no está disponible desde el espacio de usuario, por lo que dtrace por sí solo puede observar la carrera pero no puede decodificar el puntero. Ver la sección "Paths tested and ruled out" en docs/ANALYSIS.md.
El impacto demostrado es la carrera en sí misma — la ventana exacta que cierra la corrección del RW-lock de 26.7.
dtrace disponible (puede requerir ajuste de SIP en algunas instalaciones)python3, dscl, mountEl PoC se ejecuta completamente en el objetivo. El servidor NFS malicioso se enlaza a 127.0.0.1 y el montaje es sobre loopback. Esto mantiene el PoC autocontenido y reproducible sin configuración de red.
chmod +x poc.sh
sudo ./poc.sh
Ajuste opcional mediante variables de entorno:
sudo HAMMER_COUNT=30 RUN_SECONDS=600 ./poc.sh