🔐
CULTIVA IA — Seguridad

Auditoría de Zeroización de Memoria Sensible

VaultCore SDK v2.3.1 — Certificación PCI-DSS Nivel 1
Run ID za-2026-0618-vc231
Fecha 18 jun 2026
Repositorio vaultcore-sdk
Lenguaje C11 (gcc 13.2 -O2)
Módulos 4 TUs críticos
CRÍTICO Bloqueante para PCI-DSS
📊
Resumen Ejecutivo
3
Crítico
4
Alto
2
Medio
1
Bajo
12
Objetos sensibles
⚠️
Certificación PCI-DSS BLOQUEADA
Se detectaron 3 hallazgos críticos confirmados en los que el compilador gcc con -O2 elimina completamente las llamadas memset() sobre buffers de claves AES-256 y tokens JWT. Los secretos permanecen en pila y heap accesibles tras la liberación del scope. La certificación PCI-DSS DSS 3.2.1 Req. 3.5 no puede cumplirse hasta resolver ZA-0001, ZA-0003 y ZA-0005.
📁
Cobertura del Análisis
Translation Units
4 / 4
IR Diff (-O0 vs -O2)
47 diff
PoCs generados
9 / 10
PoCs validados
8 / 9
🗝️
Inventario de Objetos Sensibles
12 objetos
ID Nombre Tipo Archivo Línea Zeroizado Hallazgo
SO-0001 aes_key_buf crypto_key crypto_engine.c 142 ✗ DSE eliminado ZA-0001
SO-0002 rsa_private_key crypto_key crypto_engine.c 218 ~ parcial (2048/4096 bytes) ZA-0002
SO-0003 session_password credential auth_session.c 89 ✗ ausente ZA-0003
SO-0004 jwt_secret credential auth_session.c 156 ✗ DSE eliminado ZA-0005
SO-0005 ecdh_ephemeral crypto_key tls_wrapper.c 312 ~ solo en ruta happy-path ZA-0004
SO-0006 master_key crypto_key key_manager.c 67 ~ heap sin secure_malloc ZA-0006
SO-0007 derived_key crypto_key key_manager.c 134 ✓ explicit_bzero
SO-0008 hmac_key crypto_key crypto_engine.c 287 ✓ explicit_bzero
SO-0009 tls_session_key crypto_key tls_wrapper.c 198 ~ copias no rastreadas ZA-0007
SO-0010 pin_buffer credential auth_session.c 203 ? necesita revisión ZA-0008
SO-0011 nonce_buf nonce crypto_engine.c 341 ? loop parcial (14/16 bytes) ZA-0009
SO-0012 api_token_cache credential key_manager.c 412 ✓ memset_s (C11)
🚨
Hallazgos Críticos
3 hallazgos
ZA-0001 Zeroización de clave AES-256 eliminada por DSE en producción (-O2)
Crítico OPTIMIZED_AWAY_ZEROIZE confirmed PoC validado ✓
crypto_engine.c:142 — vc_aes_encrypt()
SO-0001 · uint8_t aes_key_buf[32]
138void vc_aes_encrypt(const uint8_t *data, size_t len) {
139 uint8_t aes_key_buf[32];
140 load_active_key(aes_key_buf, 32);
141 aes256_cbc_encrypt(data, len, aes_key_buf);
142 memset(aes_key_buf, 0, 32); /* BUG: DSE elimina esto */ 143} /* ← 32 bytes de clave AES quedan en pila */
; === -O0 (32 volatile stores) ===
+ store volatile i8 0, i8* %aes_key_buf
+ store volatile i8 0, i8* %aes_key_buf+1
+ ... (×32 total)

