--- /dev/null
+# Plano de Correção de Segurança - AdrOS 2026-05-25
+
+## Resumo Executivo
+
+Este documento estabelece um plano priorizado para corrigir as vulnerabilidades identificadas no relatório de reauditoria de segurança de 2026-05-25. O plano está organizado em 5 fases, priorizando isolamento de memória, bypasses de filesystem, parsers de boot/storage, interfaces multiusuário e correções userspace/libc.
+
+**Status atual:** 36 problemas identificados, 9 já corrigidos em sessões anteriores, 27 pendentes.
+
+**Correções confirmadas já feitas:**
+- K01: mmap(MAP_FIXED) validação de alinhamento/overflow/kernel crossing
+- K03: shm_at validação de endereço e busca de área livre
+- K04: AIO com bounce buffers
+- K05: send/recv/sendto/recvfrom com bounce buffers
+- A06: kill checa root ou UID/EUID compatível
+- K08: clone rejeita flags não suportadas, refcount de address space
+- A17: vdso.tick_hz inicializado com TIMER_HZ
+- K11: diskfs antigo removido, mount usa tipo explícito
+- K07: O_EXCL e O_DIRECTORY implementados
+- A16: pmm_free_page ganhou guarda contra double free
+
+---
+
+## Fase 1: Isolamento de Memória (Crítico)
+
+### 1.1 pmm_decref underflow (A16) - ALTA PRIORIDADE
+
+**Problema:** `pmm_decref` decrementa `frame_refcount[frame]` sem checar se o valor já é zero, permitindo underflow.
+
+**Arquivo:** `src/mm/pmm.c:243-254`
+
+**Código atual:**
+```c
+uint32_t pmm_decref(uintptr_t paddr) {
+ uint64_t frame = paddr / PAGE_SIZE;
+ if (frame == 0 || frame >= max_frames) return 0;
+ uintptr_t flags = spin_lock_irqsave(&pmm_lock);
+ uint32_t new_val = --frame_refcount[frame]; // <-- underflow possível
+ if (new_val == 0) {
+ bitmap_unset(frame);
+ used_memory -= PAGE_SIZE;
+ }
+ spin_unlock_irqrestore(&pmm_lock, flags);
+ return new_val;
+}
+```
+
+**Correção:**
+```c
+uint32_t pmm_decref(uintptr_t paddr) {
+ uint64_t frame = paddr / PAGE_SIZE;
+ if (frame == 0 || frame >= max_frames) return 0;
+ uintptr_t flags = spin_lock_irqsave(&pmm_lock);
+ if (frame_refcount[frame] == 0) {
+ // Underflow - bug de ownership, reportar e abortar
+ spin_unlock_irqrestore(&pmm_lock, flags);
+ kprintf("[PMM] ERROR: pmm_decref underflow on frame %llu\n", frame);
+ return 0;
+ }
+ uint32_t new_val = --frame_refcount[frame];
+ if (new_val == 0) {
+ bitmap_unset(frame);
+ used_memory -= PAGE_SIZE;
+ }
+ spin_unlock_irqrestore(&pmm_lock, flags);
+ return new_val;
+}
+```
+
+**Teste:** Adicionar teste em fulltest que tenta decref excessivo.
+
+---
+
+### 1.2 sysenter.S validação insuficiente (A02) - ALTA PRIORIDADE
+
+**Problema:** sysenter valida apenas `ECX < 0xC0000000`, mas não checa:
+- ECX não nulo
+- ECX + 8 ainda em userspace
+- Memória mapeada e legível
+- Leitura não cruza página inválida
+
+**Arquivo:** `src/arch/x86/sysenter.S:87-95`
+
+**Código atual:**
+```asm
+cmpl $0xC0000000, %ecx
+jae 1f
+mov 4(%ecx), %edx /* EDX = arg3 (was saved by user) */
+mov 8(%ecx), %ecx /* ECX = arg2 (was saved by user) */
+jmp 2f
+1: /* Invalid user ESP — zero args to prevent data leak */
+xor %edx, %edx
+xor %ecx, %ecx
+```
+
+**Correção:** Usar rotina segura de copy_from_user ou validar mais robustamente:
+```asm
+cmpl $0xC0000000, %ecx
+jae 1f
+cmpl $8, %ecx /* ECX must be at least 8 bytes from kernel boundary */
+jb 1f
+/* Adicionar check de página mapeada via page table walk seria ideal,
+ mas complexo em assembly. Alternativa: usar copy_from_user em C */
+mov 4(%ecx), %edx
+mov 8(%ecx), %ecx
+jmp 2f
+1:
+xor %edx, %edx
+xor %ecx, %ecx
+```
+
+**Nota:** Solução ideal seria mover a recuperação de args para C usando `copy_from_user`, mas isso requer mudança de ABI. Por enquanto, adicionar check de limite mínimo.
+
+---
+
+### 1.3 W^X/NX default em mmap/brk/SHM (A01) - ALTA PRIORIDADE
+
+**Problema:** VMM_FLAG_NX existe mas não é aplicado por default em:
+- syscall_mmap_impl: memória anônima mapeada sem NX
+- brk: heap mapeado sem NX
+- shm_at: SHM mapeado sem NX
+
+**Arquivos:** `src/kernel/syscall.c`, `src/arch/x86/vmm.c`, `src/kernel/shm.c`
+
+**Política proposta:** Todo mapeamento user sem PROT_EXEC explícito deve ter NX.
+
+**Correção em syscall_mmap_impl:**
+```c
+/* Ao calcular flags de PTE */
+uint32_t prot_flags = 0;
+if (prot & PROT_READ) prot_flags |= VMM_FLAG_PRESENT | VMM_FLAG_USER;
+if (prot & PROT_WRITE) prot_flags |= VMM_FLAG_WRITE;
+if (prot & PROT_EXEC) prot_flags |= VMM_FLAG_USER;
+else prot_flags |= VMM_FLAG_NX; /* NX por default */
+```
+
+**Correção em brk:**
+```c
+/* brk sempre mapeia heap como RW, nunca executável */
+uint32_t flags = VMM_FLAG_PRESENT | VMM_FLAG_WRITE | VMM_FLAG_USER | VMM_FLAG_NX;
+```
+
+**Correção em shm_at:**
+```c
+/* SHM por default RW sem execução */
+uint32_t flags = VMM_FLAG_PRESENT | VMM_FLAG_WRITE | VMM_FLAG_USER | VMM_FLAG_NX;
+```
+
+---
+
+### 1.4 mprotect por VMA (K02) - ALTA PRIORIDADE
+
+**Problema:** mprotect valida kernel crossing e aplica NX, mas tem fallback permissivo para endereços user acima de 0x0800000, permitindo alterar permissões de regiões não rastreadas (heap, stack, texto).
+
+**Arquivo:** `src/kernel/syscall.c`
+
+**Solução arquitetural:** Implementar tabela unificada de VMAs por processo cobrindo:
+- Heap
+- Stack
+- mmap regions
+- Loader segments
+- SHM
+
+**Plano de implementação:**
+1. Criar `struct vma { start, end, prot, flags }` em `include/vmm.h`
+2. Adicionar `vma_list` em `struct process`
+3. mprotect só opera em ranges completamente cobertos por VMAs do processo
+4. Adicionar helpers `vma_find`, `vma_add`, `vma_remove`
+5. Atualizar mmap, brk, loader para registrar VMAs
+
+**Nota:** Esta é uma mudança arquitetural significativa. Pode ser implementada em fases:
+- Fase 1a: Adicionar VMA tracking para mmap apenas
+- Fase 1b: Estender para heap/stack/loader
+- Fase 1c: Integrar com mprotect
+
+---
+
+## Fase 2: Bypasses de Filesystem (Crítico)
+
+### 2.1 pread/pwrite ignoram modo do fd (A03) - ALTA PRIORIDADE
+
+**Problema:** read/write agora validam O_WRONLY/O_RDONLY, mas pread/pwrite chamam diretamente `node->f_ops->read/write` sem validar modo nem mount read-only.
+
+**Arquivo:** `src/kernel/syscall.c:4335-4380`
+
+**Código atual (pread):**
+```c
+if (syscall_no == SYSCALL_PREAD) {
+ int fd = (int)sc_arg0(regs);
+ void* buf = (void*)sc_arg1(regs);
+ uint32_t count = sc_arg2(regs);
+ uint32_t offset = sc_arg3(regs);
+ struct file* f = fd_get(fd);
+ if (!f || !f->node) { sc_ret(regs) = (uint32_t)-EBADF; return; }
+ if (!(f->node->f_ops && f->node->f_ops->read)) { sc_ret(regs) = (uint32_t)-ESPIPE; return; }
+ if (count > 1024 * 1024) { sc_ret(regs) = (uint32_t)-EINVAL; return; }
+ /* ... chama f->node->f_ops->read diretamente ... */
+}
+```
+
+**Correção:** Adicionar validação de modo similar a read/write:
+```c
+if (syscall_no == SYSCALL_PREAD) {
+ int fd = (int)sc_arg0(regs);
+ void* buf = (void*)sc_arg1(regs);
+ uint32_t count = sc_arg2(regs);
+ uint32_t offset = sc_arg3(regs);
+ struct file* f = fd_get(fd);
+ if (!f || !f->node) { sc_ret(regs) = (uint32_t)-EBADF; return; }
+ if (!(f->node->f_ops && f->node->f_ops->read)) { sc_ret(regs) = (uint32_t)-ESPIPE; return; }
+
+ /* Validar modo do fd */
+ if ((f->flags & 3U) == O_WRONLY) {
+ sc_ret(regs) = (uint32_t)-EBADF; /* Cannot read from write-only */
+ return;
+ }
+
+ /* Validar mount read-only */
+ int rc = vfs_require_writable_path(NULL); /* Precisa de path, ver abaixo */
+ if (rc < 0) { sc_ret(regs) = (uint32_t)rc; return; }
+
+ if (count > 1024 * 1024) { sc_ret(regs) = (uint32_t)-EINVAL; return; }
+ /* ... */
+}
+```
+
+**Para pwrite:** Adicionar check O_RDONLY e vfs_require_writable_path.
+
+**Nota:** vfs_require_writable_path precisa de path. pread/pwrite não têm path. Solução:
+- Adicionar campo `mount_root` em `struct file` (já existe da fase VFS)
+- Usar `vfs_node_mount_flags(f->mount_root)` para checar MS_RDONLY
+
+---
+
+### 2.2 O_RDONLY|O_TRUNC pode truncar cedo demais (A04) - ALTA PRIORIDADE
+
+**Problema:** O caminho de open executa truncamento quando O_TRUNC está presente. A checagem usa `(flags & 3U) != 0`, mas O_RDONLY costuma ser zero. Assim, O_RDONLY|O_TRUNC pode passar.
+
+**Arquivo:** `src/kernel/syscall.c:2400-2406`
+
+**Código atual:**
+```c
+} else if ((flags & 0x200U) != 0U && node->flags == FS_FILE) {
+ /* O_TRUNC on existing file */
+ int rc = vfs_require_writable_path(path);
+ if (rc < 0) return rc;
+ if (node->i_ops && node->i_ops->truncate) {
+ node->i_ops->truncate(node, 0);
+ node->length = 0;
+```
+
+**Correção:** Exigir explicitamente O_WRONLY ou O_RDWR para O_TRUNC:
+```c
+} else if ((flags & 0x200U) != 0U && node->flags == FS_FILE) {
+ /* O_TRUNC on existing file - require write access */
+ if ((flags & 3U) == O_RDONLY) {
+ return -EACCES; /* Cannot truncate with O_RDONLY */
+ }
+ int rc = vfs_require_writable_path(path);
+ if (rc < 0) return rc;
+ if (node->i_ops && node->i_ops->truncate) {
+ node->i_ops->truncate(node, 0);
+ node->length = 0;
+```
+
+---
+
+### 2.3 Permissões VFS incompletas (A07, K06, K20) - ALTA PRIORIDADE
+
+**Problema:**
+- execve verifica MS_NOEXEC mas não exige bit executável no inode
+- Operações de diretório pai (unlink, rmdir, rename, link, mkdir, criação) não aplicam checagem completa de escrita/execução no pai
+- vfs_check_permission trata mode==0 como permissivo
+- Não há suporte a grupos suplementares/capabilities
+
+**Arquivos:** `src/kernel/fs.c`, `src/kernel/syscall.c`
+
+**Correções necessárias:**
+
+1. **execve:** Adicionar check de bit executável:
+```c
+/* Em syscall_execve_impl, após lookup */
+if (!(node->mode & 0111)) { /* Bit executável */
+ return -EACCES;
+}
+```
+
+2. **Operações de diretório pai:** Criar helper `vfs_check_parent_permission(path, perm)`:
+```c
+int vfs_check_parent_permission(const char* path, int perm) {
+ /* Extrair diretório pai */
+ char parent[256];
+ extract_parent_path(path, parent);
+
+ /* Lookup do pai */
+ fs_node_t* parent_node = vfs_lookup(parent);
+ if (!parent_node) return -ENOENT;
+
+ /* Checar permissão no pai */
+ return vfs_check_permission(parent_node, perm);
+}
+```
+
+Aplicar em:
+- unlink: checar w+x no pai
+- rmdir: checar w+x no pai
+- rename: checar w+x em ambos os pais
+- link: checar w+x no pai do novo link
+- mkdir: checar w+x no pai
+- create: checar w+x no pai
+
+3. **vfs_check_permission:** Remover fallback permissivo para mode==0:
+```c
+/* Se mode==0, tratar como 0000 (sem permissões) em vez de permissivo */
+if (mode == 0) {
+ /* Ainda checar ownership */
+ if (uid == current_process->uid) return 0;
+ return -EACCES;
+}
+```
+
+---
+
+### 2.4 access() ignora modo solicitado (A19) - MÉDIA PRIORIDADE
+
+**Problema:** SYSCALL_ACCESS copia o caminho, chama lookup e retorna sucesso se o caminho existir. O argumento mode não é aplicado.
+
+**Arquivo:** `src/kernel/syscall.c`
+
+**Correção:** Implementar F_OK, R_OK, W_OK, X_OK:
+```c
+if (syscall_no == SYSCALL_ACCESS) {
+ const char* path = (const char*)sc_arg0(regs);
+ int mode = (int)sc_arg1(regs);
+
+ char kpath[256];
+ int rc = copy_user_cstr(path, kpath, sizeof(kpath));
+ if (rc < 0) { sc_ret(regs) = (uint32_t)rc; return; }
+
+ fs_node_t* node = vfs_lookup(kpath);
+ if (!node) { sc_ret(regs) = (uint32_t)-ENOENT; return; }
+
+ /* F_OK: apenas existência */
+ if (mode == F_OK) {
+ sc_ret(regs) = 0;
+ return;
+ }
+
+ /* R_OK/W_OK/X_OK: checar permissões reais */
+ int perm = 0;
+ if (mode & R_OK) perm |= 0444;
+ if (mode & W_OK) perm |= 0222;
+ if (mode & X_OK) perm |= 0111;
+
+ rc = vfs_check_permission(node, perm);
+ sc_ret(regs) = (uint32_t)rc;
+ return;
+}
+```
+
+---
+
+### 2.5 O_NOFOLLOW ignorado (K21) - MÉDIA PRIORIDADE
+
+**Problema:** O_EXCL e O_DIRECTORY foram implementados, mas O_NOFOLLOW ainda não é efetivo. vfs_lookup_depth segue symlink automaticamente.
+
+**Arquivos:** `src/kernel/syscall.c`, `src/kernel/fs.c`
+
+**Correção:**
+1. Adicionar flag de lookup em `vfs_lookup_depth`:
+```c
+/* Adicionar parâmetro int flags */
+fs_node_t* vfs_lookup_depth(const char* path, int flags);
+#define LOOKUP_FOLLOW 0x01
+#define LOOKUP_NOFOLLOW 0x02
+```
+
+2. Em syscall_open_impl, propagar O_NOFOLLOW:
+```c
+int lookup_flags = LOOKUP_FOLLOW;
+if (flags & O_NOFOLLOW) {
+ lookup_flags = LOOKUP_NOFOLLOW;
+}
+fs_node_t* node = vfs_lookup_depth(kpath, lookup_flags);
+```
+
+3. Em vfs_lookup_depth, recusar symlink no componente final quando NOFOLLOW:
+```c
+/* Se último componente é symlink e NOFOLLOW está setado */
+if ((flags & LOOKUP_NOFOLLOW) && (last_node->flags & FS_SYMLINK)) {
+ return -ELOOP; /* ou -EMLINK */
+}
+```
+
+---
+
+### 2.6 truncate/ftruncate não atualizam storage real (A14) - ALTA PRIORIDADE
+
+**Problema:** open(O_TRUNC) chama i_ops->truncate quando disponível, mas TRUNCATE/FTRUNCATE syscalls alteram diretamente node->length sem chamar o backend.
+
+**Arquivo:** `src/kernel/syscall.c`
+
+**Correção:** Rotear todas as formas de truncamento por vfs_truncate/i_ops->truncate:
+```c
+/* Criar vfs_truncate que chama i_ops->truncate quando disponível */
+int vfs_truncate(fs_node_t* node, off_t length) {
+ if (node->i_ops && node->i_ops->truncate) {
+ return node->i_ops->truncate(node, length);
+ }
+ /* Fallback para FS que não suportam truncamento real */
+ node->length = length;
+ return 0;
+}
+
+/* Em SYSCALL_TRUNCATE e SYSCALL_FTRUNCATE, usar vfs_truncate */
+```
+
+---
+
+## Fase 3: Parsers de Boot/Storage (Crítico)
+
+### 3.1 initrd/LZ4/TAR parsers inseguros (A05) - ALTA PRIORIDADE
+
+**Problema:** Parser acessa bytes do buffer antes de validar tamanho mínimo. Leitura de magic sem garantir size >= 4, caminho LZ4 consulta header sem validar tamanho, loop TAR itera sem limite forte.
+
+**Arquivo:** `src/drivers/initrd.c`
+
+**Correção:** Transformar parser em máquina de estados limitada por (base, size):
+```c
+/* Adicionar validação de tamanho mínimo antes de cada acesso */
+if (size < 4) return -EINVAL;
+uint32_t magic = *(uint32_t*)raw;
+if (magic != EXPECTED_MAGIC) return -EINVAL;
+
+/* Para TAR: validar rec_len, name_len, overflow antes de avançar */
+while (location < size) {
+ if (location + 512 > size) break; /* Limite de bloco */
+
+ /* Validar rec_len */
+ uint32_t rec_len = parse_octal(header + 124, 12);
+ if (rec_len < 8 || rec_len > 4096) return -EINVAL; /* rec_len deve ser >= 8 */
+ if (location + rec_len > size) return -EINVAL; /* Overflow */
+
+ /* Validar name_len */
+ uint32_t name_len = strnlen(header, 100);
+ if (name_len >= rec_len - 8) return -EINVAL; /* name_len deve caber em rec_len - 8 */
+
+ location += rec_len;
+}
+```
+
+---
+
+### 3.2 Multiboot2 parsers sem validação de limites (A15) - ALTA PRIORIDADE
+
+**Problema:** Código copia estrutura Multiboot2 mas itera tags até END sem checar que cada tag permanece dentro de total_size/buffer copiado e sem validar tamanho mínimo/alinhamento.
+
+**Arquivos:** `src/arch/x86/arch_early_setup.c`, `src/arch/x86/pmm_boot.c`
+
+**Correção:**
+```c
+/* Validar total_size */
+if (total_size > buffer_size) return -EINVAL;
+
+uint32_t cursor = 8; /* Skip header */
+while (cursor + 8 <= base + total_size) {
+ multiboot_tag_t* tag = (multiboot_tag_t*)(base + cursor);
+
+ /* Validar tamanho mínimo e alinhamento */
+ if (tag->size < 8) return -EINVAL;
+ if (tag->size % 8 != 0) return -EINVAL; /* Deve ser alinhado */
+
+ /* Validar limite */
+ if (cursor + tag->size > base + total_size) return -EINVAL;
+
+ if (tag->type == MULTIBOOT_TAG_TYPE_END) break;
+
+ cursor += tag->size;
+}
+```
+
+---
+
+### 3.3 ext2 parsing vulnerável a mídia malformada (F01) - ALTA PRIORIDADE
+
+**Problema:** Loops de diretório aceitam entradas sem validar completamente: rec_len deve ser >= 8, alinhado, caber no bloco; name_len deve caber em rec_len - 8; superbloco/GDT precisam de validação de overflow.
+
+**Arquivo:** `src/kernel/ext2.c`
+
+**Correção:** Adicionar validadores estritos:
+```c
+/* Em ext2_readdir */
+while (offset < block_size) {
+ ext2_dirent_t* dirent = (ext2_dirent_t*)(block + offset);
+
+ /* Validar rec_len */
+ if (dirent->rec_len < 8) return -EIO;
+ if (dirent->rec_len % 4 != 0) return -EIO; /* Deve ser alinhado */
+ if (offset + dirent->rec_len > block_size) return -EIO;
+
+ /* Validar name_len */
+ if (dirent->name_len >= dirent->rec_len - 8) return -EIO;
+
+ offset += dirent->rec_len;
+}
+
+/* Validar superbloco */
+if (superblock->magic != EXT2_MAGIC) return -EINVAL;
+if (superblock->inode_count == 0) return -EINVAL;
+if (superblock->block_count == 0) return -EINVAL;
+/* Validar overflow de grupos */
+if (superblock->blocks_per_group == 0) return -EINVAL;
+uint32_t group_count = (superblock->block_count + superblock->blocks_per_group - 1) / superblock->blocks_per_group;
+if (group_count > 1024) return -EINVAL; /* Limite razoável */
+```
+
+---
+
+## Fase 4: Interfaces Multiusuário (Alta Prioridade)
+
+### 4.1 /proc vaza informações e não segura lifetime (K12, K13, K23) - ALTA PRIORIDADE
+
+**Problema:** /proc/dmesg, /proc/cmdline, /proc/<pid>/status, /proc/<pid>/maps, /proc/<pid>/cmdline não aplicam controle de acesso por UID/EUID. Leituras por PID buscam process_t sem pin/refcount.
+
+**Arquivo:** `src/kernel/procfs.c`
+
+**Correção:**
+1. **Política hidepid/ptrace-like:** Exigir mesmo UID ou root:
+```c
+/* Adicionar helper */
+int proc_check_access(struct process* target) {
+ if (!current_process) return -EACCES;
+ if (current_process->uid == 0) return 0; /* root */
+ if (current_process->uid == target->uid) return 0;
+ return -EACCES;
+}
+```
+
+2. **Pin/refcount durante leitura:** Usar `process_ref`/`process_unref`:
+```c
+/* Em proc_pid_status_read */
+struct process* p = process_find(pid);
+if (!p) return -ENOENT;
+if (proc_check_access(p) < 0) return -EACCES;
+process_ref(p); /* Pin */
+/* ... ler dados ... */
+process_unref(p); /* Unpin */
+```
+
+3. **Redigir endereços em /proc/<pid>/maps:** Não implementar maps ou redigir endereços como zeros.
+
+---
+
+### 4.2 SHM sem modelo de permissão e RW sem NX (K14, K24) - ALTA PRIORIDADE
+
+**Problema:** Segmentos SHM não carregam uid/gid/mode nem checagem de permissão. Mapeamento usa flags RW user sem NX.
+
+**Arquivo:** `src/kernel/shm.c`
+
+**Correção:**
+1. **Adicionar metadados de permissão:**
+```c
+struct shm_segment {
+ /* ... campos existentes ... */
+ uid_t uid;
+ gid_t gid;
+ mode_t mode;
+};
+```
+
+2. **Checagem de permissão em shm_at:**
+```c
+/* Em shm_at */
+if (shm->uid != current_process->uid && current_process->uid != 0) {
+ return -EACCES;
+}
+```
+
+3. **NX por default em shm_at:**
+```c
+uint32_t flags = VMM_FLAG_PRESENT | VMM_FLAG_WRITE | VMM_FLAG_USER | VMM_FLAG_NX;
+```
+
+4. **Suporte a attach read-only:** Adicionar flag SHM_RDONLY em shm_at.
+
+---
+
+### 4.3 Raw sockets sem privilégio (K15) - ALTA PRIORIDADE
+
+**Problema:** ksocket_create aceita SOCK_RAW e cria PCB raw no lwIP sem checar root/capacidade.
+
+**Arquivos:** `src/kernel/socket.c`, `src/kernel/syscall.c`
+
+**Correção:** Exigir root para SOCK_RAW:
+```c
+/* Em ksocket_create */
+if (type == SOCK_RAW && current_process->uid != 0) {
+ return -EPERM;
+}
+```
+
+---
+
+### 4.4 Futex chaveado somente por endereço virtual (K17) - ALTA PRIORIDADE
+
+**Problema:** Tabela de futex armazena addr e processo, wake compara endereço virtual. Não há chave por address space, permitindo interferência entre processos com mesmo VA.
+
+**Arquivo:** `src/kernel/syscall.c`
+
+**Correção:** Chavear futex por (mm, uaddr):
+```c
+struct futex_waiter {
+ struct address_space* mm; /* Adicionar address space */
+ void* uaddr;
+ struct process* process;
+ /* ... */
+};
+
+/* Em FUTEX_WAIT, usar (mm, uaddr) como chave */
+/* Em FUTEX_WAKE, match por (mm, uaddr) */
+```
+
+Adicionar lock robusto para lista global de waiters.
+
+---
+
+### 4.5 dlopen global por processo (K22) - ALTA PRIORIDADE
+
+**Problema:** dl_table[DLOPEN_MAX_LIBS] é global no kernel. Handles e símbolos não são isolados por processo.
+
+**Arquivo:** `src/kernel/syscall.c`
+
+**Correção:** Mover tabela para process_t/address space:
+```c
+/* Em struct process ou struct address_space */
+struct dl_handle {
+ char name[64];
+ void* base;
+ uint32_t size;
+ /* ... */
+};
+struct dl_handle dl_handles[DLOPEN_MAX_LIBS];
+int dl_handle_count;
+
+/* Em dlopen, usar tabela do processo atual */
+/* Em exit, liberar todos os handles do processo */
+```
+
+---
+
+## Fase 5: Correções Userspace/Lib (Média Prioridade)
+
+### 5.1 Wrappers variadicas de libc com UB (A17) - MÉDIA PRIORIDADE
+
+**Problema:** open, openat e fcntl leem argumentos opcionais de variadic mesmo quando a chamada não exige esse argumento.
+
+**Arquivos:** `user/ulibc/src/unistd.c`, `newlib/libgloss/adros/posix_stubs.c`
+
+**Correção:**
+```c
+/* Em open */
+int open(const char* pathname, int flags, ...) {
+ mode_t mode = 0;
+ if (flags & O_CREAT) {
+ va_list ap;
+ va_start(ap, flags);
+ mode = va_arg(ap, mode_t);
+ va_end(ap);
+ }
+ return _syscall3(SYS_OPEN, pathname, flags, mode);
+}
+```
+
+Aplicar padrão similar para openat e fcntl.
+
+---
+
+### 5.2 execl/execlp varargs incorreto (U04) - MÉDIA PRIORIDADE
+
+**Problema:** Funções percorrem argumentos variadicos por aritimética sobre &arg, em vez de usar va_list.
+
+**Arquivo:** `user/ulibc/src/execvp.c`
+
+**Correção:** Reimplementar com va_start, va_arg:
+```c
+int execl(const char* path, const char* arg0, ...) {
+ va_list ap;
+ va_start(ap, arg0);
+
+ /* Contar argumentos */
+ int argc = 1;
+ const char* arg;
+ while ((arg = va_arg(ap, const char*)) != NULL) {
+ argc++;
+ }
+ va_end(ap);
+
+ /* Construir argv */
+ char* argv[64];
+ va_start(ap, arg0);
+ argv[0] = (char*)arg0;
+ for (int i = 1; i < argc; i++) {
+ argv[i] = va_arg(ap, char*);
+ }
+ argv[argc] = NULL;
+ va_end(ap);
+
+ return execve(path, argv, environ);
+}
+```
+
+---
+
+### 5.3 scanf %s sem limite (U02) - MÉDIA PRIORIDADE
+
+**Problema:** Implementações de scanf copiam strings sem exigir largura de campo nem conhecer tamanho do destino.
+
+**Arquivo:** `user/ulibc/src/stdio.c`
+
+**Correção:** Respeitar larguras de campo ou fornecer variantes seguras:
+```c
+/* Se %s sem largura, usar limite padrão (ex: 255) */
+if (*fmt == 's' && width == 0) {
+ width = 255;
+}
+```
+
+Idealmente fornecer snprintf_s ou similar.
+
+---
+
+### 5.4 Shell substituição de comando malformada (A18) - BAIXA PRIORIDADE
+
+**Problema:** expand_vars monta comando adicionando '(' antes do texto, mas não adiciona ')' no final.
+
+**Arquivo:** `user/cmds/sh/sh.c`
+
+**Correção:** Corrigir montagem da string:
+```c
+/* Adicionar ')' no final */
+cmd[cmdlen] = ')';
+cmd[cmdlen + 1] = '\0';
+```
+
+Ou implementar parser próprio para substituição sem reescrever para forma parcial.
+
+---
+
+### 5.5 mkstemp/tmpfile/tmpnam previsíveis (U01) - MÉDIA PRIORIDADE
+
+**Problema:** mkstemp gera nomes com PID/counter previsíveis. tmpfile/tmpnam seguem inseguros.
+
+**Arquivos:** `user/ulibc/src/stdlib.c`, `user/ulibc/src/stdio.c`
+
+**Correção:**
+1. Alimentar mkstemp com CSPRNG quando disponível
+2. Sempre usar O_CREAT|O_EXCL
+3. Passar modo explicitamente
+4. tmpfile criar+unlink de forma atômica
+
+---
+
+### 5.6 posix_spawn quebrado (A13) - MÉDIA PRIORIDADE
+
+**Problema:** Kernel chama fork_impl e testa child_pid == 0 no mesmo fluxo. Wrapper userspace sobrescreve PID com zero.
+
+**Arquivos:** `src/kernel/syscall.c`, `user/ulibc/src/spawn.c`
+
+**Correção:** Implementar posix_spawn como syscall atômica no kernel, ou fazer wrapper userspace baseado em fork + execve + _exit preservando PID corretamente.
+
+---
+
+## Itens Deferidos (Arquiteturais/Fora de Escopo)
+
+### A08: Overlayfs semântica incompleta
+**Razão:** Requer redesign completo de coherência e lifetime. Fora de escopo de hardening imediato.
+
+### A09: Tmpfs sem limites/locking
+**Razão:** Requer arquitetura de quotas e locks por filesystem. Pode ser endereçado em fase separada.
+
+### A10: PRNGs determinísticos
+**Razão:** Requer fonte de entropia de hardware. CSPRNG é projeto separado.
+
+### A11: Rollback com array fixo
+**Razão:** Limitação aceitável para hobby OS. Solução ideal requer rollback por caminhada de range.
+
+### A12: VA fixa e colisão virtio_blk
+**Razão:** Requer alocador de VA kernel para mapeamentos temporários. Projeto arquitetural significativo.
+
+### K16: Truncamento silencioso de caminhos
+**Razão:** Alguns caminhos já retornam ENAMETOOLONG. Completar consistência é trabalho de polimento.
+
+### K34: sendmsg/recvmsg passam iov de usuário
+**Razão:** Requer refatoração significativa de camada de socket. Pode ser endereçado em fase de network.
+
+### K35: copy_to_user falhas ignoradas
+**Razão:** Correção simples mas requer revisão de todos os caminhos de socket.
+
+### K36: rumpuser_malloc alinhado
+**Razão:** Código rump é isolado e pouco usado. Correção de baixo impacto.
+
+### F02: fb0_mmap ignora prot
+**Razão:** Framebuffer é dispositivo especial. Baixo risco em single-user OS.
+
+### U03: getlogin/who fixos
+**Razão:** Aceitável para single-user OS sem banco de usuários/sessões.
+
+---
+
+## Ordem de Implementação Sugerida
+
+### Round 1 (Críticos de memória - 1-2 dias)
+1. pmm_decref underflow (A16)
+2. sysenter.S validação (A02)
+3. W^X/NX default em mmap/brk/SHM (A01)
+
+### Round 2 (Bypasses filesystem - 2-3 dias)
+4. pread/pwrite validação modo (A03)
+5. O_RDONLY|O_TRUNC (A04)
+6. truncate/ftruncate atualizam storage (A14)
+7. Permissões VFS básicas (execve bit X, operações pai) (A07)
+
+### Round 3 (Parsers - 1-2 dias)
+8. initrd/LZ4/TAR validação (A05)
+9. Multiboot2 validação (A15)
+10. ext2 validação estrita (F01)
+
+### Round 4 (Multiuser - 2-3 dias)
+11. /proc acesso/leak (K12, K13, K23)
+12. SHM permissões/NX (K14, K24)
+13. raw sockets privilégio (K15)
+14. futex chave por (mm, uaddr) (K17)
+15. dlopen global por processo (K22)
+
+### Round 5 (Userspace - 1-2 dias)
+16. varargs open/openat/fcntl (A17)
+17. execl/execlp varargs (U04)
+18. scanf %s limite (U02)
+19. mkstemp/tmpfile/tmpnam (U01)
+20. posix_spawn (A13)
+
+### Round 6 (Polimento - 1 dia)
+21. access() implementa modo (A19)
+22. O_NOFOLLOW (K21)
+23. shell substituição comando (A18)
+24. copy_to_user falhas em socket (K35)
+
+**Total estimado:** 10-13 dias de trabalho focado.
+
+---
+
+## Critérios de Sucesso
+
+- Todos os testes existentes continuam passando (smoke, battery, host)
+- Zero regressões em funcionalidade
+- cppcheck limpo nos arquivos modificados
+- Cada correção tem teste correspondente em fulltest quando aplicável
+- Documentação atualizada com mudanças arquiteturais (VMA, SHM permissões, etc.)
+
+---
+
+## Referências
+
+- Relatório de auditoria original: docs/AUDIT_REPORT.md
+- Histórico de correções: commits 91377cf, c3476f0, 3aa5178, 36476bc, 1f46f9e, bc8a27b
+- Plano VFS/Mount: docs/POSIX_ROADMAP.md