Segurança do Sistema Operacional
Vídeo da aula estará disponível em breve
Modelo de Ameaças
Antes de implementar defesas, precisamos entender o que estamos defendendo e contra quem. O modelo de ameaças (threat model) define os ativos, os atacantes e os vetores de ataque.
ATIVOS (o que proteger):
├── Dados do usuário (arquivos, senhas, chaves)
├── Integridade do kernel (não ser modificado)
├── Disponibilidade (sistema funcionando)
└── Isolamento entre processos (um não acessa outro)
ATACANTES (contra quem):
├── Usuário local malicioso (escalar privilégios)
├── Malware/exploit remoto (buffer overflow via rede)
├── Processo comprometido (sandbox escape)
└── Acesso físico (boot de USB, cold boot attack)
VETORES DE ATAQUE:
├── Buffer overflow (execução de código arbitrário)
├── Escalação de privilégios (user → root)
├── Race conditions em setuid programs
├── Engenharia social (phishing de senha root)
└── Supply chain (dependência maliciosa)
Superfície de ataque do kernel Linux:
~400 system calls = 400 pontos de entrada potenciais
Cada syscall é um vetor de ataque se tiver um bug
Autenticação
O primeiro passo da segurança é autenticação: verificar que o usuário é quem diz ser. Existem três fatores de autenticação, e combiná-los aumenta a segurança.
1. ALGO QUE VOCÊ SABE:
Senha, PIN, passphrase
→ Armazenada como hash em /etc/shadow (Linux)
→ Vulnerável a: brute force, phishing, reuso de senhas
2. ALGO QUE VOCÊ TEM:
Token físico (YubiKey), celular (TOTP), smart card
→ 2FA: senha + código do celular
→ Vulnerável a: roubo, SIM swap
3. ALGO QUE VOCÊ É:
Biometria: impressão digital, face, íris
→ Conveniente mas irrevogável (não dá para trocar seu dedo)
→ Vulnerável a: falsificação, coerção
Linux: /etc/shadow
marco:$6$salt$hash...:19000:0:99999:7:::
│ │ │
│ │ └── hash SHA-512 de salt+senha
│ └── salt aleatório (contra rainbow tables)
└── $6$ = SHA-512 (algoritmo de hash)
PAM (Pluggable Authentication Modules):
Framework modular de autenticação do Linux.
/etc/pam.d/ contém configuração por serviço.
Permite: senha + 2FA + LDAP + biometria, etc.
Buffer Overflow
O buffer overflow é a vulnerabilidade mais importante da história de segurança de sistemas. Ocorre quando um programa escreve dados além dos limites de um buffer, sobrescrevendo dados adjacentes na memória — incluindo endereços de retorno na stack.
#include <string.h>
#include <stdio.h>
void funcao_vulneravel(char* input) {
char buffer[64]; // 64 bytes na stack
strcpy(buffer, input); // NÃO verifica tamanho!
printf("Você digitou: %s\n", buffer);
}
int main(int argc, char** argv) {
funcao_vulneravel(argv[1]);
return 0;
}
/*
Se input tem 70 bytes: sobrescreve dados após buffer
Se input tem 80+ bytes: sobrescreve ENDEREÇO DE RETORNO
Layout da stack durante funcao_vulneravel():
┌──────────────────────────────────────┐ endereço alto
│ args de main (argc, argv) │
├──────────────────────────────────────┤
│ endereço de retorno de main │
├──────────────────────────────────────┤
│ frame pointer salvo (EBP) │
├──────────────────────────────────────┤
│ endereço de retorno de func_vuln │ ← atacante sobrescreve isso!
├──────────────────────────────────────┤
│ frame pointer salvo │
├──────────────────────────────────────┤
│ buffer[64] │ ← overflow começa aqui
│ ................................. │ e cresce para CIMA
└──────────────────────────────────────┘ endereço baixo
Ataque clássico:
1. Preencher buffer com shellcode (instruções de máquina)
2. Sobrescrever endereço de retorno com endereço do buffer
3. Quando função retorna: salta para o shellcode
4. Shellcode executa: execve("/bin/sh") → shell com privilégios do processo
*/
Defesas contra Buffer Overflow
STACK CANARY (gcc -fstack-protector):
Valor aleatório colocado entre buffer e endereço de retorno.
Verificado antes do retorno da função.
Se foi sobrescrito → overflow detectado → programa abortado.
Stack COM canary:
┌────────────────────────────┐
│ endereço de retorno │
├────────────────────────────┤
│ ★ CANARY (valor aleatório) │ ← verificado antes do ret
├────────────────────────────┤
│ buffer[64] │ ← overflow sobrescreve canary
└────────────────────────────┘
Se canary mudou: *** stack smashing detected ***
O atacante não sabe o valor do canary (aleatório por execução).
Compilar com proteção:
gcc -fstack-protector-strong programa.c (default no Ubuntu)
gcc -fstack-protector-all programa.c (todos os funções)
ASLR (Address Space Layout Randomization):
Randomiza posições de: stack, heap, bibliotecas, código.
Atacante não sabe onde o buffer está na memória.
Sem ASLR: buffer sempre em 0x7fffffffe000
Com ASLR: buffer em endereço diferente a cada execução.
$ cat /proc/sys/kernel/randomize_va_space
2 (0=off, 1=parcial, 2=completo)
$ ldd /bin/bash # executar duas vezes: endereços diferentes
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x7f3a1b200000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x7f8e44c00000)
DEP / NX (No-Execute / W^X):
Páginas de memória: ou executáveis, ou escrevíveis, NUNCA ambas.
Stack marcada como não-executável: shellcode na stack não roda.
Sem NX: atacante injeta código na stack e executa
Com NX: CPU gera fault ao tentar executar código na stack
Bypass: ROP (Return-Oriented Programming)
Encadeia "gadgets" (fragmentos de código existente)
Não injeta código novo, reutiliza código do programa/libc
Mais difícil, mas possível → ASLR dificulta ainda mais
Defesa em profundidade (defense in depth):
Stack canary + ASLR + NX + PIE + RELRO
Todas habilitadas por default em distribuições modernas.
Cada camada torna o ataque exponencialmente mais difícil.
Sandboxing e Isolamento
Mesmo com todas as defesas de memória, se um processo é comprometido, precisamos limitar o dano. Sandboxing confina processos em ambientes restritos.
1. chroot (change root):
Muda o "/" visível para o processo.
Processo vê /var/chroot como seu "/".
Limitado: não isola PID, rede, IPC. Escape fácil como root.
2. Namespaces (base de containers Docker):
Cada namespace isola um aspecto:
├── PID namespace: processo vê só seus PIDs
├── Network namespace: rede isolada (IP próprio)
├── Mount namespace: filesystem isolado
├── User namespace: UID 0 dentro = unprivileged fora
├── IPC namespace: shared memory isolada
└── UTS namespace: hostname próprio
3. cgroups (control groups):
Limita RECURSOS: CPU, memória, I/O, PIDs.
"Processo X pode usar no máximo 2 cores e 512MB de RAM"
4. seccomp (secure computing):
Filtra SYSCALLS: lista branca de system calls permitidas.
"Este processo só pode: read, write, exit. Nada mais."
Usado por: Chrome (tabs), Docker, Firefox.
Container = namespaces + cgroups + seccomp + filesystem overlay
Docker: empacota e isola aplicações usando esses mecanismos
Não é VM: compartilha o kernel do host
SELinux e AppArmor
Permissões Unix tradicionais são DAC (Discretionary Access Control): o dono do arquivo controla as permissões. MAC (Mandatory Access Control) adiciona políticas do sistema que nem root pode violar.
DAC (Unix padrão):
marco pode chmod 777 seus arquivos → qualquer um acessa
root pode fazer TUDO → se comprometido, game over
Problema: decisões de segurança nas mãos do usuário
MAC (SELinux / AppArmor):
Política do SISTEMA define o que cada processo pode fazer
Mesmo root está sujeito à política
"httpd pode ler /var/www e escutar porta 80. NADA mais."
Root comprometido via httpd? Não pode ler /etc/shadow.
SELinux (Red Hat, Fedora, CentOS):
- Rótulos em TUDO: arquivos, processos, portas
- Política define: qual tipo de processo acessa qual tipo de arquivo
- httpd_t pode ler httpd_sys_content_t
- httpd_t NÃO pode ler shadow_t (mesmo como root!)
- Complexo de configurar, mas muito granular
AppArmor (Ubuntu, SUSE):
- Perfis por programa (/etc/apparmor.d/)
- "Firefox pode ler ~/Downloads, /tmp, ~/.mozilla"
- "Firefox NÃO pode ler ~/.ssh, /etc/shadow"
- Mais simples que SELinux (path-based vs label-based)
$ sudo aa-status # ver perfis ativos
25 profiles are in enforce mode.
/usr/sbin/cupsd
/usr/bin/firefox
...
Conceitos de Criptografia no SO
1. HASH (integridade):
SHA-256, SHA-512: transformam dados em fingerprint fixo
Uso: /etc/shadow (hash de senhas), verificação de pacotes
Propriedade: irreversível (não dá para obter a senha do hash)
2. CRIPTOGRAFIA SIMÉTRICA (confidencialidade):
AES-256: mesma chave para cifrar e decifrar
Uso: LUKS (disk encryption), dm-crypt
$ sudo cryptsetup luksFormat /dev/sda2 # cifrar partição
3. CRIPTOGRAFIA ASSIMÉTRICA (autenticação):
RSA, Ed25519: chave pública + chave privada
Uso: SSH (login sem senha), assinatura de pacotes
$ ssh-keygen -t ed25519 # gerar par de chaves
4. GERAÇÃO DE ALEATORIEDADE:
/dev/random: entropia do hardware (bloqueia se acabar)
/dev/urandom: PRNG semeado por entropia (não bloqueia)
Kernel coleta entropia de: timing de interrupções, disco,
teclado, mouse, RDRAND (instrução Intel)
Disk encryption (LUKS/dm-crypt):
Dados no disco cifrados com AES-256.
Chave protegida por senha do usuário (na inicialização).
Se disco for roubado: dados ilegíveis sem a senha.
Transparente: filesystem monta normalmente após desbloqueio.
No harness.os
Os conceitos de segurança do SO aplicam-se diretamente a como o harness.os protege seus dados e operações:
Defesas do SO Paralelo no harness.os
══════════════ ═══════════════════════
Autenticação (login) Neon API key + MCP auth
Quem é este usuário? Quem é este agente?
Stack canary (detecção) Validação de inputs
Detecta overflow Verifica tool parameters
ASLR (imprevisibilidade) Token rotation, session IDs
Atacante não sabe endereços IDs não previsíveis
Sandboxing (isolamento) Agent scoping
Processo vê só seu namespace Agente vê só seu project
seccomp filtra syscalls MCP filtra tools disponíveis
MAC (SELinux/AppArmor) Row-Level Security (Postgres)
Política overrides permissões Policy overrides queries
Nem root viola a política Nem admin vê dados de outro tenant
Criptografia (LUKS/SSH) TLS + encrypted connections
Dados cifrados em repouso Neon: dados cifrados at rest
SSH para acesso remoto TLS para todas as conexões
Princípio compartilhado:
Defense in depth — múltiplas camadas de proteção.
Se uma falha, outras ainda protegem.
Exercícios
- Compile o programa vulnerável a buffer overflow (sem proteções):
gcc -fno-stack-protector -z execstack -no-pie -o vuln vuln.c. Execute com entrada de 60, 70 e 80 caracteres. O que acontece? Agora compile com proteções padrão e repita. Qual é a diferença? - Verifique o estado das defesas no seu sistema:
cat /proc/sys/kernel/randomize_va_space(ASLR),dmesg | grep NX(NX/DEP). Qual é a configuração padrão? Explique o que cada defesa protege. - Execute
sudo aa-status(ousestatusno Fedora). Quantos perfis estão em modo enforce? Escolha um perfil e analise: que arquivos e capacidades o processo está limitado a usar?
Resumo
Verifique seu entendimento
Qual técnica impede que um atacante execute shellcode injetado na stack?
- O modelo de ameaças define ativos, atacantes e vetores de ataque antes de implementar defesas
- Autenticação usa fatores: algo que sabe (senha), tem (token) ou é (biometria); senhas armazenadas como hash + salt
- Buffer overflow explora escrita além dos limites de um buffer para sobrescrever o endereço de retorno e executar código
- Defesas de memória: stack canary (detecção), ASLR (imprevisibilidade), NX/DEP (impedir execução de dados)
- Sandboxing (namespaces, cgroups, seccomp) e MAC (SELinux, AppArmor) limitam o dano de processos comprometidos
- Criptografia protege dados em repouso (LUKS) e em trânsito (TLS/SSH)