← Fundamentos de Sistemas Operativos Bloque 7 · Visión general de los servicios · Tema 30 de 34

Tema 30

Usuarios, permisos y protección: visión general

El procesador impide que un programa ejecute instrucciones privilegiadas, y la memoria virtual, que lea la memoria de otro. Falta la tercera barrera: decidir qué puede pedir cada uno. En este tema vemos qué es un usuario para el kernel, cómo se comprueban los permisos en cada llamada al sistema, qué tiene de especial root y qué protege a un usuario de sus propios programas.

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:

ElementoEn un sistema Unix
Quién pideUn 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 hacerLeer, 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.

terminal
$ 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:

CampoEn la línea de diegoSignificado
NombrediegoEl nombre de la cuenta.
ContraseñaxUna marca: la contraseña está en otro archivo. Lo vemos en la sección 11.
UID1000El número de usuario.
GID1000El número de su grupo principal.
DescripciónDiego,,,Texto libre.
Directorio personal/home/diegoDonde empieza su sesión.
Shell/bin/bashEl programa que se ejecuta al iniciar sesión.

Los números se reparten en tres rangos, por convención:

UIDClasePara qué
0rootEl administrador. Es el único número con un significado especial para el kernel.
1 a 999Usuarios del sistemaCuentas para servicios. Nadie inicia sesión con ellas: su shell es nologin.
1000 en adelanteUsuarios comunesLas 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:

terminal
$ 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:

GrupoLo que permite a sus miembrosTema
sudoEjecutar comandos como administrador. En Fedora se llama wheel.Sección 8
admLeer los registros del sistema.22
diskAcceder a los discos directamente. No tiene miembros, y es mejor que siga así.29
plugdev, audio, dialoutUsar 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:

identidad.c
#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;
}
terminal
$ 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:

  1. fork las copia. El hijo nace con las credenciales del padre.
  2. exec las conserva. Cambiar de programa no cambia de identidad, salvo en el caso especial de la sección 9.
  3. 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.

La cadena de permisos de ls -l dividida en cuatro partes: un carácter de tipo, tres caracteres para el dueño, tres para el grupo y tres para los demás. En el ejemplo rwx r-x ---, que en octal es 7 5 0 tipo dueño grupo los demás - r w x r - x - - - 4 + 2 + 1 = 7 4 + 0 + 1 = 5 0 + 0 + 0 = 0 en octal: 750
Tres grupos de tres bits. Cada grupo se resume en un dígito: lectura vale 4, escritura 2 y ejecución 1.

Son tres clases de usuarios, y para cada una, tres permisos. En un archivo regular significan lo que su nombre indica:

LetraPermisoPermiteLlamadas que lo requieren
rLecturaLeer el contenido.open para lectura
wEscrituraModificar el contenido.open para escritura
xEjecuciónEjecutarlo 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:

ComandoEfectoResultado
chmod 640 archivoFija los nueve bits.rw-r-----
chmod 755 archivoFija los nueve bits.rwxr-xr-x
chmod u+x archivoAñade ejecución para el dueño.—
chmod go-w archivoQuita escritura al grupo y a los demás.—
chown usuario:grupo archivoCambia 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:

  1. ¿El usuario efectivo del proceso es 0? Si lo es, el acceso se concede sin mirar los permisos. root
  2. ¿Coincide con el dueño del archivo? Si coincide, se aplican los tres bits del dueño, y solo esos. dueño
  3. ¿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
  4. 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:

terminal
$ 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:

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:

PermisoEn un archivoEn un directorio
rLeer el contenidoListar los nombres que contiene
wModificar el contenidoAñadir, quitar y renombrar entradas
xEjecutarloAtravesarlo: 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:

terminal
$ 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:

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.

terminal
$ 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?

terminal
$ 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 realUsuario efectivo
IndicaQuién lanzó el programaCon qué permisos se ejecuta
Lo usa el kernel paraPoco: decidir quién puede enviarle señalesTodas las comprobaciones de permisos
NormalmenteSon iguales
En un programa setuidEl 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:

terminal
$ 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 mecanismo peligroso

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ónLa puede hacerLa encontramos en
Abrir un archivo o un dispositivoQuien tenga los permisos correspondientes.Temas 14, 28 y 29
Enviar una señal a un procesoUn proceso del mismo usuario, o root.Tema 26
Observar un proceso con ptraceUn proceso del mismo usuario, con restricciones adicionales.Tema 16
Aumentar la propia prioridadroot.Tema 23
Usar una política de tiempo realroot.Tema 23
Acceder a puertos de entrada y salidaroot.Tema 8
Crear un archivo de dispositivoroot.Tema 29
Cargar un módulo en el kernelroot; con arranque seguro, además debe estar firmado.Tema 24
Montar un sistema de archivos, reiniciar, cambiar la horaroot.—

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ódigoMensajeSignificaEjemplo
EACCESPermission deniedLos permisos del archivo no lo conceden.cat /etc/shadow
EPERMOperation not permittedLa 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:

terminal
$ 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 memoriaSíCada proceso tiene su espacio de direcciones. Lo impone el hardware.
Los archivosNoLos permisos se conceden por usuario.
La continuidadNoUn proceso puede enviar señales a los demás del mismo usuario.
La redNoCualquier 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:

MecanismoIdeaDónde se usa
Un usuario por programaAprovechar el modelo existente: cada aplicación o servicio recibe su propio UID.Android; los servicios del sistema.
Espacios de nombresDar 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 sistemaUn 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 obligatorioReglas 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ónLa 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:

BarreraLa imponeImpide que un programa…Tema
Modos de ejecuciónEl procesadorejecute instrucciones que controlan el hardware.12
Memoria virtualEl procesador, con tablas del kernellea o modifique la memoria de otro proceso o del kernel.27
TemporizadorUn dispositivo, atendido por el kernelse quede con la CPU.19
Llamadas al sistemaEl kernelentre al kernel por un lugar que no sea una entrada prevista.13
Usuarios y permisosEl kerneluse los recursos de otro usuario.30
CapacidadesEl kerneltenga más privilegio del que su tarea requiere.30
Aislamiento por programaEl kerneluse los recursos de otro programa del mismo usuario.30
Arranque seguroEl firmwarereemplace al kernel antes de que arranque.20
Cifrado del discoEl kernel, con una clave del usuariolea 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

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

  1. Ejecuta id y averigua, con /etc/group y la tabla de la sección 3, qué te concede cada uno de tus grupos.
  2. 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?
  3. 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.
  4. Crea un directorio con un archivo dentro. Quítale el permiso x e intenta listarlo, leer el archivo y entrar con cd. Repite quitando solo r.
  5. Mira los permisos de /tmp con ls -ld e identifica el bit de la sección 7. Averigua los permisos de tu directorio personal.
  6. Realiza el experimento de la sección 9 y, antes de borrar el archivo, ejecuta strace ./identidad. ¿Qué usuario efectivo informa ahora? Explícalo.
  7. Busca los programas setuid de root de tu sistema con find /usr/bin -perm -4000 -user root. ¿Para qué necesita el privilegio cada uno?
  8. Provoca un EACCES y un EPERM, y obsérvalos con strace -Z: prueba cat /etc/shadow y kill -0 1.
  9. Mira en /proc/$$/status las líneas Uid, Gid y Groups, y compáralas con la salida de id.
  10. Averigua con systemctl cat con qué usuario se ejecuta algún servicio de tu sistema, buscando la línea User=. Si no la tiene, ¿con cuál corre?