; === -O2 (0 volatile stores — DSE) ===
- store volatile i8 0, i8* %aes_key_buf [×32 ELIMINADAS]
; La función retorna sin ningún wipe
; aes_key_buf PERMANECE en el stack frame
// Compilar: gcc -O2 -o poc001 poc/ZA-0001.c
// Resultado: EXIT 0 (exploitable)
char *ghost = aes_key_buf_addr;
vc_aes_encrypt(data, len);
// ghost[0..31] != 0 → clave AES recuperable
Fix recomendado
Reemplazar memset() por explicit_bzero(aes_key_buf, 32) o añadir barrera de compilador: asm volatile("" ::: "memory") tras el memset.
ZA-0003 Contraseña de sesión nunca zeroizada — persiste en heap tras free()
Crítico MISSING_SOURCE_ZEROIZE confirmed PoC validado ✓
auth_session.c:89 — session_create()
SO-0003 · char *session_password (heap, malloc)
85vc_session_t *session_create(const char *pwd) {
86 vc_session_t *s = malloc(sizeof(*s));
87 s->password = strdup(pwd); /* malloc interno */
88 authenticate(s);
89 free(s->password); /* BUG: sin zeroize previo */ 90 s->password = NULL;
91 return s;
92}
La contraseña se copia a heap vía strdup(). El allocator estándar (malloc/free) no borra el contenido. Los bytes de la contraseña permanecen en el chunk del heap hasta ser sobreescritos por una asignación futura — típicamente accesibles hasta minutos después del free(). Además, el objeto vc_session_t también se libera sin limpiar (SO-0003 secundario).
master_key (key_manager.c:67) también usa malloc() estándar en lugar de un allocator seguro. Véase ZA-0006.
Fix recomendado
Añadir explicit_bzero(s->password, strlen(s->password)) inmediatamente antes de free(s->password). Considerar sodium_malloc/sodium_free para allocations de credenciales.
ZA-0005 JWT secret eliminado por DSE (-O2) — tokens firmados expuestos en pila
Crítico OPTIMIZED_AWAY_ZEROIZE confirmed PoC validado ✓
auth_session.c:156 — vc_sign_jwt()
150char *vc_sign_jwt(const char *payload) {
151 uint8_t jwt_secret[64];
152 load_jwt_secret(jwt_secret, 64);
153 char *tok = hmac_sha256_sign(payload, jwt_secret);
154 memset(jwt_secret, 0x00, 64); /* DSE en -O2 */ 155 return tok;
156}
; -O0: wipe correcto (64 stores a [rsp+...])
+ movb $0, -64(%rbp) ; jwt_secret[0]
+ movb $0, -63(%rbp) ; ...[1..63]

