Seguridad y permisos
"La seguridad no es un producto, es un proceso." — Bruce Schneier.
Qué vas a aprender en este capítulo
Hasta acá viste cómo el SO ejecuta y administra. Ahora ves cómo se defiende. La seguridad de un SO es uno de los temas más amplios y peligrosos: un detalle mal hecho deja vulnerable un país entero. Vas a aprender los conceptos base (autenticación, autorización, auditoría), el modelo clásico Unix (uid/gid + permisos), las extensiones modernas (ACLs, capabilities), los ataques clásicos que el SO debe resistir, y los sandboxes modernos que aíslan código no confiable.
8.1 La idea: confianza por niveles
💡 Intuición
Imaginate un edificio de oficinas. Hay seguridad en la entrada (verifica quién entra: autenticación). Adentro, hay puertas con llave (qué oficinas podés visitar: autorización). Hay cámaras grabando (registro: auditoría). Todo cooperando.
En un SO, la misma trinidad se aplica a procesos:
- Autenticación: ¿qué usuario es? Login, ssh key, biométrica.
- Autorización: ¿qué permite hacer este proceso? Permisos, ACLs, capabilities.
- Auditoría: ¿qué hizo? Logs, syslog, audit framework.
Y como pasa con los edificios, el eslabón más débil define la seguridad del sistema. El mejor cifrado no sirve si la contraseña está pegada en un post-it. El mejor firewall no protege contra un usuario engañado. Por eso la seguridad es un proceso y no un producto.
8.2 Modelo clásico Unix
📐 Fundamento
Identidades
Cada proceso corre con una identidad:
- UID (User ID): número entero, identifica al usuario.
- GID (Group ID): grupo principal del usuario.
- Pertenencia a otros grupos: lista adicional.
UID 0 es root, el superusuario — ignora casi todos los permisos.
$ id
uid=1000(ana) gid=1000(ana) groups=1000(ana),27(sudo),100(users)
sudo corre un comando con UID 0 temporalmente.
Control de acceso a archivos
Ya lo viste en cap 6: cada archivo tiene 9 bits de permisos (rwx para dueño/grupo/otros). Cuando un proceso intenta abrir un archivo:
- Si UID del proceso == UID del archivo → usa permisos de dueño.
- Sino, si GID coincide o pertenece al grupo → permisos de grupo.
- Sino → permisos de otros.
Si los bits requeridos están seteados, acceso permitido. Sino, denegado (EACCES).
Limitaciones del modelo clásico:
- Solo 3 categorías. ¿Y si querés "Pedro y María sí, otros no"? No se puede directamente.
- O todo o nada. ¿"Lectura para grupo A, escritura para grupo B"? Tampoco.
Por eso existen ACLs (Access Control Lists), una extensión que permite reglas más finas:
$ setfacl -m u:beto:rw archivo.txt # Beto: rw específico
$ setfacl -m g:devs:r archivo.txt # grupo devs: r
$ getfacl archivo.txt
# file: archivo.txt
# owner: ana
# group: users
user::rw-
user:beto:rw-
group::r--
group:devs:r--
mask::rw-
other::r--
Casi todos los FS modernos (ext4, NTFS, ZFS) soportan ACLs. Es la base del control de acceso fino en Linux moderno.
8.3 Capabilities — el problema con root
📐 Fundamento
Problema clásico: root puede todo. Un programa que solo necesita poder bindear puerto < 1024 (privilegio especial) tradicionalmente debía correr como root, ganando todos los demás poderes. Si el programa tenía un bug, el atacante también obtenía todo.
Solución: capabilities (Linux). Dividir los poderes de root en capacidades específicas:
| Capability | Para qué |
|---|---|
CAP_NET_BIND_SERVICE |
Bindear puertos < 1024 |
CAP_NET_RAW |
Sockets raw (ping) |
CAP_SYS_ADMIN |
Casi todo lo administrativo |
CAP_SYS_TIME |
Cambiar el reloj |
CAP_KILL |
Enviar señales a cualquier proceso |
CAP_DAC_OVERRIDE |
Saltarse permisos de archivo |
Un proceso puede tener un subconjunto, no todo. Si solo tenés CAP_NET_BIND_SERVICE, podés bindear puerto bajo pero no podés leer archivos ajenos.
Comando para asignar:
$ sudo setcap 'cap_net_bind_service=+ep' /usr/bin/nginx
Ahora nginx puede correr sin ser root pero igual bindear el puerto 80.
Ver capabilities de un proceso:
$ getpcaps <pid>
8.4 Ataques clásicos contra el SO
⚠️ Trampa común
Buffer overflow
Programa en C copia más bytes de los que caben en un buffer:
char buf[64];
strcpy(buf, entrada_del_usuario); // ← si la entrada es de 200 bytes...
El extra sobrescribe la pila — incluyendo la dirección de retorno. Atacante puede inyectar shellcode y desviar la ejecución.
Defensas modernas:
- Stack canaries. Un valor "centinela" entre buffer y dirección de retorno. Si se sobrescribió, el programa muere.
- NX bit / DEP. Páginas de pila marcadas como no ejecutables — el shellcode inyectado no corre.
- ASLR (Address Space Layout Randomization). Las direcciones de bibliotecas y stack se randomizan en cada ejecución. El atacante no puede saber dónde está su shellcode.
- Lenguajes seguros. Rust, Go, Python no permiten buffer overflow.
Escalación de privilegios
Atacante entra como usuario normal y busca una falla en algo setuid root para escalar a root.
Histórico: dirtyc0w (2016), dirty pipe (2022), bugs en sudo, ataques contra /proc.
Defensa: principio de mínimo privilegio — nada con setuid si no es necesario; usar capabilities; mantener todo actualizado.
TOCTOU (Time-Of-Check Time-Of-Use)
Race condition de seguridad:
if (access(path, W_OK) == 0) { // 1. verificás
fd = open(path, O_WRONLY); // 2. abrís
}
Entre paso 1 y 2, el atacante cambia el archivo (con un symlink, por ejemplo). El programa termina abriendo lo que el atacante quería, pero pasó el chequeo.
Defensa: usar openat con O_NOFOLLOW, no separar check de uso.
Symlink attacks
Crear un symlink en /tmp apuntando a /etc/passwd. Cuando un programa root crea archivo en /tmp, sobrescribe /etc/passwd.
Defensa: programas creando temporales deben usar O_NOFOLLOW, mkstemp, etc.
Inyección de comandos
Programa que pasa input del usuario al shell sin escapar:
char cmd[256];
sprintf(cmd, "ls %s", entrada);
system(cmd);
Si el usuario escribe dir; rm -rf /, ejecutás dos comandos.
Defensa: nunca pasar entrada del usuario directo al shell. Usar execve con array de argumentos. O escapar con cuidado extremo.
8.5 Sandboxes y aislamiento
📐 Fundamento
Sandboxing: ejecutar código no confiable con acceso restringido, así que aunque tenga bugs (o sea malicioso), no puede dañar el resto del sistema.
Chroot
Cambia la "raíz" aparente del proceso a un subdirectorio. Lo que antes era /, ahora es ese subdirectorio. Acceso a /etc/passwd es realmente <chroot>/etc/passwd.
Limitaciones: un proceso root puede escapar de chroot. No aísla red, procesos, otros recursos.
Namespaces
Linux ofrece namespaces que aíslan distintos aspectos:
| Namespace | Aísla |
|---|---|
mount |
Sistemas de archivos visibles |
pid |
IDs de proceso |
net |
Interfaces de red |
ipc |
Memoria compartida, semáforos |
uts |
Hostname |
user |
UIDs y GIDs |
cgroup |
Recursos limitados |
Combinándolos: cada conjunto de procesos vive en su propio "micromundo". Eso es lo que hacen Docker y los contenedores: una abstracción sobre namespaces + cgroups + sistema de archivos overlay.
Seccomp
Seccomp-bpf filtra qué syscalls puede hacer un proceso. Si tu proceso solo necesita read/write/close, podés bloquearle todo lo demás. Si después tiene un exploit, no puede pedir fork, execve, etc.
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
// solo read, write, exit, sigreturn permitidas a partir de acá
Lo usan navegadores (Chrome separa renderer del proceso principal y le aplica seccomp), systemd, contenedores.
Linux Security Modules (LSM)
Marco de extensiones del kernel para reglas de seguridad mandatorias:
- SELinux (NSA, RedHat). Etiquetas en cada archivo y proceso, política central.
- AppArmor (Ubuntu, SUSE). Perfiles por ejecutable, más simple que SELinux.
- TOMOYO, Smack — alternativas.
Estos van además de los permisos clásicos. Si los permisos dicen "permitido" pero la política SELinux dice "denegado", gana SELinux.
8.6 Criptografía en el SO
📐 Fundamento
El SO usa criptografía para muchas cosas:
Hashes de contraseñas
/etc/shadow guarda hashes de contraseñas, no las contraseñas reales. Cuando hacés login:
- El sistema toma tu contraseña.
- Le aplica la misma función hash.
- Compara con la guardada.
Hashes modernos usan salt (valor aleatorio único por usuario) y son computacionalmente caros (bcrypt, argon2, scrypt) para frustrar ataques de fuerza bruta.
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>6</mn></mrow><annotation encoding="application/x-tex">6</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">6</span></span></span></span>a7Bx9<span class="katex-error" title="ParseError: KaTeX parse error: Expected 'EOF', got '#' at position 12: aUg3... #̲ " style="color:#cc0000">aUg3... # </span>6 = SHA-512 con $a7Bx9 como salt, después el hash
Cifrado de disco
LUKS en Linux, BitLocker en Windows, FileVault en Mac. El disco se cifra con una clave derivada de tu contraseña. Sin la clave, los datos son aleatorios.
$ cryptsetup luksOpen /dev/sda3 cryptohome
TLS / SSL
El SO no implementa TLS directo (eso es cosa de bibliotecas como OpenSSL), pero le da las primitivas:
/dev/randomy/dev/urandom— generadores de números aleatorios criptográficos.- syscalls para sockets, certificados.
Firma de paquetes
Cuando hacés apt install, los paquetes están firmados con la clave del repo. El gestor verifica la firma antes de instalar. Sin esto, un MitM podría inyectarte malware.
Secure Boot
UEFI verifica que el bootloader esté firmado. Bootloader verifica firma del kernel. Kernel verifica firma de módulos. Cadena de confianza desde el firmware hasta el espacio usuario.
8.7 Logs y auditoría
🛠️ En la práctica
Saber qué pasó es la mitad de la seguridad.
/var/log/: directorio principal de logs.
| Archivo | Para qué |
|---|---|
/var/log/auth.log |
Logins, sudo, ssh |
/var/log/syslog |
Mensajes generales |
/var/log/kern.log |
Mensajes del kernel |
/var/log/dpkg.log |
Cambios de paquetes |
journalctl (systemd):
$ journalctl -u sshd # solo del servicio sshd
$ journalctl --since "1 hour ago"
$ journalctl -p err # solo errores
auditd: framework de auditoría detallada.
$ sudo auditctl -w /etc/passwd -p wa -k passwd_watch
# graba toda escritura/cambio de permisos en /etc/passwd
$ sudo ausearch -k passwd_watch
Lección crítica: logs sin rotación llenan disco. logrotate gestiona eso.
Lección 2: logs sin monitoreo no sirven de nada. SIEMs (Splunk, ELK, Loki) los analizan.
8.8 Buenas prácticas para administrar un SO
🛠️ En la práctica
Lista corta de hábitos seguros:
- Mínimo privilegio. No correr nada como root si podés evitarlo.
- Mantener actualizado.
apt update && apt upgraderegular. Las vulnerabilidades publicadas se explotan en horas. - Firewall.
ufw(Ubuntu),firewalld(RedHat). Bloquear puertos no usados. - SSH con llaves, no contraseñas. Deshabilitar root login.
- Backup regular. Y probar la restauración. Backup que no se prueba no existe.
- Logs y monitoreo. Saber qué pasa antes de que sea problema.
- Principio de menor confianza. No correr código de fuentes desconocidas. Verificar firmas.
- 2FA donde se pueda.
- Cifrado de disco en laptops.
- No reutilizar contraseñas. Usar gestor (Bitwarden, KeePass).
Si administrás servidores:
fail2banpara bloquear IPs con muchos intentos fallidos.unattended-upgradespara actualizaciones automáticas críticas.- Auditoría regular con herramientas como
lynis. - Aislar servicios en contenedores o VMs.
8.9 Ejercicios
✏️ Ejercicio 8.1 — Permisos seguros
¿Qué permisos darías a:
a. Tu carpeta ~/.ssh/
b. La clave privada ~/.ssh/id_rsa
c. Un script con tu contraseña hardcodeada (mala idea, pero...)
d. Un archivo output.log que escribís durante una corrida
e. Un binario público para todos en /usr/local/bin/
Solución
a. 700 (drwx------): solo tú.
b. 600 (-rw-------): solo tú leés. SSH rechaza llaves con permisos más laxos.
c. 600 o mejor: no hacer eso, usar variable de entorno o secret manager.
d. 644 (-rw-r--r--): tú escribís, otros leen. O 600 si tiene info sensible.
e. 755 (-rwxr-xr-x): el dueño puede modificar; cualquiera ejecuta.
✏️ Ejercicio 8.2 — Identificar la vulnerabilidad
Este código tiene dos vulnerabilidades. Identificalas y arreglalas.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
char nombre[16];
char comando[64];
strcpy(nombre, argv[1]);
sprintf(comando, "echo Hola %s", nombre);
system(comando);
return 0;
}
Solución
Vulnerabilidad 1: buffer overflow. Si argv[1] tiene >16 bytes, sobrescribe la pila.
Vulnerabilidad 2: inyección de shell. Si argv[1] es "; rm -rf ~", el comando se vuelve echo Hola ; rm -rf ~ — borra el home.
Arreglo:
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc < 2 || strlen(argv[1]) > 14) {
fprintf(stderr, "uso: hola <nombre> (max 14 chars)\n");
return 1;
}
/* Sin shell — usar execve directo o función de string segura */
printf("Hola %s\n", argv[1]);
return 0;
}
Si realmente necesitás ejecutar otro programa, usá execve con argumentos como array, nunca system con strings concatenadas.
✏️ Ejercicio 8.3 — Configurar SSH seguro
Listá las cinco configuraciones que harías en /etc/ssh/sshd_config para endurecer el servicio.
Solución
PermitRootLogin no # No login directo como root
PasswordAuthentication no # Solo claves, no contraseñas
PubkeyAuthentication yes
Port 2222 # Puerto no estándar (security through obscurity, complemento)
AllowUsers ana beto # Whitelist explícita
MaxAuthTries 3 # Tras 3 fallos, desconecta
ClientAliveInterval 600 # Mata conexiones idle
Después, en el cliente:
$ ssh-keygen -t ed25519 -a 100 # generar clave moderna
$ ssh-copy-id -p 2222 ana@servidor # subirla
$ ssh -p 2222 ana@servidor # login sin contraseña
Combinado con fail2ban y firewall, queda razonablemente seguro.
8.10 Cierre del libro
Llegaste al final de Sistemas Operativos. Repaso del recorrido:
- Qué es un SO — abstracciones, kernel, modos.
- Procesos e hilos — fork/exec, race conditions.
- Planificación de CPU — algoritmos, CFS, multinúcleo.
- Sincronización — mutex, semáforos, deadlock.
- Memoria virtual — paginación, TLB, swap.
- Sistemas de archivos — inodos, journaling, VFS.
- E/S y dispositivos — DMA, drivers, scheduling.
- Seguridad — permisos, sandboxes, ataques.
Lo que falta para ser un experto SO:
- Sistemas distribuidos — múltiples máquinas como una.
- Hipervisores y virtualización — KVM, Xen, contenedores a fondo.
- Sistemas en tiempo real — RTOS para aviones, autos.
- Kernel hacking — escribir tu propio módulo.
Materias relacionadas en la carrera:
- Redes de Computadoras (RED515).
- Bases de Datos (BDD315/415).
- Compiladores (opcional/electiva).
- Arquitectura de Computadoras — la otra mitad de la moneda (hardware).
Si te apasionó el tema, OSTEP sigue siendo el mejor punto de inicio para profundizar. Y nada reemplaza leer kernel y escribir módulos pequeños — el código de Linux es público y muy comentado.
8.11 Para profundizar
- Tanenbaum & Bos cap. 9 (seguridad).
- Ross Anderson, Security Engineering — biblia de seguridad de sistemas, gratis online.
- OWASP — lista actualizada de vulnerabilidades web (muchas se aplican a SO también).
- NIST publications — guías oficiales de hardening.
- Curso siguiente: Redes de Computadoras — el "afuera" del SO local.
Definiciones nuevas: autenticación, autorización, auditoría, UID/GID, ACL, capability, buffer overflow, ASLR, NX, TOCTOU, sandbox, chroot, namespace, seccomp, SELinux, AppArmor, LUKS, secure boot, principio de mínimo privilegio.