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:

  1. Si UID del proceso == UID del archivo → usa permisos de dueño.
  2. Sino, si GID coincide o pertenece al grupo → permisos de grupo.
  3. 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.

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:

  1. El sistema toma tu contraseña.
  2. Le aplica la misma función hash.
  3. 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 &#x27;EOF&#x27;, got &#x27;#&#x27; 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/random y /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:

  1. Mínimo privilegio. No correr nada como root si podés evitarlo.
  2. Mantener actualizado. apt update && apt upgrade regular. Las vulnerabilidades publicadas se explotan en horas.
  3. Firewall. ufw (Ubuntu), firewalld (RedHat). Bloquear puertos no usados.
  4. SSH con llaves, no contraseñas. Deshabilitar root login.
  5. Backup regular. Y probar la restauración. Backup que no se prueba no existe.
  6. Logs y monitoreo. Saber qué pasa antes de que sea problema.
  7. Principio de menor confianza. No correr código de fuentes desconocidas. Verificar firmas.
  8. 2FA donde se pueda.
  9. Cifrado de disco en laptops.
  10. No reutilizar contraseñas. Usar gestor (Bitwarden, KeePass).

Si administrás servidores:

  • fail2ban para bloquear IPs con muchos intentos fallidos.
  • unattended-upgrades para 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/

✏️ 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;
}

✏️ Ejercicio 8.3 — Configurar SSH seguro

Listá las cinco configuraciones que harías en /etc/ssh/sshd_config para endurecer el servicio.

8.10 Cierre del libro

Llegaste al final de Sistemas Operativos. Repaso del recorrido:

  1. Qué es un SO — abstracciones, kernel, modos.
  2. Procesos e hilos — fork/exec, race conditions.
  3. Planificación de CPU — algoritmos, CFS, multinúcleo.
  4. Sincronización — mutex, semáforos, deadlock.
  5. Memoria virtual — paginación, TLB, swap.
  6. Sistemas de archivos — inodos, journaling, VFS.
  7. E/S y dispositivos — DMA, drivers, scheduling.
  8. Seguridad — permisos, sandboxes, ataques.

Lo que falta para ser un experto SO:

Materias relacionadas en la carrera:

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


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.