Viewing: SECURITY_FIX_PLAN_2026-05-25.md
📄 SECURITY_FIX_PLAN_2026-05-25.md (Read Only) ⬅ To go back
# 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