1. Atributos del objeto y credenciales del proceso
Los temas anteriores explicaron cómo una ruta llega a un objeto y cómo sus nombres pueden cambiar. Comenzamos ahora el bloque de protección: localizar un archivo no significa que cualquier proceso pueda leerlo, modificarlo o ejecutarlo.
Permisos clásicos: nueve bits asociados al objeto que describen lectura, escritura y ejecución o búsqueda para tres clases: propietario, grupo y otros. Su interpretación exige conocer también las credenciales del proceso y la operación solicitada.
Objeto:
UID propietario = 1000
GID del archivo = 2000
modo = 0640
Proceso:
identidad usada para acceder al sistema de archivos
grupo usado para esa comprobación
lista de grupos suplementarios
Operación:
leer, escribir, ejecutar, buscar un nombre, crear una entrada...El propietario es un UID y el grupo del objeto es un GID. Los nombres que muestra ls, como ana o equipo, son una presentación obtenida mediante los servicios de cuentas del entorno. El kernel no necesita comparar literalmente esas cadenas para seleccionar una clase.
Un archivo tiene un grupo propietario, pero un proceso puede pertenecer a varios grupos. Los grupos suplementarios importan al comprobar si corresponde la clase de grupo. Que un usuario haya sido incorporado a un grupo en la base de cuentas no obliga a que un proceso ya iniciado haya renovado sus credenciales.
Los atributos se consultan con stat o variantes y las credenciales con llamadas como getuid, geteuid, getgid, getegid y getgroups. La referencia inode(7) describe los campos del objeto; credentials(7) describe las identidades del proceso.
2. Las tres clases de permisos
| Clase | Letra habitual | Cuándo se selecciona en el modelo clásico |
|---|---|---|
| Propietario | u | La identidad de acceso coincide con el UID propietario. |
| Grupo | g | No es el propietario y el GID del objeto coincide con el grupo de acceso o alguno suplementario. |
| Otros | o | No corresponde ninguna de las dos clases anteriores. |
Otros no es un grupo concreto de la base de cuentas: es la clase restante de esa comprobación. La palabra grupo en el modo se refiere al GID del archivo, no a cualquiera de los grupos existentes en el sistema.
En este tema usaremos procesos sin privilegios especiales y objetos sin ACL extendida para explicar la selección básica. Esa declaración permite razonar con precisión; después señalaremos qué otras condiciones pueden intervenir en una operación real.
La clase se selecciona para cada objeto consultado. Un proceso puede ser propietario de un archivo pero entrar por la clase de grupo en uno de los directorios de su ruta. No existe una clase única que se aplique automáticamente a todo el recorrido.
3. Leer los nueve bits rwx
-rw-r-----
│└─┬┘└─┬┘└─┬┘
│ │ │ └─ otros: ---
│ │ └───── grupo: r--
│ └───────── propietario: rw-
└──────────── tipo de objeto: archivo regular
Los nueve permisos son rw- r-- ---.
La primera posición no es otro bit de permiso.Dentro de cada terna, r habilita lectura, w escritura y x ejecución o búsqueda según el tipo. Un guion indica ausencia de ese bit. Las letras aparecen en posiciones fijas; rw- no es lo mismo que r-x.
El primer carácter de ls -l expresa el tipo: por ejemplo, - para regular, d para directorio y l para enlace simbólico. Los bits de tipo y los permisos viven dentro de st_mode, pero se interpretan mediante máscaras distintas. Para comprobar tipo se usan macros como S_ISREG y S_ISDIR.
En sistemas reales pueden aparecer s, S, t o T en posiciones de ejecución, y un indicador adicional de ACL u otro contexto. No son errores de impresión: se relacionan con bits especiales y atributos que estudiaremos en el tema 28. El decodificador básico de este tema muestra solo rwx y declara ese alcance.
$ id
$ ls -ld -- .
$ stat -c 'nombre=%n tipo=%F uid=%u gid=%g modo=%a simbolico=%A' -- .Los enlaces duros del mismo archivo comparten estos atributos. En Linux, los permisos ordinarios mostrados sobre el objeto enlace simbólico no conceden acceso a su destino, como vimos en el tema 24.
4. Representación octal
Cada terna usa tres bits: r vale 4, w vale 2 y x vale 1. Su suma forma una cifra octal de cero a siete. Tres cifras representan propietario, grupo y otros, en ese orden.
| Cifra | Bits | Terna | Capacidades de esa clase |
|---|---|---|---|
| 0 | 000 | --- | Ninguna de las tres. |
| 1 | 001 | --x | Ejecución o búsqueda. |
| 2 | 010 | -w- | Escritura. |
| 3 | 011 | -wx | Escritura y ejecución o búsqueda. |
| 4 | 100 | r-- | Lectura. |
| 5 | 101 | r-x | Lectura y ejecución o búsqueda. |
| 6 | 110 | rw- | Lectura y escritura. |
| 7 | 111 | rwx | Las tres capacidades. |
0600 → rw- --- ---
0640 → rw- r-- ---
0644 → rw- r-- r--
0750 → rwx r-x ---
0755 → rwx r-x r-x
0004 → --- --- r--
En C: 0640 es octal; 640 sin cero inicial es decimal.
Las máscaras S_IRUSR, S_IWUSR, etc. evitan depender de cifras al nombrar bits.El cero inicial del literal de C no representa otra clase de permisos. Un modo presentado con cuatro cifras puede incluir en su primera cifra los bits especiales; este tema se centra en las tres cifras bajas. Tampoco se suman cifras de clases distintas para calcular lo que recibe un proceso.
5. Seleccionar una clase, sin mezclarla con otras
El error más frecuente consiste en buscar cualquier r, w o x disponible entre las nueve posiciones. En el modelo clásico se selecciona primero una sola clase y después se comprueba si contiene todos los bits requeridos.
si UID_de_acceso == UID_del_archivo:
permisos = terna de propietario
si no, si GID_del_archivo está entre los grupos de acceso del proceso:
permisos = terna de grupo
si no:
permisos = terna de otros
permitir si (permisos & solicitados) == solicitados
denegar en caso contrarioNo se vuelve a probar grupo u otros porque el propietario tenga pocos permisos. Tampoco se usa otros como alternativa cuando el proceso pertenece al grupo del archivo y la terna de grupo no autoriza la operación.
| Situación | Modo | Clase seleccionada | Lectura |
|---|---|---|---|
| El proceso es propietario. | 0004 | Propietario: --- | Denegada, aunque otros tenga r. |
| No es propietario; pertenece al grupo. | 0604 | Grupo: --- | Denegada, aunque otros tenga r. |
| No coincide UID ni grupo. | 0640 | Otros: --- | Denegada. |
| No es propietario; coincide un grupo suplementario. | 0640 | Grupo: r-- | Permitida por estos bits. |
Una solicitud combinada rw exige ambos bits dentro de la clase elegida. No basta con que propietario aporte r y grupo aporte w. En una apertura O_RDWR hacen falta las condiciones de lectura y escritura correspondientes, además del resto de comprobaciones de la operación.
Si el propietario denegado puede cambiar el modo en su contexto, podría modificar después la situación; eso es otra operación. Ser propietario no equivale a que una lectura actual ignore sus bits de acceso.
6. Qué significan rwx en un archivo regular
- r: permite obtener contenido mediante una apertura de lectura, si el resto del recorrido y de la política lo admiten.
- w: permite modificar contenido mediante operaciones de escritura admitidas. No concede por sí solo cambiar propietario o cualquier metadato.
- x: interviene en la autorización para ejecutar el archivo como programa. No convierte cualquier secuencia de bytes en un ejecutable válido.
Un archivo de datos puede tener x y seguir sin un formato que el sistema pueda ejecutar. Un script requiere también las condiciones de su intérprete y del acceso a su texto. Un montaje noexec puede impedir ejecución aunque el modo muestre x.
El permiso w del archivo no equivale a permiso para borrar su nombre. Retirar una entrada modifica su directorio padre. Del mismo modo, cambiar el modo o la propiedad tiene sus propias reglas; no son una consecuencia automática de tener escritura del contenido.
El modo de apertura también importa. Un descriptor obtenido con O_RDONLY no se transforma en descriptor de escritura porque los bits del objeto permitan w. La autorización del objeto y las capacidades con las que se abrió el descriptor son conceptos relacionados pero distintos.
Las comprobaciones de acceso ordinarias se realizan al abrir y en otros puntos definidos por cada operación. Cambiar el modo después no revoca de forma general un descriptor regular ya abierto; tampoco garantiza que todas sus operaciones futuras vayan a tener éxito bajo cualquier política o estado del sistema.
7. Qué significan rwx en un directorio
El tipo directorio cambia la interpretación práctica: r permite enumerar nombres, x permite buscar y atravesar, y w interviene en modificar sus entradas. Para crear o retirar entradas normalmente se necesitan w y x en el padre, además de las demás condiciones.
| Terna elegida | Posibilidad básica | Limitación |
|---|---|---|
| r-- | Leer nombres del directorio. | No concede búsqueda para consultar los objetos por esos nombres. |
| --x | Buscar un nombre conocido y atravesar. | No concede enumerar la lista de nombres. |
| -wx | Modificar entradas cuyos nombres se conocen, si lo demás lo permite. | No concede leer la lista completa. |
| r-x | Enumerar y buscar. | No concede crear o retirar entradas. |
| rw- | Lectura de nombres y bit de escritura presente. | La falta de búsqueda impide las modificaciones normales por nombre. |
Con r sin x, una enumeración de nombres puede funcionar pero un listado que intente mostrar atributos de cada hijo puede fallar. Con x sin r puede abrirse un hijo conocido si tiene los permisos necesarios, aunque listar el directorio no esté permitido.
La herramienta puede solicitar más de lo mínimo necesario para una tarea abstracta. Por ejemplo, un programa que abra explícitamente el directorio con O_RDONLY necesitará condiciones para leerlo, aunque otra llamada pudiera buscar un hijo por ruta sin enumerarlo.
El bit x de un directorio no significa ejecutar sus nombres como programas. Es búsqueda. Esta distinción explica por qué un archivo legible puede resultar inaccesible si uno de sus directorios intermedios no permite atravesarlo.
8. Resolver rutas, crear y eliminar entradas
/proyecto/datos/informe.txt
│ │ │
│ │ └─ lectura: r en la clase del archivo
│ └────────── búsqueda: x en la clase de datos
└────────────────── búsqueda: x en la clase de proyecto
También deben poder atravesarse los demás directorios anteriores.| Operación | Requisitos básicos relevantes | No exige por ese motivo |
|---|---|---|
| Leer archivo por nombre | x en directorios atravesados y r en el archivo. | Enumerar todos los padres. |
| Crear archivo nuevo | Acceso al prefijo y w+x en el padre. | Un permiso previo del archivo inexistente. |
| Retirar un nombre de archivo | Acceso al prefijo y w+x en el padre, sujeto a reglas adicionales. | w del contenido del archivo. |
| Renombrar entre padres | Condiciones de ambos padres y de los objetos involucrados. | Copiar todo el contenido. |
Un usuario puede no tener escritura sobre un archivo y sí poder retirar su nombre en un directorio que controla, si no interviene una restricción adicional. Sticky bit, atributos, solo lectura y políticas de seguridad se estudiarán o recordarán según su alcance; no se deben omitir al interpretar un fallo real.
La referencia de resolución de rutas explica las comprobaciones de búsqueda. Que r del archivo sea suficiente en un modelo de un solo objeto no autoriza a ignorar el recorrido necesario para llegar hasta él.
9. Credenciales reales y límites del modelo
Un proceso tiene UID y GID reales y efectivos, además de identidades y capacidades adicionales. En Linux, el acceso ordinario al sistema de archivos utiliza fsuid y fsgid, que normalmente siguen a los efectivos, junto con los grupos suplementarios. No es correcto usar siempre el UID real como si fuera el que decide cada apertura.
En nuestros programas no se cambian credenciales ni se añaden capacidades. El inspector muestra identificadores reales y efectivos y la lista suplementaria, pero no pretende demostrar por sí solo el estado completo de seguridad del proceso.
- ACL: pueden introducir entradas y una máscara que amplían el algoritmo de selección de los nueve bits. Se desarrollarán en el tema 28.
- Capacidades: determinadas capacidades pueden superar comprobaciones discrecionales. «Root ignora todo» no describe todas las operaciones ni todos los límites.
- Estado del montaje y atributos: solo lectura, noexec, inmutabilidad y otras condiciones pueden impedir una operación admitida por los bits.
- Políticas adicionales: mecanismos de control de acceso y sistemas remotos pueden imponer más condiciones.
La referencia de capabilities(7) detalla privilegios específicos de Linux. Los casos del laboratorio que esperan una denegación están dirigidos a una cuenta ordinaria sin privilegios ni ACL extendida que altere el resultado.
access plantea una consulta con credenciales y contrato diferentes de una apertura ordinaria. Tampoco evita que el árbol cambie antes de usarlo. En este tema no se usa para autorizar una apertura: se intenta la operación y se comprueba el resultado. El tema 29 estudiará access y la carrera TOCTOU.
10. Programa en C: observar atributos y credenciales
El inspector consulta una ruta con lstat y muestra identificadores numéricos, modo y grupos del proceso. No cambia permisos ni pretende calcular una autorización completa. Si la ruta final es un simbólico sin barra final, describe el propio enlace.
#define _POSIX_C_SOURCE 200809L
#define _FILE_OFFSET_BITS 64
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, "Uso: %s RUTA\n", argv[0]);
return 1;
}
struct stat s;
if (lstat(argv[1], &s) == -1) { perror("lstat"); return 1; }
int n = getgroups(0, NULL);
if (n == -1) { perror("getgroups cantidad"); return 1; }
if ((uintmax_t)n > SIZE_MAX / sizeof(gid_t)) {
fputs("Lista de grupos no representable\n", stderr);
return 1;
}
gid_t *grupos = n ? malloc((size_t)n * sizeof *grupos) : NULL;
if (n && !grupos) { perror("malloc grupos"); return 1; }
int obtenidos = n ? getgroups(n, grupos) : 0;
if (obtenidos == -1) { perror("getgroups lista"); free(grupos); return 1; }
printf("proceso uid_real=%ju uid_efectivo=%ju gid_real=%ju gid_efectivo=%ju\n",
(uintmax_t)getuid(), (uintmax_t)geteuid(),
(uintmax_t)getgid(), (uintmax_t)getegid());
fputs("grupos_suplementarios=", stdout);
if (!obtenidos) fputs("(ninguno)", stdout);
for (int i = 0; i < obtenidos; ++i)
printf("%s%ju", i ? "," : "", (uintmax_t)grupos[i]);
putchar('\n');
free(grupos);
const mode_t mascara[9] = {
S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP,
S_IROTH, S_IWOTH, S_IXOTH
};
const char *letras = "rwxrwxrwx";
char basicos[10];
for (int i = 0; i < 9; ++i)
basicos[i] = s.st_mode & mascara[i] ? letras[i] : '-';
basicos[9] = '\0';
const char *tipo = S_ISREG(s.st_mode) ? "regular" :
S_ISDIR(s.st_mode) ? "directorio" :
S_ISLNK(s.st_mode) ? "enlace" : "otro";
printf("objeto tipo=%s uid=%ju gid=%ju modo=%04o basicos=%s\n",
tipo, (uintmax_t)s.st_uid, (uintmax_t)s.st_gid,
(unsigned)(s.st_mode & 07777), basicos);
puts("alcance=atributos_y_credenciales_no_autorizacion_completa");
if (fflush(stdout) == EOF) { perror("fflush stdout"); return 1; }
return 0;
}$ gcc -std=c11 -Wall -Wextra -Wpedantic -O2 ver-permisos.c -o ver-permisos
$ ./ver-permisos .El modo octal conserva los bits especiales bajos para no ocultar que podrían existir, pero basicos solo decodifica los nueve bits rwx. Por eso no reproduce las letras s o t de ls. Los identificadores se muestran como números sin consultar nombres de cuentas.
Getgroups no garantiza que la lista incluya el GID efectivo, por lo que este se muestra aparte. El programa no cambia grupos ni ejecuta operaciones concurrentes que los cambien entre sus consultas; si la segunda consulta falla, lo informa. La referencia de getgroups(2) describe esa interfaz.
11. Programa en C: evaluar el modelo clásico
El evaluador siguiente trabaja con identificadores hipotéticos recibidos como texto. No cambia UID, GID ni grupos del proceso y no intenta abrir ningún archivo. Calcula únicamente la selección de clase y los bits solicitados.
Recibe modo, UID y GID del objeto, UID y GID de acceso del proceso, lista suplementaria separada por comas o un guion si está vacía, y una petición no vacía formada por r, w y x sin repeticiones. Admite identificadores decimales de 0 a 4294967294. Incluso UID cero se trata como un número ordinario: este modelo no incluye privilegios.
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static int leer_id(const char *s, size_t n, uintmax_t *valor)
{
const uintmax_t limite = UINT32_MAX - (uintmax_t)1;
uintmax_t x = 0;
if (n == 0) return -1;
for (size_t i = 0; i < n; ++i) {
if (s[i] < '0' || s[i] > '9') return -1;
unsigned d = (unsigned)(s[i] - '0');
if (x > (limite - d) / 10) return -1;
x = x * 10 + d;
}
*valor = x;
return 0;
}
static int leer_modo(const char *s, unsigned *modo)
{
size_t n = strlen(s);
if (n == 4 && s[0] == '0') { ++s; --n; }
if (n != 3) return -1;
unsigned m = 0;
for (size_t i = 0; i < n; ++i) {
if (s[i] < '0' || s[i] > '7') return -1;
m = m * 8 + (unsigned)(s[i] - '0');
}
*modo = m;
return 0;
}
static int en_grupos(const char *s, uintmax_t gid, int *pertenece)
{
if (!strcmp(s, "-")) return 0;
const char *p = s;
for (;;) {
const char *fin = strchr(p, ',');
size_t n = fin ? (size_t)(fin - p) : strlen(p);
uintmax_t grupo;
if (leer_id(p, n, &grupo) == -1) return -1;
if (grupo == gid) *pertenece = 1;
if (!fin) return 0;
p = fin + 1;
}
}
int main(int argc, char **argv)
{
if (argc != 8) {
fprintf(stderr, "Uso: %s MODO UID_OBJ GID_OBJ UID_PROC GID_PROC GRUPOS PETICION\n", argv[0]);
return 2;
}
unsigned modo, pedido = 0;
uintmax_t uobj, gobj, uproc, gproc;
if (leer_modo(argv[1], &modo) == -1 ||
leer_id(argv[2], strlen(argv[2]), &uobj) == -1 ||
leer_id(argv[3], strlen(argv[3]), &gobj) == -1 ||
leer_id(argv[4], strlen(argv[4]), &uproc) == -1 ||
leer_id(argv[5], strlen(argv[5]), &gproc) == -1) goto invalido;
int grupo = gobj == gproc;
if (en_grupos(argv[6], gobj, &grupo) == -1 || !argv[7][0]) goto invalido;
for (const char *p = argv[7]; *p; ++p) {
unsigned bit = *p == 'r' ? 4 : *p == 'w' ? 2 : *p == 'x' ? 1 : 0;
if (!bit || (pedido & bit)) goto invalido;
pedido |= bit;
}
unsigned bits;
const char *clase;
if (uproc == uobj) { clase = "propietario"; bits = (modo >> 6) & 7; }
else if (grupo) { clase = "grupo"; bits = (modo >> 3) & 7; }
else { clase = "otros"; bits = modo & 7; }
char terna[4] = { bits & 4 ? 'r' : '-', bits & 2 ? 'w' : '-',
bits & 1 ? 'x' : '-', '\0' };
int permite = (bits & pedido) == pedido;
printf("clase=%s bits=%u terna=%s solicitud=%u resultado=%s\n",
clase, bits, terna, pedido, permite ? "permitido" : "denegado");
if (fflush(stdout) == EOF) { perror("fflush stdout"); return 2; }
return permite ? 0 : 1;
invalido:
fputs("Entrada inválida: modo, identificadores, grupos o petición\n", stderr);
return 2;
}$ gcc -std=c11 -Wall -Wextra -Wpedantic -O2 evaluar-rwx.c -o evaluar-rwx
$ ./evaluar-rwx 004 1000 2000 1000 3000 - r
$ ./evaluar-rwx 604 1000 2000 1001 3000 2000 r
$ ./evaluar-rwx 640 1000 2000 1001 3000 2000 rLos dos primeros devuelven 1 de manera esperada: no hay alternativa automática a otra terna. El tercero devuelve 0 por pertenencia suplementaria al grupo. El estado 2 indica entrada inválida o fallo de salida, distinto de una denegación calculada.
No se debe usar el resultado de este programa como autorización previa de una operación real. Omite permisos del prefijo, ACL, capacidades, atributos, estado del montaje y cambios concurrentes. Su propósito es hacer comprobable el algoritmo de los bits clásicos.
12. Laboratorio de decisiones y acceso real
Usa Linux o WSL con GCC y Python 3, una cuenta ordinaria sin capacidades especiales y un árbol de archivos propios. En WSL trabaja preferentemente dentro del directorio personal de Linux. No hace falta crear usuarios, cambiar propietarios ni ejecutar como administrador.
En este tema solo fijaremos modos de objetos nuevos del ensayo para observar sus efectos. El uso sistemático de chmod, chown y umask se desarrollará en el tema 27. No se modifican permisos de otros directorios ni políticas globales.
$ LAB26=$(mktemp -d "$HOME/tema26.XXXXXX")
$ cd -- "$LAB26"
$ id
$ findmnt -T .
$ gcc -std=c11 -Wall -Wextra -Wpedantic -O2 ver-permisos.c -o ver-permisos
$ gcc -std=c11 -Wall -Wextra -Wpedantic -O2 evaluar-rwx.c -o evaluar-rwxComprobar la selección de clase sin cambiar las credenciales
El primer verificador usa valores hipotéticos. Incluye coincidencia por grupo primario y suplementario, prioridad del propietario, solicitudes combinadas e identidades distintas. No intenta hacerse pasar por esos usuarios en el sistema.
import subprocess
modos = ("000", "004", "040", "044", "400", "444", "604", "640",
"0644", "0750", "755", "777")
peticiones = {"r": 4, "w": 2, "x": 1, "rw": 6, "rx": 5, "wx": 3, "rwx": 7}
escenarios = [
(1000, 3000, "-", "propietario", 0),
(1000, 2000, "2000", "propietario", 0),
(1001, 2000, "-", "grupo", 1),
(1001, 3000, "5000,2000", "grupo", 1),
(1001, 3000, "5000", "otros", 2),
]
casos = 0
for modo in modos:
ternas = [int(c, 8) for c in modo[-3:]]
for uid, gid, grupos, clase, posicion in escenarios:
for peticion, bits in peticiones.items():
p = subprocess.run(["./evaluar-rwx", modo, "1000", "2000",
str(uid), str(gid), grupos, peticion],
capture_output=True, text=True)
permite = all(ternas[posicion] & b for b in (4, 2, 1) if bits & b)
assert p.returncode == (0 if permite else 1), p.stderr
campos = dict(c.split("=", 1) for c in p.stdout.split())
assert campos["clase"] == clase
assert int(campos["bits"]) == ternas[posicion]
assert campos["resultado"] == ("permitido" if permite else "denegado")
casos += 1
validos = ["640", "1000", "2000", "1001", "3000", "-", "r"]
for indice, valor in [(0, "888"), (0, "1640"), (1, "-1"),
(1, "4294967295"), (5, "2000,"),
(5, "2000,,3000"), (6, "rr"), (6, "")]:
argumentos = validos.copy()
argumentos[indice] = valor
p = subprocess.run(["./evaluar-rwx", *argumentos], capture_output=True)
assert p.returncode == 2 and p.stderr and not p.stdout
print(f"{casos} decisiones correctas; entradas inválidas rechazadas")$ python3 verificar-modelo-rwx.pyObservar las decisiones reales sobre archivos propios
El segundo verificador realiza aperturas y operaciones de directorio. Detecta el caso de ejecución como root y termina con 77 para indicar que omite una prueba que requiere denegaciones sin privilegios. Si otra política o capacidad altera los resultados, conserva el fallo y describe el entorno; no lo resuelvas ampliando permisos ajenos.
from pathlib import Path
import errno
import os
import subprocess
import sys
import tempfile
if os.geteuid() == 0:
print("Prueba omitida: usa una cuenta ordinaria sin privilegios", file=sys.stderr)
raise SystemExit(77)
base = Path(tempfile.mkdtemp(prefix="prueba-permisos-", dir="."))
archivo = base / "dato.txt"
archivo.write_bytes(b"datos\n")
def abrir(ruta, flags, permitido):
try:
fd = os.open(ruta, flags | os.O_CLOEXEC)
except OSError as e:
assert not permitido and e.errno == errno.EACCES, e
else:
try:
assert permitido, "La apertura debía ser denegada en este entorno"
finally:
os.close(fd)
for modo, lectura, escritura in [(0o004, False, False),
(0o040, False, False),
(0o400, True, False),
(0o200, False, True),
(0o600, True, True)]:
os.chmod(archivo, modo)
abrir(archivo, os.O_RDONLY, lectura)
abrir(archivo, os.O_WRONLY, escritura)
abrir(archivo, os.O_RDWR, lectura and escritura)
os.chmod(archivo, 0o600)
fd = os.open(archivo, os.O_RDWR | os.O_CLOEXEC)
try:
os.chmod(archivo, 0)
abrir(archivo, os.O_RDONLY, False)
assert os.pread(fd, 6, 0) == b"datos\n"
finally:
os.close(fd)
os.chmod(archivo, 0o600)
d = base / "directorio"
d.mkdir()
hijo = d / "legible"
hijo.write_bytes(b"hijo\n")
os.chmod(hijo, 0o400)
try:
os.chmod(d, 0o100) # x sin r: buscar un nombre conocido
assert hijo.read_bytes() == b"hijo\n"
try:
os.listdir(d)
except PermissionError:
pass
else:
raise AssertionError("La enumeración sin r debía fallar")
os.chmod(d, 0o400) # r sin x: nombres sin búsqueda
assert "legible" in os.listdir(d)
try:
os.stat(hijo)
except PermissionError:
pass
else:
raise AssertionError("La consulta del hijo sin búsqueda debía fallar")
os.chmod(d, 0o300) # w+x sin r: entradas conocidas
nuevo = d / "nuevo"
f = os.open(nuevo, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
os.close(f)
os.unlink(hijo) # no exige w del contenido del hijo
finally:
os.chmod(d, 0o700)
assert sorted(p.name for p in d.iterdir()) == ["nuevo"]
p = subprocess.run(["./ver-permisos", str(archivo)], capture_output=True, text=True)
assert p.returncode == 0, p.stderr
assert "modo=0600 basicos=rw-------" in p.stdout
print(p.stdout, end="")
print("Correctos: selección del propietario, aperturas, descriptor existente y r/w/x de directorio")
print("Árbol conservado:", base)$ python3 verificar-acceso-real.pyEl archivo de modo 0004 pertenece al ejecutor: tener r para otros no le concede lectura porque se selecciona propietario. Las pruebas de grupo y otros se validan con identificadores hipotéticos en el primer programa; este laboratorio no cambia las credenciales reales para forzar esas clases.
El bloque finally restituye acceso al directorio del ensayo incluso si una comprobación intermedia falla. La prueba sobre el descriptor ya abierto demuestra el comportamiento ordinario observado en ese entorno, no una promesa de que permisos o políticas no puedan afectar nunca a ninguna operación posterior.
13. Simuladores de bits y operaciones
Seleccionar la clase y comprobar una petición
El primer modelo permite editar los nueve bits o aplicar un modo octal, introducir identificadores hipotéticos y elegir qué permisos se solicitan. Los cambios no afectan a ningún archivo ni a las credenciales reales del equipo.
Propietario, grupo u otros: una sola terna
| Clase | r | w | x |
|---|---|---|---|
| Propietario | |||
| Grupo | |||
| Otros |
| Clase | Terna | Cifra | Uso en esta comprobación |
|---|
Con los valores iniciales se selecciona grupo por pertenencia suplementaria, pero w se deniega. Cambia la solicitud a r y calcula: debe permitirse por los bits. Después prueba UID de proceso 1000 y modo 004 para observar que otros no sirve de alternativa al propietario.
Las casillas recalculan el modo; al editar los campos de texto o la solicitud, pulsa el botón para aplicar el escenario. Usa grupos separados por comas sin espacios o un guion para ninguno. El modelo no incluye permisos del prefijo, ACL, capacidades, atributos ni estado del montaje. UID cero tampoco recibe privilegios especiales en este cálculo hipotético.
La operación decide qué objetos importan
El segundo panel representa /proyecto/datos/informe.txt. Cada terna ya es la clase seleccionada para ese objeto: puede ser distinta en cada nivel. El resto del prefijo permite búsqueda. Para crear se usa un nombre nuevo dentro de datos y el modo del archivo mostrado no interviene.
Recorrido, objeto final y directorio padre
| Objeto | Terna elegida | Bits requeridos | Resultado de este paso |
|---|
Quita x de proyecto y observa que r del archivo no basta para abrirlo. Después permite x en proyecto, elige datos=r-- y enumera nombres: puede pasar sin búsqueda en datos. Consultar atributos del hijo conocido exige x en ese padre. Con datos=-wx puedes retirar el nombre aunque informe.txt no tenga w.
El montaje de solo lectura demuestra otra condición independiente: los bits pueden ser suficientes y la modificación seguir impedida. El panel comprueba primero sus máscaras para explicar los pasos; no promete el orden exacto de errores que devuelve cualquier operación real. No simula sticky, ACL, políticas de seguridad, permisos de directorios adicionales ni concurrencia.
14. Diagnosticar una denegación
- Identifica la operación. Leer contenido, abrir para escribir, enumerar, buscar, crear o retirar una entrada no exigen lo mismo.
- Identifica las credenciales del proceso real. UID/GID de acceso y grupos vigentes; no solo los nombres de la cuenta.
- Consulta los objetos relevantes. Archivo y directorios del prefijo, distinguiendo enlaces propios de destinos seguidos.
- Selecciona la clase en cada objeto. Propietario primero; después grupo; finalmente otros. No combines las ternas.
- Comprueba todos los bits necesarios. Una solicitud combinada debe satisfacerse completa en la terna elegida.
- Revisa condiciones adicionales. ACL, capacidades, montaje, atributos y política aplicable cuando el modelo básico no explica el resultado.
- Conserva el error efectivo. EACCES, EROFS, ENOENT y otros fallos no describen la misma causa.
Para presentar el recorrido, herramientas como namei -l pueden mostrar los componentes y atributos de una ruta. Su consulta también está sujeta al entorno visible. No se obtiene una instantánea completa de seguridad leyendo una sola fila de ls.
Un signo + junto al modo puede indicar una ACL que requiere otra interpretación. La máscara de ACL puede influir en los bits de grupo mostrados. No deduzcas los permisos efectivos de todas sus entradas sin consultar la ACL; el tema 28 desarrollará esa relación.
La propiedad del archivo puede permitir cambiar su modo bajo ciertas condiciones, pero eso no demuestra que la operación denegada ya estuviera autorizada. Diagnosticar y cambiar permisos son pasos diferentes; el tema 27 tratará los cambios con alcance concreto.
La información observada puede cambiar antes de la apertura. El código de aplicación debe comprobar el resultado de la operación real, como explica open(2), y no usar una predicción del modo como garantía futura.
15. Resumen
- El objeto tiene UID, GID y modo; el proceso tiene identidades y grupos que intervienen en el acceso.
- Los nueve bits se organizan en propietario, grupo y otros, con ternas rwx.
- Cada cifra octal suma r=4, w=2 y x=1 dentro de una terna.
- Se selecciona una clase: propietario tiene prioridad; después grupo; en otro caso, otros.
- Una denegación en la clase elegida no habilita probar otra terna como alternativa.
- Los grupos suplementarios pueden seleccionar la clase de grupo.
- En un directorio, r enumera nombres, x permite búsqueda y w interviene en modificar entradas.
- Leer el archivo exige considerar búsqueda del prefijo; retirar su nombre depende del padre y otras reglas.
- Las aperturas y los descriptores tienen sus propios modos de uso; los bits del objeto no sustituyen ese estado.
- ACL, capacidades, montajes y políticas pueden ampliar o restringir la decisión respecto del modelo básico.
- Los evaluadores hipotéticos explican el algoritmo; las operaciones reales deben comprobar su propio resultado.
16. Preguntas de repaso y práctica
1. ¿Propietario y grupo se guardan como nombres de texto?
Los campos del objeto son identificadores numéricos UID y GID. Las herramientas pueden resolverlos a nombres mediante los servicios de cuentas del entorno.
2. ¿Otros es un grupo concreto de usuarios?
No. Es la clase restante para una comprobación cuando no se selecciona propietario ni grupo.
3. ¿Qué representa el primer carácter de -rw-r-----?
El tipo de objeto. Los nueve permisos comienzan después. En ese ejemplo el objeto es regular y el modo básico es 0640.
4. ¿Cómo se representa r-x en octal?
Con 5, porque r aporta 4 y x aporta 1. La terna -wx es 3 y rw- es 6.
5. ¿Por qué se escribe 0640 en un literal de C?
El cero inicial indica base octal. El literal 640 sin ese cero es decimal y no representa el mismo conjunto de bits.
6. ¿El propietario de un archivo 0004 puede leerlo por otros?
No en el modelo clásico sin privilegios ni ACL extendida. Se selecciona propietario, cuya terna no tiene lectura; no hay alternativa a otros.
7. ¿Un miembro del grupo puede usar otros si grupo le deniega?
No en ese modelo. Una vez seleccionada grupo se evalúa su terna, sin recurrir a otros.
8. ¿Importan los grupos suplementarios?
Sí. Si no se seleccionó propietario, la coincidencia del GID del objeto con alguno de ellos puede seleccionar grupo.
9. ¿Un archivo tiene tantos grupos como el proceso?
No. Tiene un GID propietario, aunque el proceso pueda pertenecer a varios grupos.
10. ¿Una solicitud rw puede combinar r de propietario con w de grupo?
No. Ambos bits deben estar presentes en la terna seleccionada para esa comprobación.
11. ¿x convierte cualquier archivo en un programa ejecutable?
No. Es una condición de autorización, no una conversión de formato. También importan formato, intérprete, montaje y otras políticas.
12. ¿r de un directorio permite necesariamente consultar todos sus hijos?
No. Permite enumerar nombres. Para buscar los hijos por nombre se necesita x en ese directorio y cumplir los demás requisitos.
13. ¿Puede abrirse un hijo conocido sin r de su padre?
Puede, si existe búsqueda x en el padre y el resto de la ruta y del objeto lo permite. No hace falta enumerar todos los nombres para buscar uno conocido.
14. ¿w del archivo autoriza a borrar su nombre?
No por sí solo. La eliminación modifica la entrada del directorio padre y requiere sus condiciones, además de restricciones como sticky cuando correspondan.
15. ¿Un archivo con todos los bits puede ser inaccesible por su ruta?
Sí. Un directorio anterior puede impedir búsqueda u otra política puede denegar la operación.
16. ¿Getgroups siempre incluye el GID efectivo?
No está garantizado. Se consulta y considera el GID efectivo por separado de la lista suplementaria.
17. ¿Las aperturas ordinarias usan siempre el UID real?
No. En Linux intervienen fsuid y fsgid, normalmente asociados a los efectivos, y los grupos suplementarios. Access tiene un contrato distinto.
18. ¿Cambiar el modo a cero revoca cualquier descriptor abierto?
No de forma general para un archivo regular ya abierto. El descriptor mantiene su modo de apertura, aunque otras condiciones pueden afectar operaciones posteriores.
19. ¿Los nueve bits explican toda denegación del kernel?
No. ACL, capacidades, estado del montaje, atributos y políticas adicionales pueden cambiar lo permitido.
20. ¿El evaluador del tema debe usarse para autorizar antes de abrir?
No. Solo calcula un modelo hipotético. La aplicación debe intentar la operación apropiada y comprobar su resultado, sin separar una predicción de un uso posterior como si fueran atómicos.
Para practicar
- Convierte 0600, 0640, 0644, 0710 y 0755 a ternas simbólicas y explica cada clase.
- Reconstruye el modo octal de rw- r-x --- y de r-- --- --x.
- Diseña un caso donde propietario tenga menos acceso que otros y explica por qué no se mezclan.
- Diseña otro caso donde un grupo suplementario seleccione la terna de grupo.
- Prueba solicitudes rw y rx y determina qué bit falta cuando se deniegan.
- Ejecuta el inspector sobre un archivo, un directorio y un simbólico y separa tipo de permisos.
- Ejecuta las 420 decisiones del verificador del modelo sin modificar las credenciales del equipo.
- Realiza el laboratorio real con una cuenta ordinaria y compara r sin x frente a x sin r en directorios propios.
- Explica por qué se pudo retirar el hijo legible de un padre -wx sin dar escritura al contenido del hijo.
- En el simulador de operaciones, permite los bits de escritura y activa montaje de solo lectura.
- Describe un diagnóstico que incluya identidades vigentes, prefijo, clase seleccionada y error efectivo.
- Explica qué condiciones quedarían fuera de una tabla que solo predijera acceso con st_mode.
En el tema 27 usaremos chmod, chown y umask desde Bash y C para cambiar permisos y propiedad y razonar sobre el modo con el que nacen los archivos.