1. La tercera barrera
A lo largo del curso vimos dos mecanismos de protección, ambos impuestos por el hardware. Los modos de ejecución del tema 12 impiden que un programa toque los dispositivos o altere las tablas del kernel. La memoria virtual del tema 27 impide que un proceso lea o modifique la memoria de otro.
Ninguno de los dos responde una pregunta más cotidiana. Un programa en modo usuario no puede escribir en el disco por su cuenta: tiene que pedírselo al kernel con una llamada al sistema. ¿Con qué criterio decide el kernel si acepta? Si aceptara todo, cualquier programa podría borrar cualquier archivo, finalizar cualquier proceso y apagar la máquina, con la sola condición de pedirlo con buenos modales.
La protección es el conjunto de mecanismos con que el sistema operativo controla qué operaciones puede realizar cada proceso sobre cada recurso. Se apoya en una identidad asociada a cada proceso y en unas reglas asociadas a cada recurso.
El esquema tiene tres elementos, y siempre los mismos:
| Elemento | En un sistema Unix |
|---|---|
| Quién pide | Un proceso, que actúa en nombre de un usuario. |
| Sobre qué | Un archivo, un dispositivo, otro proceso, o el sistema en su conjunto. |
| Qué quiere hacer | Leer, escribir, ejecutar, finalizar, montar, apagar. |
El lugar donde se decide es siempre el mismo: la entrada de una llamada al sistema. Es el paso de validación que describimos en el tema 13. No hay otro punto posible, porque es el único camino por el que un programa llega a los recursos, y por eso es suficiente. Conviene, por último, separar dos palabras: la protección es el mecanismo que ofrece el sistema; la seguridad es el objetivo, más amplio, de que el sistema resista a quien quiere usarlo mal.
2. Un usuario es un número
Para el kernel, un usuario no es una persona ni un nombre. Es un número entero, el UID, de user identifier. Todo lo que el kernel sabe de un usuario es ese número, y todo lo que hace es compararlo con otros.
$ id
uid=1000(diego) gid=1000(diego) groups=1000(diego),4(adm),27(sudo),46(plugdev),100(users)
$ grep -E '^(root|www-data|diego):' /etc/passwd
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
diego:x:1000:1000:Diego,,,:/home/diego:/bin/bash
Los nombres existen para las personas. La correspondencia entre nombres y números está en un archivo de texto, /etc/passwd, que el kernel no lee nunca: lo consultan los programas, como ls o ps, para mostrar «diego» donde el kernel les entregó «1000». Cada línea describe una cuenta, con siete campos separados por dos puntos:
| Campo | En la línea de diego | Significado |
|---|---|---|
| Nombre | diego | El nombre de la cuenta. |
| Contraseña | x | Una marca: la contraseña está en otro archivo. Lo vemos en la sección 11. |
| UID | 1000 | El número de usuario. |
| GID | 1000 | El número de su grupo principal. |
| Descripción | Diego,,, | Texto libre. |
| Directorio personal | /home/diego | Donde empieza su sesión. |
| Shell | /bin/bash | El programa que se ejecuta al iniciar sesión. |
Los números se reparten en tres rangos, por convención:
| UID | Clase | Para qué |
|---|---|---|
| 0 | root | El administrador. Es el único número con un significado especial para el kernel. |
| 1 a 999 | Usuarios del sistema | Cuentas para servicios. Nadie inicia sesión con ellas: su shell es nologin. |
| 1000 en adelante | Usuarios comunes | Las personas. |
El rango intermedio muestra que los usuarios no sirven solo para distinguir personas. En un sistema actual, cada servicio se ejecuta con su propia cuenta. El servidor web corre como www-data, y si un atacante logra tomar su control mediante un defecto, se encuentra con los permisos de ese usuario: no puede leer los archivos de diego ni modificar el sistema. Es la misma idea que Android aplica a cada aplicación, según vimos en el tema 23.
3. Los grupos
Para conceder un permiso a varios usuarios a la vez existen los grupos, identificados por otro número, el GID. Cada usuario tiene un grupo principal, el que figura en /etc/passwd, y puede pertenecer a otros, anotados en /etc/group:
$ grep -E '^(adm|sudo|disk):' /etc/group
adm:x:4:syslog,diego
disk:x:6:
sudo:x:27:diego
La salida de id mostraba que diego pertenece a cinco. Varios aparecieron en temas anteriores, y ahora se entiende qué concedían:
| Grupo | Lo que permite a sus miembros | Tema |
|---|---|---|
sudo | Ejecutar comandos como administrador. En Fedora se llama wheel. | Sección 8 |
adm | Leer los registros del sistema. | 22 |
disk | Acceder a los discos directamente. No tiene miembros, y es mejor que siga así. | 29 |
plugdev, audio, dialout | Usar dispositivos extraíbles, el sonido, los puertos serie. | 29 |
El grupo es la herramienta para conceder un acceso preciso sin entregar el control total. Añadir a alguien al grupo dialout le permite usar un puerto serie, y nada más.
4. La identidad de un proceso
Los usuarios no hacen llamadas al sistema: las hacen los procesos. Por eso la identidad no está en el usuario sino en cada proceso, como uno de los componentes que enumeramos en el tema 26. Son sus credenciales: un UID, un GID y la lista de grupos adicionales. Un programa puede consultar las suyas:
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/* Busca en /etc/passwd el nombre que corresponde a un número de usuario. */
static const char *nombre(uid_t uid)
{
struct passwd *p = getpwuid(uid);
return (p != NULL) ? p->pw_name : "?";
}
int main(void)
{
uid_t real = getuid();
uid_t efectivo = geteuid();
int fd;
printf("Usuario real: %u (%s)\n", (unsigned)real, nombre(real));
printf("Usuario efectivo: %u (%s)\n", (unsigned)efectivo, nombre(efectivo));
printf("Grupo: %u\n", (unsigned)getgid());
fd = open("/etc/shadow", O_RDONLY); /* un archivo reservado al administrador */
printf("Abrir /etc/shadow: %s\n", (fd == -1) ? strerror(errno) : "permitido");
if (fd != -1)
close(fd);
return 0;
}
$ gcc -Wall -Wextra -g identidad.c -o identidad
$ ./identidad
Usuario real: 1000 (diego)
Usuario efectivo: 1000 (diego)
Grupo: 1000
Abrir /etc/shadow: Permission denied
$ sudo ./identidad
Usuario real: 0 (root)
Usuario efectivo: 0 (root)
Grupo: 0
Abrir /etc/shadow: permitido
El mismo ejecutable, la misma llamada open sobre el mismo archivo, y dos respuestas distintas del kernel. Lo único que cambió entre una ejecución y otra son las credenciales del proceso. Hay dos números de usuario, «real» y «efectivo», que aquí coinciden; la diferencia es el asunto de la sección 9.
Falta saber de dónde saca un proceso sus credenciales. La respuesta es la herencia que vimos en el tema 26:
forklas copia. El hijo nace con las credenciales del padre.execlas conserva. Cambiar de programa no cambia de identidad, salvo en el caso especial de la sección 9.- Solo un proceso privilegiado puede cambiarlas. Un proceso común no puede decidir ser otro usuario.
De ahí se sigue cómo adquiere su identidad una sesión entera. El proceso 1 y los servicios que inicia se ejecutan como root. El programa que atiende el inicio de sesión, que también corre como root, pide el nombre y la contraseña; si son correctos, crea un proceso hijo, le cambia las credenciales a las del usuario y ejecuta en él la shell. A partir de ese proceso, todo lo que el usuario lance hereda su UID. La identidad se fija una vez, en la raíz de la sesión, y se propaga por el árbol de procesos.
5. Los permisos de un archivo
Del otro lado están las reglas asociadas a cada recurso. Para los archivos, el inodo guarda tres datos, que vimos de pasada en el tema 28: un usuario dueño, un grupo dueño y nueve bits de permisos.
Son tres clases de usuarios, y para cada una, tres permisos. En un archivo regular significan lo que su nombre indica:
| Letra | Permiso | Permite | Llamadas que lo requieren |
|---|---|---|---|
r | Lectura | Leer el contenido. | open para lectura |
w | Escritura | Modificar el contenido. | open para escritura |
x | Ejecución | Ejecutarlo como programa. | execve |
El tercer permiso es el que distingue un programa de un archivo de datos. En el tema 4 hubo que dar permiso de ejecución a un script con chmod +x antes de poder lanzarlo: sin ese bit, el kernel rechaza la llamada execve, sin mirar siquiera el contenido.
Los permisos se cambian con chmod, de dos maneras equivalentes:
| Comando | Efecto | Resultado |
|---|---|---|
chmod 640 archivo | Fija los nueve bits. | rw-r----- |
chmod 755 archivo | Fija los nueve bits. | rwxr-xr-x |
chmod u+x archivo | Añade ejecución para el dueño. | — |
chmod go-w archivo | Quita escritura al grupo y a los demás. | — |
chown usuario:grupo archivo | Cambia el dueño y el grupo. Solo puede hacerlo el administrador. | — |
Solo el dueño de un archivo, y el administrador, pueden cambiar sus permisos. Es un rasgo de este modelo: quien crea un archivo decide quién más puede usarlo. Los archivos nuevos reciben como dueño al usuario del proceso que los crea, y unos permisos iniciales que dependen de un ajuste de cada proceso llamado umask.
6. Cómo se comprueban
Cuando un proceso pide abrir un archivo, el kernel compara las credenciales del proceso con los datos del inodo. El procedimiento tiene un orden estricto:
- ¿El usuario efectivo del proceso es 0? Si lo es, el acceso se concede sin mirar los permisos. root
- ¿Coincide con el dueño del archivo? Si coincide, se aplican los tres bits del dueño, y solo esos. dueño
- ¿Alguno de los grupos del proceso coincide con el grupo del archivo? Si es así, se aplican los tres bits del grupo, y solo esos. grupo
- En cualquier otro caso, se aplican los tres bits de los demás. los demás
El detalle que suele pasarse por alto es que las clases no se acumulan. Se aplica la primera que corresponde, y la búsqueda termina. Un experimento lo deja claro:
$ echo "secreto" > nota.txt
$ chmod 044 nota.txt # nada para el dueño; lectura para el grupo y los demás
$ ls -l nota.txt
----r--r-- 1 diego diego 8 oct 12 14:02 nota.txt
$ cat nota.txt
cat: nota.txt: Permission denied
Cualquier usuario del sistema puede leer este archivo, excepto su dueño. El kernel encontró que el proceso era del dueño, aplicó los bits del dueño, que no conceden nada, y no siguió mirando. No es una configuración útil, pero muestra el algoritmo sin ambigüedad. El dueño conserva, eso sí, el derecho de cambiar los permisos, de modo que puede devolvérselos con chmod.
Hay dos propiedades más que conviene tener presentes:
- La comprobación se hace al abrir. Una vez que el proceso tiene el descriptor, las lecturas y escrituras no vuelven a comprobar nada. Si alguien cambia los permisos después, quien ya tenía el archivo abierto conserva su acceso.
- El descriptor lleva el permiso consigo. Como los descriptores se heredan, un proceso privilegiado puede abrir un archivo y pasarle el descriptor a un hijo sin privilegios. Es la técnica habitual para dar a un programa acceso a un recurso concreto y a ninguno más.
7. Permisos en directorios
Los directorios tienen los mismos nueve bits, pero como un directorio es una tabla de nombres, según vimos en el tema 28, los permisos significan otra cosa:
| Permiso | En un archivo | En un directorio |
|---|---|---|
r | Leer el contenido | Listar los nombres que contiene |
w | Modificar el contenido | Añadir, quitar y renombrar entradas |
x | Ejecutarlo | Atravesarlo: usar los nombres para llegar a lo que hay dentro |
De esta tabla salen dos consecuencias que desconciertan la primera vez. La primera es que borrar un archivo no depende de los permisos del archivo:
$ chmod 444 nota.txt # solo lectura, para todos
$ echo "más" >> nota.txt
bash: nota.txt: Permission denied
$ rm -f nota.txt # y sin embargo…
$ ls nota.txt
ls: cannot access 'nota.txt': No such file or directory
No se pudo modificar el archivo, y se lo pudo borrar. No hay contradicción: borrar es quitar una entrada del directorio, y lo que cuenta es el permiso de escritura sobre el directorio, que es nuestro. El archivo no interviene.
La segunda es que, para abrir un archivo, hace falta permiso de x en todos los directorios de su ruta. Es la comprobación que mencionamos al recorrer una ruta en el tema 28. Quitar ese permiso a un directorio lo cierra por completo, sin tocar nada de lo que contiene; por eso el directorio personal de cada usuario suele tener permisos 750 o 700.
El directorio /tmp plantea un caso particular. Todos deben poder crear archivos en él, lo que exige permiso de escritura para todos, y con ese permiso cualquiera podría borrar los archivos ajenos. Se resuelve con un bit adicional, que se muestra como una t al final de los permisos: en un directorio que lo tiene, solo el dueño de cada archivo puede borrarlo.
8. root
El primer paso del algoritmo de la sección 6 es el que define al administrador. El usuario con UID 0, llamado root, está exento de las comprobaciones de permisos. Puede leer cualquier archivo, finalizar cualquier proceso, montar discos, cargar módulos en el kernel y apagar la máquina.
En el tema 12 insistimos en lo que eso no significa. Un proceso de root se ejecuta en modo usuario, en el anillo 3, como cualquier otro. No puede ejecutar instrucciones privilegiadas ni leer la memoria del kernel. Su poder consiste en que el kernel acepta sus pedidos. La diferencia entre ambas cosas es la que mostraron anillo.c y el módulo del tema 24.
Ese poder sin límites es un riesgo, y la manera de administrarlo se resume en un principio:
El principio de mínimo privilegio establece que cada programa y cada usuario deben disponer solo de los permisos necesarios para su tarea, y solo durante el tiempo necesario.
De él derivan las prácticas habituales:
- No se trabaja como
root. Un comando mal escrito con permisos de administrador puede destruir el sistema. Se usa una cuenta común, y se pide el privilegio para el comando que lo requiere. - Los servicios no se ejecutan como
rootsi pueden evitarlo. Es el motivo de las cuentas de sistema de la sección 2. - El privilegio se pide por comando. Es lo que hace
sudo: ejecuta un único programa como administrador, tras comprobar en su configuración que el usuario está autorizado, y lo deja anotado en el registro.
El modelo de «todo o nada» resultó demasiado tosco, y Linux dividió el poder de root en unas cuarenta capacidades (capabilities) independientes: una para cambiar la hora del sistema, otra para usar puertos de red reservados, otra para cargar módulos. Un proceso puede tener unas y no otras.
$ grep CapEff /proc/self/status
CapEff: 0000000000000000
$ sudo grep CapEff /proc/self/status
CapEff: 000001ffffffffff
El valor es un mapa de bits, uno por capacidad. Un proceso común no tiene ninguna; uno de root las tiene todas. Con este mecanismo, un servidor web puede recibir únicamente la capacidad de usar el puerto 80, sin las otras cuarenta. Es lo que usa systemd cuando un archivo de servicio restringe lo que ese servicio puede hacer.
9. Identidad real e identidad efectiva
La regla de la sección 4, según la cual un proceso no puede cambiar de identidad, tropieza con una necesidad práctica. Un usuario debe poder cambiar su contraseña. Las contraseñas están en un archivo que solo root puede modificar. ¿Cómo lo hace el comando passwd, lanzado por un usuario común?
$ ls -l /usr/bin/passwd /usr/bin/sudo
-rwsr-xr-x 1 root root 64152 abr 9 2024 /usr/bin/passwd
-rwsr-xr-x 1 root root 277936 abr 8 2024 /usr/bin/sudo
Donde debería haber una x en los permisos del dueño hay una s. Es un bit adicional, llamado setuid. Cuando execve carga un programa que lo tiene, el proceso pasa a tener como identidad efectiva la del dueño del archivo, en lugar de la de quien lo lanzó. Es la excepción a la regla de que exec conserva las credenciales.
Para eso existen los dos números que mostraba identidad:
| Usuario real | Usuario efectivo | |
|---|---|---|
| Indica | Quién lanzó el programa | Con qué permisos se ejecuta |
| Lo usa el kernel para | Poco: decidir quién puede enviarle señales | Todas las comprobaciones de permisos |
| Normalmente | Son iguales | |
| En un programa setuid | El usuario que lo ejecutó | El dueño del archivo |
Lo comprobamos con nuestro propio programa. Le damos como dueño a root y le activamos el bit:
$ sudo chown root identidad
$ sudo chmod u+s identidad
$ ls -l identidad
-rwsrwxr-x 1 root diego 16480 oct 12 14:10 identidad
$ ./identidad # sin sudo
Usuario real: 1000 (diego)
Usuario efectivo: 0 (root)
Grupo: 1000
Abrir /etc/shadow: permitido
$ sudo rm identidad # no dejar esto en el disco
Un usuario común ejecutó el programa, sin sudo ni contraseña, y el programa abrió un archivo reservado al administrador. El usuario real sigue siendo diego; el efectivo, que es el que el kernel compara, es root. Así funcionan passwd y el propio sudo: son programas de root con el bit setuid, que cualquiera puede ejecutar, y que deciden por sí mismos qué permitirle al usuario real.
Un programa setuid de root es una puerta al privilegio total, y toda su seguridad depende de que el programa no tenga errores. Si acepta una entrada que no validó bien, quien lo ejecuta puede lograr que haga, como root, algo que no estaba previsto. Es una de las formas clásicas de la escalada de privilegios del tema 12. Por eso estos programas son pocos, se revisan con especial cuidado, y la tendencia es reemplazarlos por capacidades. Y por eso strace no funciona sobre ellos, como vimos en el tema 16: el kernel anula el bit cuando el proceso está siendo observado. Si el experimento no dio el resultado esperado, es probable que tu directorio esté en un sistema de archivos montado con la opción nosuid, que lo anula por la misma razón.
10. Lo que el kernel comprueba en cada llamada
Los archivos son el caso más visible, pero la protección abarca todas las llamadas al sistema que afectan a algo fuera del propio proceso. Esta tabla reúne comprobaciones que fuimos encontrando:
| Operación | La puede hacer | La encontramos en |
|---|---|---|
| Abrir un archivo o un dispositivo | Quien tenga los permisos correspondientes. | Temas 14, 28 y 29 |
| Enviar una señal a un proceso | Un proceso del mismo usuario, o root. | Tema 26 |
Observar un proceso con ptrace | Un proceso del mismo usuario, con restricciones adicionales. | Tema 16 |
| Aumentar la propia prioridad | root. | Tema 23 |
| Usar una política de tiempo real | root. | Tema 23 |
| Acceder a puertos de entrada y salida | root. | Tema 8 |
| Crear un archivo de dispositivo | root. | Tema 29 |
| Cargar un módulo en el kernel | root; con arranque seguro, además debe estar firmado. | Tema 24 |
| Montar un sistema de archivos, reiniciar, cambiar la hora | root. | — |
Donde la tabla dice root, lo exacto sería nombrar la capacidad correspondiente. En todos los casos el esquema es el de la sección 1: el kernel recibe la llamada, compara las credenciales del proceso con lo que la operación exige y, si no alcanzan, devuelve un error sin hacer nada. Hay dos códigos de error para esto, que conviene distinguir:
| Código | Mensaje | Significa | Ejemplo |
|---|---|---|---|
EACCES | Permission denied | Los permisos del archivo no lo conceden. | cat /etc/shadow |
EPERM | Operation not permitted | La operación requiere un privilegio que el proceso no tiene. | kill 1, chown root archivo |
El primero se resuelve cambiando permisos; el segundo, solo con privilegio. Reconocer cuál de los dos aparece en un mensaje, o en una traza de strace, ahorra tiempo al diagnosticar.
11. Dónde están las contraseñas
El archivo /etc/passwd debe ser legible por todos, porque cualquier programa necesita traducir números de usuario a nombres. Por eso las contraseñas, que estuvieron allí en los primeros Unix y le dieron el nombre, se mudaron a otro archivo:
$ ls -l /etc/passwd /etc/shadow
-rw-r--r-- 1 root root 2984 sep 3 11:20 /etc/passwd
-rw-r----- 1 root shadow 1532 sep 3 11:20 /etc/shadow
$ sudo grep '^diego:' /etc/shadow
diego:$y$j9T$Xq2…$9fK…:19968:0:99999:7:::
Tampoco allí están las contraseñas. Lo que se guarda es el resultado de aplicarles una función de un solo sentido, un hash: fácil de calcular a partir de la contraseña, e impracticable de invertir. Para verificar una contraseña, el sistema le aplica la misma función y compara los resultados. Ni el administrador puede saber qué contraseña tiene un usuario; solo puede reemplazarla.
El campo resaltado tiene tres partes: un indicador del algoritmo, un valor aleatorio propio de cada cuenta, llamado sal, y el resultado. La sal hace que dos usuarios con la misma contraseña tengan resultados distintos, e impide que un atacante prepare de antemano una tabla de contraseñas comunes ya calculadas.
Aun así el archivo se protege, porque quien lo obtenga puede probar millones de contraseñas candidatas con su propio equipo, sin que el sistema lo note. Es el motivo de que identidad recibiera Permission denied.
Todo este cuidado tiene un límite que señalamos en el tema 21. Las contraseñas las comprueba el sistema en funcionamiento. Quien arranca el equipo con otros parámetros, o desde otro disco, no pasa por esa comprobación, y lee los archivos directamente. Los permisos protegen frente a los procesos del sistema, no frente a quien tiene el disco en la mano; contra eso, la defensa es cifrarlo.
12. Aislar los programas entre sí
El modelo de usuarios y permisos se diseñó en los años setenta para un problema concreto: varias personas que comparten una máquina y no deben interferir entre sí. Lo resuelve bien. Tiene una limitación que entonces no importaba y hoy es la principal:
Todos los programas de un usuario tienen todos los permisos de ese usuario. El kernel distingue a diego de ana; no distingue el editor de texto de diego de un programa que diego descargó ayer de un sitio dudoso. Los dos se ejecutan con el UID 1000, y los dos pueden leer sus documentos, sus claves y los archivos de su navegador.
Conviene precisar qué está protegido y qué no entre dos procesos del mismo usuario:
| Recurso | ¿Protegido de otro proceso del mismo usuario? | Por qué |
|---|---|---|
| La memoria | Sí | Cada proceso tiene su espacio de direcciones. Lo impone el hardware. |
| Los archivos | No | Los permisos se conceden por usuario. |
| La continuidad | No | Un proceso puede enviar señales a los demás del mismo usuario. |
| La red | No | Cualquier proceso puede abrir conexiones. |
En una computadora personal con un solo usuario, que ejecuta programas de miles de autores, la pregunta «¿qué usuario lo pide?» distingue poco. La pregunta útil es «¿qué programa lo pide?», y los sistemas actuales añadieron varios mecanismos para poder hacerla:
| Mecanismo | Idea | Dónde se usa |
|---|---|---|
| Un usuario por programa | Aprovechar el modelo existente: cada aplicación o servicio recibe su propio UID. | Android; los servicios del sistema. |
| Espacios de nombres | Dar a un grupo de procesos su propia vista de los archivos, de los procesos y de la red. No pueden nombrar lo que no ven. | Los contenedores del tema 23. |
| Filtros de llamadas al sistema | Un proceso declara qué llamadas va a necesitar, y el kernel le niega las demás para siempre. | Los navegadores, para las pestañas. |
| Control de acceso obligatorio | Reglas del administrador que indican qué puede hacer cada programa, y que ni el dueño de un archivo puede relajar. | AppArmor en Ubuntu; SELinux en Fedora y Android. |
| Permisos por aplicación | La aplicación no accede a la cámara, los contactos o los archivos hasta que el usuario lo autoriza. | Teléfonos; aplicaciones empaquetadas en el escritorio. |
El conjunto de restricciones dentro del cual se ejecuta un programa del que se desconfía se llama sandbox, «caja de arena». El caso más elaborado es el de un navegador web, que ejecuta código descargado de cualquier sitio: cada pestaña corre en un proceso aparte, sin acceso a los archivos, con casi todas las llamadas al sistema prohibidas, y debe pedir a un proceso principal cada cosa que necesita.
Todos estos mecanismos se aplican en el mismo lugar que los permisos tradicionales: la entrada de las llamadas al sistema. Son reglas adicionales para la misma decisión.
13. Todas las barreras, juntas
La protección de un sistema operativo no es un mecanismo, sino una serie de barreras superpuestas, cada una pensada para lo que las otras no cubren. Reunidas, son estas:
| Barrera | La impone | Impide que un programa… | Tema |
|---|---|---|---|
| Modos de ejecución | El procesador | ejecute instrucciones que controlan el hardware. | 12 |
| Memoria virtual | El procesador, con tablas del kernel | lea o modifique la memoria de otro proceso o del kernel. | 27 |
| Temporizador | Un dispositivo, atendido por el kernel | se quede con la CPU. | 19 |
| Llamadas al sistema | El kernel | entre al kernel por un lugar que no sea una entrada prevista. | 13 |
| Usuarios y permisos | El kernel | use los recursos de otro usuario. | 30 |
| Capacidades | El kernel | tenga más privilegio del que su tarea requiere. | 30 |
| Aislamiento por programa | El kernel | use los recursos de otro programa del mismo usuario. | 30 |
| Arranque seguro | El firmware | reemplace al kernel antes de que arranque. | 20 |
| Cifrado del disco | El kernel, con una clave del usuario | lea los datos saltándose al sistema. | 21 |
Las tres primeras son de hardware, y sin ellas las demás no valdrían nada: de poco sirve negarle a un proceso un archivo si puede leer la memoria del proceso que sí lo abrió. Las del medio son la política del sistema. Las dos últimas cubren los flancos que quedan fuera del sistema en marcha.
Ninguna es perfecta, y cada una ha sido atravesada alguna vez por un defecto. La idea es que el fallo de una no entregue el sistema entero: se llama defensa en profundidad. Un atacante que toma el control de un servicio se encuentra con un usuario sin privilegios; para seguir necesita además un defecto en el kernel, y luego otro que le permita permanecer tras un reinicio.
Con este tema quedan recorridos los cinco servicios del bloque: procesos, memoria, archivos, dispositivos y protección. En el tema 31 los revisamos todos desde un único lugar, el que fuimos usando como ventana durante el curso: los directorios /proc y /sys.
14. Resumen
- La protección decide, en cada llamada al sistema, si el proceso que pide una operación puede hacerla. Se suma a las barreras de hardware.
- Para el kernel, un usuario es un número, el UID. Los nombres están en
/etc/passwdy solo los usan los programas. - El UID 0 es
root; los números bajos son cuentas de servicios; los usuarios comunes empiezan en 1000. - Los grupos permiten conceder un acceso concreto a varios usuarios sin darles privilegio total.
- La identidad está en cada proceso: se hereda con
fork, se conserva conexecy se fija al iniciar sesión. - Un archivo tiene un dueño, un grupo y nueve bits de permisos: lectura, escritura y ejecución para el dueño, el grupo y los demás.
- El kernel aplica la primera clase que corresponde, y solo esa. La comprobación se hace al abrir el archivo.
- En un directorio,
rpermite listar,wañadir y quitar entradas, yxatravesarlo. Borrar un archivo depende del directorio, no del archivo. rootestá exento de las comprobaciones, pero se ejecuta en modo usuario. Su poder puede dividirse en capacidades.- El principio de mínimo privilegio aconseja dar a cada programa solo lo que necesita.
- Un programa con el bit setuid se ejecuta con la identidad efectiva del dueño del archivo. Así funcionan
sudoypasswd. - Las contraseñas se guardan como hashes con sal en
/etc/shadow, legible solo por el administrador. - El modelo tradicional no separa los programas de un mismo usuario. Lo complementan los espacios de nombres, los filtros de llamadas y el control de acceso obligatorio.
15. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿En qué momento decide el kernel si una operación está permitida, y por qué alcanza con ese?
Al entrar a la llamada al sistema que la pide. Alcanza porque las llamadas al sistema son el único camino por el que un programa llega a los recursos que están fuera de su propia memoria.
2. ¿Qué sabe el kernel del usuario «diego»?
Nada con ese nombre. Conoce un número, el UID 1000. La correspondencia entre nombres y números está en /etc/passwd, un archivo que el kernel no lee.
3. ¿Para qué existen las cuentas de usuario con las que nadie inicia sesión?
Para ejecutar servicios. Cada servicio corre con su propia cuenta, de modo que, si es comprometido, el atacante queda limitado a los permisos de esa cuenta.
4. ¿Cómo obtiene su identidad un proceso?
La hereda de su padre al ser creado con fork, y la conserva al ejecutar exec. En el origen, el programa de inicio de sesión, que corre como root, la fija antes de ejecutar la shell del usuario.
5. Traduce rwxr-x--- a octal y explica qué concede.
Es 750. El dueño puede leer, escribir y ejecutar; los miembros del grupo, leer y ejecutar; los demás, nada.
6. Un archivo tiene permisos ----r--r--. ¿Puede leerlo su dueño?
No. El kernel aplica la primera clase que corresponde: el proceso es del dueño, se usan los bits del dueño, que no conceden lectura, y no se consultan los demás.
7. Un proceso abre un archivo, y después alguien le quita el permiso de lectura. ¿Puede seguir leyéndolo?
Sí. Los permisos se comprueban al abrir. Una vez obtenido el descriptor, las lecturas no vuelven a comprobarlos.
8. ¿Qué significa el permiso x en un directorio?
Que se lo puede atravesar: usar los nombres que contiene para llegar a los archivos y subdirectorios que hay dentro. Sin él, nada de lo que contiene es accesible.
9. ¿Por qué se puede borrar un archivo de solo lectura?
Porque borrar es quitar una entrada del directorio. Lo que se necesita es permiso de escritura sobre el directorio; los permisos del archivo no intervienen.
10. ¿Qué puede hacer root que no pueda un usuario común, y qué no puede hacer ninguno de los dos?
root obtiene del kernel cualquier operación que pida: está exento de las comprobaciones de permisos. Ninguno de los dos se ejecuta en modo kernel: no pueden usar instrucciones privilegiadas ni acceder directamente a la memoria del kernel.
11. ¿Qué dice el principio de mínimo privilegio?
Que cada programa y cada usuario deben tener solo los permisos necesarios para su tarea, y solo mientras los necesiten. Así se limita el daño de un error o de un ataque.
12. ¿Qué diferencia hay entre el usuario real y el efectivo?
El real es quien lanzó el programa. El efectivo es el que el kernel usa para comprobar los permisos. Coinciden, salvo en los programas con el bit setuid, donde el efectivo es el dueño del archivo ejecutable.
13. ¿Cómo logra passwd, ejecutado por un usuario común, modificar /etc/shadow?
Su archivo pertenece a root y tiene el bit setuid. Al ejecutarlo, el proceso obtiene a root como usuario efectivo, y el kernel le permite escribir en el archivo.
14. ¿Qué diferencia hay entre EACCES y EPERM?
EACCES indica que los permisos de un archivo no conceden el acceso pedido. EPERM, que la operación requiere un privilegio que el proceso no tiene, como enviar una señal a un proceso ajeno.
15. ¿Qué se guarda en /etc/shadow, y para qué sirve la sal?
El resultado de aplicar a cada contraseña una función de un solo sentido. La sal es un valor aleatorio por cuenta que hace distintos los resultados de contraseñas iguales e impide usar tablas calculadas de antemano.
16. ¿Cuál es la principal limitación del modelo de usuarios y permisos?
Que todos los programas de un usuario tienen todos sus permisos. No protege a un usuario de un programa malicioso que él mismo ejecuta.
Para practicar
- Ejecuta
idy averigua, con/etc/groupy la tabla de la sección 3, qué te concede cada uno de tus grupos. - Cuenta los procesos de cada usuario con
ps -eo user:20 | sort | uniq -c | sort -rn. ¿Qué cuentas de servicio están activas en tu sistema? - Crea un archivo y prueba los permisos 600, 640, 644, 400 y 000. Para cada uno, predice si podrás leerlo y escribirlo antes de intentarlo.
- Crea un directorio con un archivo dentro. Quítale el permiso
xe intenta listarlo, leer el archivo y entrar concd. Repite quitando solor. - Mira los permisos de
/tmpconls -lde identifica el bit de la sección 7. Averigua los permisos de tu directorio personal. - Realiza el experimento de la sección 9 y, antes de borrar el archivo, ejecuta
strace ./identidad. ¿Qué usuario efectivo informa ahora? Explícalo. - Busca los programas setuid de
rootde tu sistema confind /usr/bin -perm -4000 -user root. ¿Para qué necesita el privilegio cada uno? - Provoca un
EACCESy unEPERM, y obsérvalos constrace -Z: pruebacat /etc/shadowykill -0 1. - Mira en
/proc/$$/statuslas líneasUid,GidyGroups, y compáralas con la salida deid. - Averigua con
systemctl catcon qué usuario se ejecuta algún servicio de tu sistema, buscando la líneaUser=. Si no la tiene, ¿con cuál corre?