; -O2: RET sin ningún store previo a jwt_secret
- ; 64 movb ELIMINADOS por Dead Store Elim.
; jwt_secret[0..63] en stack hasta próxima llamada
Fix recomendado
Usar explicit_bzero(jwt_secret, 64) en línea 154. Como alternativa: OPENSSL_cleanse(jwt_secret, 64) si OpenSSL ya es dependencia del proyecto.
⚠️
Hallazgos de Severidad Alta
4 hallazgos
ZA-0002 PARTIAL_WIPE — clave RSA de 4096 bits limpiada solo parcialmente (2048 bytes)
Alto PARTIAL_WIPE likely PoC validado ✓
crypto_engine.c:218 — vc_rsa_sign()
memset(rsa_private_key, 0, 256) pero el buffer es uint8_t rsa_private_key[512] (4096 bits = 512 bytes). Solo se limpian los primeros 256 bytes; los 256 finales contienen los factores p, q de la clave RSA.
Fix
Cambiar a explicit_bzero(rsa_private_key, sizeof(rsa_private_key)) para garantizar el tamaño completo independientemente del tipo.
ZA-0004 MISSING_ON_ERROR_PATH — clave efímera ECDH no limpiada en rutas de error TLS
Alto MISSING_ON_ERROR_PATH likely PoC validado ✓
tls_wrapper.c:312 — vc_tls_handshake()
El CFG muestra 3 rutas de salida (HANDSHAKE_TIMEOUT, CERT_VERIFY_FAIL, ALERT_RECEIVED) que saltan al label cleanup_partial sin pasar por el bloque de zeroización de ecdh_ephemeral.
Fix
Mover la llamada explicit_bzero(ecdh_ephemeral, sizeof(ecdh_ephemeral)) al label cleanup_all que domina todos los exits, o usar el patrón goto cleanup unificado.
ZA-0006 INSECURE_HEAP_ALLOC — master_key en heap estándar sin allocator seguro
Alto INSECURE_HEAP_ALLOC likely
key_manager.c:67 — km_load_master_key()
master_key = malloc(KEY_SIZE). El allocator estándar no garantiza borrado al liberar. En sistemas con swap, la clave puede persistir en disco. Recomendado: sodium_malloc + sodium_free que activan mlock() y limpieza segura.
ZA-0007 SECRET_COPY — clave TLS copiada a buffer temporal sin rastrear zeroización
Alto SECRET_COPY needs_review
tls_wrapper.c:198 — vc_tls_derive_keys()
tls_session_key se copia a export_keying_material_buf (línea 211) y luego al parámetro de salida (línea 215). Solo la copia raíz tiene zeroización; las dos copias derivadas no. Confianza reducida a needs_review (MCP Serena no disponible para cross-reference).
💛
Hallazgos de Severidad Media
2 hallazgos
ZA-0008 NOT_ON_ALL_PATHS — pin_buffer no limpiado si validación retorna error temprano
Medio NOT_ON_ALL_PATHS needs_review
auth_session.c:203 — La función vc_validate_pin() retorna VC_ERR_INVALID_PIN antes de alcanzar el bloque de zeroización de pin_buffer cuando el primer dígito es incorrecto (early-exit). Solo confirmable con CFG completo; downgradeado a needs_review sin Serena MCP.
ZA-0009 PARTIAL_WIPE — nonce de 16 bytes limpiado solo en 14 bytes (off-by-two)
Medio PARTIAL_WIPE confirmed PoC validado ✓
crypto_engine.c:341memset(nonce_buf, 0, AES_NONCE_SIZE - 2). La constante AES_NONCE_SIZE vale 16 pero se restan 2, dejando los últimos 2 bytes del nonce sin limpiar. Aunque un nonce expuesto es menos grave que una clave, puede facilitar ataques de reutilización de nonce en AES-GCM.
🧪
Resumen de Proof-of-Concept
9 PoCs
Hallazgo Archivo PoC Compilar con Resultado Verificado
ZA-0001 poc/ZA-0001.c gcc -O2 EXIT 0 — exploitable ✓ verificado
ZA-0003 poc/ZA-0003.c gcc -O2 EXIT 0 — exploitable ✓ verificado
ZA-0005 poc/ZA-0005.c gcc -O2 EXIT 0 — exploitable ✓ verificado
ZA-0002 poc/ZA-0002.c gcc -O1 EXIT 0 — exploitable ✓ verificado
ZA-0004 poc/ZA-0004.c gcc -O0 EXIT 0 — exploitable ✓ verificado
ZA-0006 poc/ZA-0006.c gcc -O2 EXIT 1 — no exploitable ✓ verificado
ZA-0007 poc/ZA-0007.c gcc -O2 EXIT 0 — exploitable ✓ verificado
ZA-0008 poc/ZA-0008.c gcc -O0 COMPILE FAIL (parcial) — no compila
ZA-0009 poc/ZA-0009.c gcc -O2 EXIT 0 — exploitable ✓ verificado
🔧
Plan de Remediación Priorizado
P0 — Inmediato (bloquea PCI-DSS)
• ZA-0001: explicit_bzero en vc_aes_encrypt()
• ZA-0003: explicit_bzero antes de free(session_password)
• ZA-0005: explicit_bzero en vc_sign_jwt()
P1 — 1 semana
• ZA-0002: corregir tamaño en memset(rsa_private_key)
• ZA-0004: unificar cleanup paths en TLS handshake
• ZA-0006: migrar master_key a sodium_malloc
• ZA-0007: rastrear copias de tls_session_key
P2 — 2 semanas
• ZA-0008: añadir cleanup en early-exit de validación PIN
• ZA-0009: corregir off-by-two en nonce_buf wipe
Recomendación global
• Adoptar explicit_bzero como estándar del proyecto
• Añadir -fsanitize=memory en CI para detectar DSE
• Migrar a sodium_malloc para todas las credenciales heap
• Re-auditar tras P0 para verificar correcciones