From: Tulio A M Mendes Date: Tue, 26 May 2026 04:47:51 +0000 (-0300) Subject: docs: add security fix plan for 2026-05-26 reanalysis X-Git-Url: https://projects.tadryanom.me/?a=commitdiff_plain;h=50ebb2c06bb85f22364197ee6a8781d1ea10a5a4;p=AdrOS.git docs: add security fix plan for 2026-05-26 reanalysis --- diff --git a/docs/SECURITY_FIX_PLAN_2026-05-26.md b/docs/SECURITY_FIX_PLAN_2026-05-26.md new file mode 100644 index 00000000..583e9706 --- /dev/null +++ b/docs/SECURITY_FIX_PLAN_2026-05-26.md @@ -0,0 +1,360 @@ +# Security Fix Plan - 2026-05-26 + +## Contexto + +Este plano é baseado na reanálise completa do AdrOS (ZIP CDFA4C08) realizada em 2026-05-25, cruzada com as correções já implementadas em sessões anteriores (documentadas nas memórias do sistema). + +## Status dos Itens do Relatório + +### Itens JÁ Corrigidos (sessões anteriores) + +| ID | Descrição | Status | +|---|---|---| +| A16 | pmm_decref underflow check | ✅ Corrigido | +| A03 | pread/pwrite mode validation | ✅ Corrigido | +| A04 | O_RDONLY\|O_TRUNC rejeição | ✅ Corrigido | +| A14 | truncate/ftruncate VFS integration | ✅ Corrigido | +| A17 | varargs open/openat/fcntl | ✅ Corrigido | +| U04 | execl/execlp varargs | ✅ Corrigido | +| U02 | scanf %s limit (255 bytes) | ✅ Corrigido | +| A18 | shell command substitution | ✅ Corrigido | +| A13 | posix_spawn PID fix | ✅ Corrigido | +| A19 | access() mode implementation | ✅ Corrigido | +| K21 | O_NOFOLLOW implementation | ✅ Corrigido | +| K35 | socket bounce buffers (SMAP) | ✅ Corrigido | +| K14 | SHM permissions | ✅ Corrigido | +| K22 | dlopen per-process isolation | ✅ Corrigido | +| K17 | futex keying (mm, uaddr) | ⚠️ Parcial (addr_space adicionado, mas não robusto) | +| K24 | NX flag in SHM | ⚠️ TODO (desabilitado temporariamente) | + +### Itens Tentados mas Causaram Regressões + +| ID | Descrição | Status | +|---|---|---| +| K12/K13/K23 | /proc access control (UID checks) | ❌ Requer infraestrutura UID completa | +| K15 | raw socket privilege (UID check) | ❌ Requer infraestrutura UID completa | + +### Itens Remanescentes (não abordados) + +Abaixo estão os itens que ainda precisam ser corrigidos, organizados por prioridade. + +--- + +## Fase 1: Críticos de Memória e Parser (7 itens) + +### C1: ELF Loader p_filesz > p_memsz Validation +**Severidade:** CRÍTICA +**Arquivos:** `src/arch/x86/elf.c` +**Problema:** O loader valida `p_offset + p_filesz <= file_len` mas não valida `p_filesz <= p_memsz`. Isso permite escrever além da área mapeada. +**Solução:** +- Adicionar validação `if (ph[i].p_filesz > ph[i].p_memsz) return -EINVAL;` em `elf32_load_segments` +- Aplicar mesma validação em `elf32_load_shared_lib_at` (dlopen path) +- Validar também `p_align`, `e_phentsize`, `e_phnum`, tipo ELF, endianess e ABI + +### C2: NX por Default em Todos os Mapeamentos User +**Severidade:** CRÍTICA +**Arquivos:** `src/kernel/syscall.c`, `src/arch/x86/elf.c`, `src/kernel/shm.c`, `src/drivers/vbe.c` +**Problema:** NX não é aplicado por default em mmap anônimo, brk, stack inicial, SHM e fb0_mmap. +**Solução:** +- `mmap`: Adicionar `VMM_FLAG_NX` por default, remover apenas se `PROT_EXEC` presente +- `brk`: Adicionar `VMM_FLAG_NX` ao criar heap +- `elf32_load_user_from_initrd`: Stack inicial deve ter NX +- `shm_at`: Mapear com NX por default (já documentado como TODO K24) +- `fb0_mmap`: Aplicar NX e respeitar argumento `prot` + +### C3: Initrd TAR Parser com Limites de Tamanho +**Severidade:** CRÍTICA +**Arquivo:** `src/drivers/initrd.c` +**Problema:** Loop TAR sem limite pelo tamanho do initrd, `tar_is_zero_block` sem validar 512 bytes disponíveis, acesso a header/payload sem bounds checking. +**Solução:** +- Manter `end = base + size` e validar cada acesso +- Validar `p + TAR_BLOCK <= end` antes de `tar_is_zero_block` +- Validar `p + sizeof(tar_header_t) + payload <= end` antes de ler +- Validar checksum TAR e overflow em `adv` +- Validar `LZ4B_HDR_SIZE + comp_sz <= size` + +### C4: pmm_boot.c Multiboot2 Parsing com Cursor/Limit +**Severidade:** CRÍTICA +**Arquivo:** `src/arch/x86/pmm_boot.c` +**Problema:** Loops sem validação de `total_size`, `tag->size >= 8`, limite do buffer, nem `mmap->entry_size`. +**Solução:** +- Portar lógica de cursor/limite de `arch_early_setup.c` para `pmm_boot.c` +- Validar `tag->size >= 8` antes de acessar campos +- Validar `cursor + tag->size <= end` antes de `MB2_TAG_NEXT` +- Validar `mmap->entry_size` e bounds do array de entradas + +### C5: VMA Table per Process para mprotect Robusto +**Severidade:** CRÍTICA +**Arquivos:** `src/kernel/syscall.c`, `include/process.h` +**Problema:** `mprotect` tem fallback permissivo para endereços não modelados como VMA. +**Solução:** +- Criar tabela VMA unificada por processo (struct vma_region com start, end, prot) +- Registrar cada mmap, brk, stack, ELF segment na tabela VMA +- `mprotect` deve aceitar apenas ranges completamente cobertos por VMAs +- Remover fallback permissivo + +### C6: Ext2 Superblock/GDT Validation +**Severidade:** CRÍTICA +**Arquivo:** `src/kernel/ext2.c` +**Problema:** `s_blocks_per_group`, `s_inodes_per_group` podem ser zero; `num_groups` e `gdt_bytes` podem overflowar; validação incompleta. +**Solução:** +- Validar `s_blocks_per_group > 0` e `s_inodes_per_group > 0` +- Validar overflow em `num_groups = (s_blocks_count - s_first_data_block + s_blocks_per_group - 1) / s_blocks_per_group` +- Validar `gdt_bytes = num_groups * sizeof(ext2_group_desc_t)` não overflow +- Validar `block_size` é potência de 2 e >= 1024 +- Validar inode size, GDT blocks e bitmap blocks contra limites do dispositivo + +### C7: FAT BPB Validation +**Severidade:** CRÍTICA +**Arquivo:** `src/kernel/fat.c` +**Problema:** `total_sectors - (data_lba - partition_lba)` pode underflowar; `sectors_per_cluster` não validado como potência de 2; cadeias de cluster sem limite. +**Solução:** +- Validar `data_lba >= partition_lba` antes de subtração +- Validar `sectors_per_cluster` é potência de 2 entre 1 e 128 +- Validar `fat_size`, `reserved_sectors`, `root_cluster` contra limites do dispositivo +- Limitar caminhada de cluster por `total_clusters` em `fat_extend_chain`/`fat_free_chain` +- Detectar ciclos em cadeias de cluster + +--- + +## Fase 2: Isolamento e Políticas de Segurança (8 itens) + +### H1: AIO fd Mode e Read-Only Mount Validation +**Severidade:** ALTA +**Arquivo:** `src/kernel/syscall.c` +**Problema:** `syscall_aio_rw_impl` não reutiliza política de read/write/pread/pwrite. +**Solução:** +- Centralizar validação de modo e mount em helper usado por todas as rotas de I/O +- `aio_write` deve rejeitar fd `O_RDONLY` e mount `MS_RDONLY` +- `aio_read` deve rejeitar fd `O_WRONLY` + +### H2: /proc Access Control +**Severidade:** ALTA +**Arquivo:** `src/kernel/procfs.c` +**Problema:** Entradas `/proc/dmesg`, `/proc/cmdline`, `/proc//status`, `/proc//maps`, `/proc//cmdline` sem controle de acesso. +**Solução:** +- Implementar função `proc_may_access(pid, uid)` similar a `ptrace_may_access` +- Redigir endereços em `/proc//maps` para não-root +- Implementar hidepid (2 = hide all pids from non-root) +- Usar pin/refcount ou snapshot sob lock para evitar UAF +- **NOTA:** Requer infraestrutura UID completa (ver Fase 4) + +### H3: SHM Permissões Completas +**Severidade:** ALTA +**Arquivo:** `src/kernel/shm.c` +**Problema:** Falta mode por segmento, grupos, attach read-only, NX default. +**Solução:** +- Adicionar campo `mode`, `gid` em `struct shm_segment` +- Implementar attach read-only (sem flag SHM_RDONLY no momento) +- Aplicar NX por default (já documentado como TODO K24) +- Modelar uid/gid/mode como SysV/POSIX SHM + +### H4: SOCK_RAW Privilege Check +**Severidade:** ALTA +**Arquivo:** `src/kernel/socket.c` +**Problema:** `ksocket_create` aceita `SOCK_RAW` sem checar `current_process->euid`. +**Solução:** +- Adicionar verificação `if (type == SOCK_RAW && current_process->euid != 0) return -EPERM` +- **NOTA:** Requer infraestrutura UID completa (ver Fase 4) + +### H5: Futex Robusto com Spinlock e Shared Memory Keying +**Severidade:** ALTA +**Arquivo:** `src/kernel/syscall.c` +**Problema:** Tabela global sem lock; futex em memória compartilhada não usa objeto físico/inode+offset. +**Solução:** +- Adicionar spinlock para proteger tabela global de futex +- Chavear futex privado por `(mm, uaddr)` (já parcialmente feito) +- Chavear futex compartilhado por página física ou inode+offset +- Coordenar wait/wake com scheduler de forma mais robusta + +### H6: Remover Fixed VAs (KVA_PHYS_MAP) +**Severidade:** ALTA +**Arquivos:** `src/hal/x86/mm.c`, `src/drivers/virtio_blk.c`, `include/arch/x86/kernel_va_map.h` +**Problema:** `hal_mm_map_physical_range` usa sempre `KVA_PHYS_MAP` sem alocador; colisão virtio-blk/E1000 em VAs fixas. +**Solução:** +- Criar alocador de VA kernel para MMIO/DMA/mapeamentos temporários +- Remover VAs fixas locais (KVA_PHYS_MAP) +- Resolver colisão virtio-blk (0xC0340000) com E1000 (0xC0330000..0xC034FFFF) + +### H7: Tmpfs Locking, Quotas e Overflow Validation +**Severidade:** ALTA +**Arquivo:** `src/kernel/tmpfs.c` +**Problema:** Sem locks, sem quotas, `new_cap *= 2` pode virar zero, metadados não inicializados. +**Solução:** +- Adicionar lock por árvore/inode +- Adicionar quota global +- Validar overflow em `new_cap *= 2` +- Inicializar `uid/gid/mode` corretamente + +### H8: rumpuser_free Alignment Handling +**Severidade:** ALTA +**Arquivo:** `src/rump/rumpuser_adros.c` +**Problema:** Quando `alignment > 16`, `rumpuser_free` chama `kfree(mem)` diretamente em vez do ponteiro bruto. +**Solução:** +- Armazenar header uniforme para todas as alocações rumpuser +- Recuperar ponteiro bruto antes de chamar `kfree` + +--- + +## Fase 3: Polimento de VFS e Userspace (12 itens) + +### M1: VFS Permissions - mode != 0 e execve Bit Check +**Severidade:** MÉDIA +**Arquivos:** `src/kernel/fs.c`, `src/kernel/syscall.c` +**Problema:** `vfs_check_permission` retorna sucesso quando `node->mode == 0`; `execve` não verifica bit executável. +**Solução:** +- Remover caso permissivo quando `mode == 0` +- `execve` deve verificar bit executável do inode (S_IXUSR|S_IXGRP|S_IXOTH) + +### M2: POSIX access() Completo +**Severidade:** MÉDIA +**Arquivo:** `src/kernel/syscall.c` +**Problema:** `access()` simplificado - `R_OK` assume legível se existir, `X_OK` testa apenas arquivo regular. +**Solução:** +- Implementar `F_OK/R_OK/W_OK/X_OK` sobre mesma função de permissão +- Usar uid/gid reais em vez de efetivos + +### M3: O_NOFOLLOW -ELOOP Return +**Severidade:** MÉDIA +**Arquivos:** `src/kernel/syscall.c`, `src/kernel/fs.c` +**Problema:** `vfs_lookup_nofollow` evita seguir symlink mas não retorna erro POSIX claro quando componente final é symlink. +**Solução:** +- Se `O_NOFOLLOW` presente e componente final for `FS_SYMLINK`, retornar `-ELOOP` + +### M4: Truncate Fallback Remoção +**Severidade:** MÉDIA +**Arquivo:** `src/kernel/fs.c` +**Problema:** `vfs_truncate` altera apenas `node->length` se backend não implementa truncate. +**Solução:** +- Sem `i_ops->truncate`, retornar `-ENOSYS` +- Limitar fallback a filesystems explicitamente marcados como seguros (se necessário) + +### M5: Socket copy_to_user Ignored Failures +**Severidade:** MÉDIA +**Arquivo:** `src/kernel/syscall.c` +**Problema:** `accept`, `recvfrom`, `recvmsg`, `getsockname`, `getpeername` usam `(void)copy_to_user(...)`. +**Solução:** +- Retornar `-EFAULT` quando copyout obrigatório falhar +- Para campos opcionais, validar ponteiro e tamanho antes + +### M6: Overlayfs Wrapper Lifetime/Refcount +**Severidade:** MÉDIA +**Arquivo:** `src/kernel/overlayfs.c` +**Problema:** `overlay_wrap_child` aloca wrappers sem caminho claro de liberação. +**Solução:** +- Introduzir cache/refcount/release para wrappers +- Ou simplificar overlayfs até haver ownership claro + +### M7: fb0_mmap Respeitar prot e NX +**Severidade:** MÉDIA +**Arquivo:** `src/drivers/vbe.c` +**Problema:** `fb0_mmap` ignora argumento `prot` e sempre mapeia RW sem NX. +**Solução:** +- Respeitar `PROT_READ/PROT_WRITE` +- Negar execução e aplicar NX + +### M8: CSPRNG Implementation +**Severidade:** MÉDIA +**Arquivos:** `src/kernel/devfs.c`, `include/net/lwipopts.h`, `user/ulibc/src/rand.c`, `src/rump/rumpuser_adros.c` +**Problema:** `/dev/random`, `/dev/urandom`, lwIP, libc usam RNGs previsíveis. +**Solução:** +- Implementar CSPRNG de kernel com seed de entropia real (RDRND se disponível, ou TSC + timer) +- Fazer lwIP/devfs/userspace consumirem essa fonte + +### M9: mkstemp Retry Multiple Names +**Severidade:** MÉDIA +**Arquivo:** `user/ulibc/src/stdlib.c` +**Problema:** `mkstemp` tenta apenas um nome; se colidir, falha. +**Solução:** +- Após corrigir CSPRNG, fazer `mkstemp` tentar múltiplos nomes (loop com limite) +- Documentar/desencorajar `tmpnam` + +### M10: fcntl Varargs em ulibc +**Severidade:** MÉDIA +**Arquivo:** `user/ulibc/src/unistd.c` +**Problema:** `fcntl` lê vararg para `F_GETFD` e `F_GETFL` que não exigem argumento. +**Solução:** +- Ler vararg somente nos comandos que precisam: `F_SETFD`, `F_SETFL`, `F_DUPFD`, `F_DUPFD_CLOEXEC` + +### M11: newlib libgloss Varargs +**Severidade:** MÉDIA +**Arquivo:** `newlib/libgloss/adros/posix_stubs.c` +**Problema:** `fcntl` e `openat` leem vararg incondicionalmente. +**Solução:** +- Aplicar mesma correção que ulibc: leitura condicional de vararg + +### M12: scanf Field Width e Buffer Size +**Severidade:** MÉDIA +**Arquivo:** `user/ulibc/src/stdio.c` +**Problema:** Limite 255 bytes não resolve API - `%s` não sabe tamanho real do buffer. +**Solução:** +- Respeitar largura de campo (ex: `%255s`) +- Suportar formatos de largura corretamente +- Auditar comandos que usam `%s` + +--- + +## Fase 4: Infraestrutura UID (Pré-requisito para H2, H4) + +### UID Infrastructure +**Severidade:** ALTA (pré-requisito) +**Descrição:** AdrOS é um OS single-user sem infraestrutura de autenticação multi-usuário completa. +**Solução:** +- Implementar `/etc/passwd` parsing +- Implementar login com autenticação +- UID/EUID inheritance em fork/clone +- Per-process mount namespaces (opcional, mas desejável) +- **Estimativa:** 3-4 dias de trabalho + +--- + +## Fase 5: Low Priority (2 itens) + +### L1: Path Truncation - ENAMETOOLONG +**Severidade:** BAIXA +**Arquivos:** `src/kernel/syscall.c`, `src/kernel/fs.c` +**Problema:** `path_resolve_user` e `vfs_lookup_parent` truncam strings silenciosamente. +**Solução:** +- Tratar truncamento como erro em todos os caminhos de path +- Retornar `-ENAMETOOLONG` quando input excede limite + +### L2: getlogin/who Session/TTY/utmp +**Severidade:** BAIXA +**Arquivos:** `user/ulibc/src/unistd.c`, `user/cmds/who/who.c` +**Problema:** `getlogin()` usa fallback `"root"`, `who` imprime linha fixa. +**Solução:** +- Conectar login a sessões/TTY/utmp +- Retornar erro quando não houver informação confiável + +--- + +## Itens NÃO Incluídos (Falsos Positivos ou Fora de Escopo) + +### sysenter.S (A02) +**Status:** NÃO incluído neste plano +**Motivo:** Relatório menciona problema mas validação atual (`ECX < 0xC0000000` e `ECX >= 8`) é razoável para ABI sysenter. Melhoria seria mudança de ABI, não correção de bug. + +### posix_spawn Kernel Implementation (A13) +**Status:** NÃO incluído neste plano +**Motivo:** Wrapper userspace foi corrigido. Implementação kernel como fork+execve é padrão POSIX e funciona corretamente. + +--- + +## Ordem de Execução Sugerida + +1. **Fase 1 (Críticos):** 7 itens - estimativa 10-14 dias +2. **Fase 4 (UID Infrastructure):** Pré-requisito para H2, H4 - estimativa 3-4 dias +3. **Fase 2 (Isolamento):** 8 itens (H1, H3, H5, H6, H7, H8 primeiro; H2, H4 após UID) - estimativa 8-10 dias +4. **Fase 3 (Polimento):** 12 itens - estimativa 6-8 dias +5. **Fase 5 (Low):** 2 itens - estimativa 1-2 dias + +**Total estimado:** 28-38 dias de trabalho focado + +--- + +## Notas Importantes + +- Este plano assume que os itens já corrigidos (sessões anteriores) permanecem estáveis. +- Itens marcados como "REQUIRES UID INFRASTRUCTURE" devem ser implementados após Fase 4. +- Testes de regressão devem ser executados após cada fase completa. +- Alguns itens (como VMA table per process) são arquiteturais e podem ter impactos amplos.