Atributos de un proceso - Procesos e Hilos
← Procesos e Hilos Bloque 1 · El concepto de proceso · Tema 5 de 43

Tema 5

Atributos de un proceso

Además de su identidad y su estado, cada proceso carga un conjunto de atributos que definen como se comporta en el sistema: con que usuario y grupo se ejecuta, en que directorio trabaja, que variables de entorno tiene, que archivos tiene abiertos y que límites de recursos le aplican. En este tema verás cada uno de esos atributos, como consultarlo y cambiarlo desde C, y como aparecen en /proc.

1. Qué son los atributos de un proceso

Los atributos de un proceso son los datos que acompañan al proceso durante toda su vida y que definen como interactúa con el sistema: con que identidad de usuario se ejecuta, en que directorio está parado, que variables de entorno heredó, que archivos tiene abiertos y que límites de recursos le aplican. Muchos se heredan del padre al crear el proceso, y algunos se pueden cambiar durante la ejecución.

En el tema anterior viste que el PCB guarda, entre otros campos, las credenciales, el directorio de trabajo, la tabla de archivos abiertos y los límites. Esos son los atributos. Aquí nos concentramos en ellos, porque son los que más se usan en la práctica: un programa que necesita saber quién lo ejecuta, dónde está parado, que configuración heredó o cuántos archivos puede abrir, consulta estos atributos.

Los atributos se dividen en cuatro grandes grupos, que son los que recorremos en este tema:

2. Usuario y grupo: las credenciales

Cada proceso se ejecuta «como» un usuario y un grupo. Esas credenciales determinan que archivos puede leer o escribir, que señales puede enviar a otros procesos y que operaciones privilegiadas puede realizar. El núcleo las guarda en el PCB, en la estructura cred, y las consulta cada vez que el proceso intenta acceder a un recurso protegido.

Un usuario se identifica por un número, el UID (user identifier); un grupo, por el GID (group identifier). Los nombres que ves en ls -l o en ps son solo traducciones de esos números, que el sistema hace consultando /etc/passwd y /etc/group. Para el núcleo, lo que importa es el número.

El usuario con UID 0 es root, el superusuario, que pasa todas las comprobaciones de permisos. Los usuarios normales tienen UIDs a partir de 1000 (o 500 en sistemas más antiguos). El grupo con GID 0 también es root.

terminal · ver tu UID y GID
$ id
uid=1000(ana) gid=1000(ana) groups=1000(ana),27(sudo),100(users)
$ id root
uid=0(root) gid=0(root) groups=0(root)

3. UID y GID reales, efectivos y guardados

Aquí viene la parte sutil. Un proceso no tiene un solo UID, sino tres, y lo mismo para el GID. Cada uno tiene un propósito distinto:

AtributoNombrePara que sirve
UID realruidQuién lanzó el proceso. No cambia salvo que el proceso tenga privilegios para cambiarlo. Sirve para saber quién es el usuario «verdadero».
UID efectivoeuidEl que se usa para comprobar permisos de acceso a archivos y recursos. Es el que importa cuando el proceso intenta leer o escribir.
UID guardadosuidUna copia del UID efectivo anterior. Permite que un proceso privilegiado baje sus privilegios temporalmente y los recupere después.

Lo mismo aplica a los GID: real, efectivo y guardado. Además, hay un UID y GID adicionales llamados fsuid y fsgid, que se usan específicamente para comprobar permisos de acceso al sistema de archivos. En la práctica casi siempre coinciden con el UID y GID efectivos, pero existen para casos especiales como los servidores NFS.

¿Por que tres UIDs? Por el mecanismo de setuid. Cuando un programa tiene el bit setuid activado (lo viste en el curso de permisos), se ejecuta con el UID del dueño del archivo, no del usuario que lo lanzó. Por ejemplo, /usr/bin/passwd es propiedad de root y tiene setuid: cualquier usuario puede ejecutarlo, y mientras corre tiene UID efectivo 0, lo que le permite modificar /etc/shadow. Pero su UID real sigue siendo el del usuario que lo lanzó, para que el programa sepa quién es.

El UID guardado permite que ese programa, una vez que terminó su tarea privilegiada, baje su UID efectivo al del usuario real (con seteuid) y, si necesita volver a ser root, lo recupere desde el UID guardado. Sin el UID guardado, una vez bajado el privilegio no se podría recuperar.

Los tres UIDs de un proceso. El UID real es quien lanzó el proceso. El UID efectivo es el que se usa para comprobar permisos. El UID guardado es una copia del efectivo anterior, para poder recuperar privilegios. Muestra el caso de passwd: lanzado por ana (UID 1000), se ejecuta con UID efectivo 0 (root) por setuid, y guarda 0 para poder recuperarlo. Credenciales de un proceso UID real quién lanzó el proceso 1000 (ana) UID efectivo se usa para comprobar permisos 0 (root) UID guardado copia del efectivo anterior (recuperar) 0 (root) Ejemplo: /usr/bin/passwd lanzado por ana GID real 1000 (ana) GID efectivo 0 (root) GID guardado 0 (root)
Las credenciales de un proceso. En el ejemplo, passwd se lanza como ana (UID real 1000) pero se ejecuta como root (UID efectivo 0) por el bit setuid, guardando 0 para poder recuperar el privilegio.

Las funciones de POSIX para consultar estas credenciales, declaradas en <unistd.h>, son:

FunciónDevuelve
uid_t getuid(void)UID real.
uid_t geteuid(void)UID efectivo.
gid_t getgid(void)GID real.
gid_t getegid(void)GID efectivo.

4. Cambiar de usuario: setuid y amigos

Un proceso puede cambiar sus credenciales durante la ejecución, pero solo si tiene privilegios para hacerlo. Las funciones relevantes son:

FunciónQue cambiaQuién puede usarla
setuid(uid)UID real, efectivo y guardado (si es root) o solo efectivo (si no).Root cambia todo; un usuario normal solo puede poner el efectivo igual al real o al guardado.
seteuid(uid)Solo el UID efectivo.Root a cualquier valor; un usuario normal solo a real o guardado.
setreuid(ruid, euid)UID real y efectivo de forma independiente.Sujeto a las mismas reglas.
setgid, setegid, setregidLo mismo para los grupos.Igual que para los UIDs.

La regla general es: un proceso sin privilegios solo puede cambiar su UID efectivo a su UID real o a su UID guardado, pero no a un valor arbitrario. Root (UID efectivo 0) puede cambiar a cualquier valor. Por eso el patrón típico de un programa setuid es:

patrón setuid
/* El programa se ejecuta como root (euid 0) por setuid */
int main(void)
{
    /* Hacer la tarea privilegiada */
    abrir_archivo_protegido();

    /* Bajar privilegios para el resto del programa */
    if (seteuid(getuid()) != 0) {   /* vuelve al usuario real */
        perror("seteuid");
        return 1;
    }

    /* A partir de aquí, euid == ruid: sin privilegios */
    procesar_datos_del_usuario();

    /* Si necesita volver a ser root, puede hacerlo: */
    /* seteuid(0);  (solo si suid era 0) */
    return 0;
}
Bajar privilegios es obligatorio

Un programa setuid que no baja sus privilegios después de la tarea privilegiada es un riesgo de seguridad: cualquier vulnerabilidad en el código posterior se ejecuta como root. La buena práctica es bajar los privilegios lo antes posible y solo recuperarlos si es estrictamente necesario.

En linux también existen las capacidades (capabilities), que dividen el poder de root en permisos individuales (por ejemplo, CAP_NET_BIND_SERVICE permite abrir puertos bajos sin ser root completo). Un proceso puede tener un subconjunto de capacidades en lugar del UID 0 completo. Es un tema avanzado que se profundiza en el curso de seguridad.

5. Directorio de trabajo y directorio raíz

Cada proceso tiene un directorio de trabajo actual (current working directory, o cwd). Es el directorio desde el que se resuelven las rutas relativas. Cuando escribes open("datos.txt", ...), el núcleo busca datos.txt en el cwd del proceso, no en el directorio desde el que se lanzó el programa.

El cwd se hereda del padre al crear el proceso, y se puede cambiar en cualquier momento con chdir():

cambiar directorio
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    char buf[4096];
    if (getcwd(buf, sizeof buf) != NULL)
        printf("cwd inicial: %s\n", buf);

    if (chdir("/tmp") == 0) {
        getcwd(buf, sizeof buf);
        printf("cwd nuevo:   %s\n", buf);
    } else {
        perror("chdir");
    }
    return 0;
}

El directorio de trabajo es un atributo del proceso, no de la terminal. Por eso, cuando un programa hace chdir(), no cambia el directorio de la shell que lo lanzó: el proceso hijo tiene su propia copia del cwd, y modificarla no afecta al padre. Es por eso que un programa no puede cambiar el directorio de la shell; para eso se usan los comandos internos de la shell, como cd, que sí modifican el cwd de la shell misma.

Además del cwd, cada proceso tiene un directorio raíz, que por omisión es /. Se puede cambiar con chroot(), que restringe al proceso a un subárbol del sistema de archivos. Es la base de los contenedores y de los entornos enjaulados (chroot jails). Cambiar el directorio raíz requiere privilegios de root.

Las funciones POSIX son getcwd(), chdir() y chroot(), declaradas en <unistd.h>. getcwd() puede fallar si el búfer es demasiado chico; en ese caso devuelve NULL y pone ERANGE en errno.

6. Variables de entorno

El entorno de un proceso es un conjunto de pares NOMBRE=valor que se heredan del padre y que el programa puede consultar para saber como comportarse. Es la forma en que la shell y el sistema le pasan configuración a los programas sin usar argumentos de línea de comandos.

Las variables más comunes son:

Variablesignificado
PATHLista de directorios donde buscar ejecutables.
HOMEDirectorio personal del usuario.
USER / LOGNAMENombre del usuario.
SHELLShell por omisión.
LANG / LC_ALLIdioma y formato de locale.
PWDDirectorio de trabajo actual (mantenido por la shell).
TERMTipo de terminal.
TMPDIRDirectorio para archivos temporales.

En C, el entorno se accede a través de la variable global environ, un arreglo de punteros a cadenas terminado en NULL:

leer_entorno.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char **environ;

int main(int argc, char *argv[], char *envp[])
{
    /* envp es el tercer parámetro de main: el entorno al arrancar */
    printf("Variables de entorno (via envp):\n");
    for (int i = 0; envp[i] != NULL; i++)
        printf("  %s\n", envp[i]);

    /* environ es la variable global, siempre disponible */
    printf("\ntotal: ");
    int n = 0;
    for (char **e = environ; *e != NULL; e++) n++;
    printf("%d variables\n", n);

    /* Consultar una variable específica */
    char *path = getenv("PATH");
    if (path)
        printf("PATH = %s\n", path);

    return 0;
}

Para modificar el entorno desde un programa se usan setenv(), putenv() y unsetenv():

modificar_entorno.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    /* Agregar o modificar una variable */
    if (setenv("MI_VAR", "hola", 1) != 0) {   /* 1 = sobrescribir */
        perror("setenv");
        return 1;
    }
    printf("MI_VAR = %s\n", getenv("MI_VAR"));

    /* Eliminar una variable */
    unsetenv("MI_VAR");
    printf("Después de unsetenv: %s\n", getenv("MI_VAR") ? getenv("MI_VAR") : "(no existe)");

    /* putenv modifica directamente el arreglo environ */
    putenv("OTRA=valor");
    printf("OTRA = %s\n", getenv("OTRA"));

    return 0;
}

Un detalle importante: las variables de entorno que un proceso modifica solo afectan a ese proceso y a sus hijos. El padre (por ejemplo, la shell) no se entera. Por eso, cuando ejecutas export VAR=valor en la shell, es la shell la que modifica su propio entorno, y los programas que lanza heredan esa variable. Un programa no puede «devolver» variables de entorno a su padre.

En la shell se pueden ver y modificar las variables con env, printenv, export y unset:

terminal · variables de entorno en la shell
$ env | head -5
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/ana
USER=ana
SHELL=/bin/bash
TERM=xterm-256color
$ export MI_VAR=hola
$ printenv MI_VAR
hola
$ unset MI_VAR

7. Descriptores de archivo abiertos

Cada proceso tiene una tabla de archivos abiertos, que es un arreglo de punteros a estructuras del núcleo que representan archivos, sockets, tuberías, etc. Cada entrada se identifica por un número entero llamado descriptor de archivo (fd). Los tres primeros descriptores están reservados:

DescriptorNombreUso por omisión
0stdinEntrada estándar (el teclado o un archivo redirigido).
1stdoutSalida estándar (la terminal o un archivo redirigido).
2stderrError estándar (la terminal, por omisión).

Cuando un programa abre un archivo con open(), el núcleo busca el primer descriptor libre (el más bajo) y lo asigna. Por eso, si cierras el descriptor 1 y abres un archivo, ese archivo queda en el descriptor 1, y lo que escribas en stdout va al archivo. Es el mecanismo que usa la shell para redirigir la salida.

La tabla de descriptores se hereda del padre al crear el proceso: el hijo recibe una copia de los descriptores del padre (apuntando a los mismos archivos abiertos). Por eso, si la shell tiene abiertos los descriptores 0, 1 y 2 apuntando a la terminal, el programa que lanza también los tiene. Y por eso, si rediriges la salida de un programa con >, la shell cierra su stdout, abre el archivo y luego crea el proceso hijo, que hereda esa redirección.

Para consultar y modificar la tabla de descriptores se usan open(), close(), dup(), dup2() y fcntl(). El tema 17 profundiza en la redirección con dup2.

descriptores.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>

int main(void)
{
    /* Abrir un archivo: obtiene el primer descriptor libre */
    int fd = open("/etc/hostname", O_RDONLY);
    if (fd < 0) { perror("open"); return 1; }
    printf("Archivo abierto en descriptor %d\n", fd);

    /* Listar los descriptores abiertos leyendo /proc/self/fd */
    DIR *d = opendir("/proc/self/fd");
    if (d) {
        struct dirent *e;
        printf("Descriptores abiertos:\n");
        while ((e = readdir(d)) != NULL) {
            if (e->d_name[0] != '.')
                printf("  %s\n", e->d_name);
        }
        closedir(d);
    }

    close(fd);
    return 0;
}

En /proc, la tabla de descriptores de un proceso se expone en /proc/PID/fd/, donde cada entrada es un enlace simbólico al archivo o recurso que representa:

terminal · ver los descriptores de un proceso
$ ls -l /proc/self/fd
total 0
lrwx------ 1 ana ana 64 oct 16 20:00 0 -> /dev/pts/0
lrwx------ 1 ana ana 64 oct 16 20:00 1 -> /dev/pts/0
lrwx------ 1 ana ana 64 oct 16 20:00 2 -> /dev/pts/0
lrwx------ 1 ana ana 64 oct 16 20:00 3 -> /proc/448/fd

La entrada 3 apunta a /proc/448/fd porque ls abrió ese directorio para leerlo. Cada vez que un programa abre un archivo, aparece una entrada nueva. Cuando se cierra, desaparece.

8. Límites de recursos

Cada proceso tiene un conjunto de límites de recursos (rlimits) que acotan cuánto puede consumir: cuántos archivos puede tener abiertos, cuánta memoria puede usar, cuántos procesos puede crear, que tamaño máximo pueden tener sus archivos, etc. Estos límites protegen al sistema de que un proceso agote los recursos compartidos.

Cada límite tiene dos valores: el límite suave (soft), que el proceso puede cambiar hasta el límite duro, y el límite duro (hard), que solo root puede elevar. El límite suave es el que se aplica; el proceso puede bajarlo o subirlo hasta el duro.

Límite (rlimit)Que controlaComando shell equivalente
RLIMIT_NOFILENúmero máximo de descriptores de archivo abiertos.ulimit -n
RLIMIT_NPROCNúmero máximo de procesos por usuario.ulimit -u
RLIMIT_FSIZETamaño máximo de un archivo que puede crear.ulimit -f
RLIMIT_STACKTamaño máximo de la pila.ulimit -s
RLIMIT_CPUTiempo de CPU máximo en segundos.ulimit -t
RLIMIT_ASEspacio de direcciones máximo.ulimit -v
RLIMIT_CORETamaño máximo del archivo core (para depuración).ulimit -c
RLIMIT_MEMLOCKMemoria que se puede bloquear en RAM (sin swap).ulimit -l

Para consultar y modificar los límites desde C se usan getrlimit() y setrlimit():

limites.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/resource.h>

int main(void)
{
    struct rlimit rl;

    /* Consultar el límite de archivos abiertos */
    if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
        printf("NOFILE: soft=%lu  hard=%lu\n",
               (unsigned long) rl.rlim_cur,
               (unsigned long) rl.rlim_max);
    }

    /* Bajar el límite suave (siempre se puede bajar) */
    rl.rlim_cur = 1024;
    if (setrlimit(RLIMIT_NOFILE, &rl) == 0)
        printf("Límite suave bajado a 1024\n");

    /* Consultar el límite de procesos */
    if (getrlimit(RLIMIT_NPROC, &rl) == 0) {
        printf("NPROC: soft=%lu  hard=%lu\n",
               (unsigned long) rl.rlim_cur,
               (unsigned long) rl.rlim_max);
    }

    return 0;
}

Los límites se heredan del padre al crear el proceso. La shell los configura con ulimit antes de lanzar programas, y esos límites se propagan a todos los hijos. Por eso, si quieres que un programa pueda abrir más archivos, debes subir el límite en la shell antes de lanzarlo: ulimit -n 4096 && ./programa.

En /proc, los límites de un proceso se exponen en /proc/PID/limits:

terminal · límites de un proceso
$ cat /proc/self/limits
Limit                     Soft limit      Hard limit      Units
Max cpu time              unlimited       unlimited       seconds
Max file size             unlimited       unlimited       bytes
Max data size             unlimited       unlimited       bytes
Max stack size            8388608         unlimited       bytes
Max core file size        0               unlimited       bytes
Max resident set          unlimited       unlimited       bytes
Max processes             31263           31263           processes
Max open files            1024            1048576         files
Max locked memory         65536           65536           bytes
Max address space         unlimited       unlimited       bytes
Max file locks            unlimited       unlimited       locks
Max pending signals       31263           31263           signals
Max msgqueue size         819200          819200          bytes
Max nice priority         0               0
Max realtime priority     0               0
Max realtime timeout      unlimited       unlimited       us

9. umask y creación de archivos

La umask (user file creation mask) es un atributo del proceso que determina los permisos con los que se crean los archivos nuevos. Cuando un programa crea un archivo con open() o creat(), el núcleo toma los permisos solicitados y les quita los bits que están activos en la umask.

Por ejemplo, si un programa pide crear un archivo con permisos 0666 (lectura y escritura para todos) y la umask es 0022, el resultado es 0644 (lectura para todos, escritura solo para el dueño). Si la umask es 0002, el resultado es 0664 (lectura y escritura para dueño y grupo, lectura para otros).

terminal · umask
$ umask
0022
$ umask 0002
$ umask
0002

La umask se hereda del padre al crear el proceso, y se puede consultar y cambiar con la función umask(), que devuelve el valor anterior:

umask.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
    mode_t anterior = umask(0027);   /* cambia y devuelve la anterior */
    printf("umask anterior: %04o\n", anterior);

    /* Crear un archivo: los permisos se filtran por la umask */
    int fd = open("/tmp/prueba_umask", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd >= 0) {
        close(fd);
        printf("Archivo creado. verifica con: ls -l /tmp/prueba_umask\n");
    }
    return 0;
}

La shell configura la umask al arrancar (típicamente 0022 o 0002) y la heredan todos los programas que lanza. Por eso, si quieres que los archivos que crea un programa tengan permisos distintos, debes cambiar la umask en la shell antes de lanzarlo, o dentro del programa.

10. Consultar los atributos en /proc

Los atributos de un proceso se exponen en varios archivos de /proc/PID:

ArchivoAtributo que expone
/proc/PID/statusUID real, efectivo, guardado y fsuid; GID real, efectivo, guardado y fsgid (líneas Uid: y Gid:).
/proc/PID/cwdEnlace al directorio de trabajo actual.
/proc/PID/rootEnlace al directorio raíz.
/proc/PID/environLas variables de entorno, separadas por bytes nulos.
/proc/PID/fd/Los descriptores de archivo abiertos, como enlaces.
/proc/PID/limitsLos límites de recursos.
/proc/PID/umaskLa umask del proceso (en kernels recientes).
terminal · consultar atributos en /proc
$ grep -E '^(Uid|Gid):' /proc/self/status
Uid:	1000	1000	1000	1000
Gid:	1000	1000	1000	1000
$ ls -l /proc/self/cwd /proc/self/root
lrwxrwxrwx 1 ana ana 0 oct 16 20:15 /proc/self/cwd -> /home/ana/procesos
lrwxrwxrwx 1 ana ana 0 oct 16 20:15 /proc/self/root -> /
$ tr '\0' '\n' < /proc/self/environ | head -5
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOME=/home/ana
USER=ana
SHELL=/bin/bash
TERM=xterm-256color

Las cuatro columnas de Uid: y Gid: en status son, en orden: real, efectivo, guardado y fsuid (o fsgid). En un proceso normal de usuario, las cuatro son iguales. En un programa setuid, la segunda y la tercera serán distintas de la primera.

11. Un programa que muestra sus atributos

El siguiente programa consulta y muestra todos los atributos que vimos en este tema. Es útil para entender como un proceso puede inspeccionar su propia configuración:

atributos.c
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/resource.h>

extern char **environ;

int main(void)
{
    char buf[4096];

    printf("=== Atributos del proceso %d ===\n\n", (int) getpid());

    /* Credenciales */
    printf("--- Credenciales ---\n");
    printf("UID real:     %d\n", (int) getuid());
    printf("UID efectivo: %d\n", (int) geteuid());
    printf("GID real:     %d\n", (int) getgid());
    printf("GID efectivo: %d\n", (int) getegid());

    /* Directorio de trabajo */
    printf("\n--- Directorio de trabajo ---\n");
    if (getcwd(buf, sizeof buf))
        printf("cwd: %s\n", buf);

    /* Entorno (primeras 5 variables) */
    printf("\n--- Entorno (primeras 5) ---\n");
    for (int i = 0; i < 5 && environ[i]; i++)
        printf("  %s\n", environ[i]);

    /* Límites */
    printf("\n--- Límites ---\n");
    struct rlimit rl;
    if (getrlimit(RLIMIT_NOFILE, &rl) == 0)
        printf("NOFILE: soft=%lu hard=%lu\n",
               (unsigned long) rl.rlim_cur,
               (unsigned long) rl.rlim_max);
    if (getrlimit(RLIMIT_NPROC, &rl) == 0)
        printf("NPROC:  soft=%lu hard=%lu\n",
               (unsigned long) rl.rlim_cur,
               (unsigned long) rl.rlim_max);
    if (getrlimit(RLIMIT_STACK, &rl) == 0)
        printf("STACK:  soft=%lu hard=%lu\n",
               (unsigned long) rl.rlim_cur,
               (unsigned long) rl.rlim_max);

    /* umask */
    printf("\n--- umask ---\n");
    mode_t m = umask(0);
    umask(m);   /* restaurar */
    printf("umask: %04o\n", m);

    return 0;
}

Al ejecutarlo, verás que los valores coinciden con los que muestra id, pwd, env y ulimit en la shell. Es una buena forma de confirmar que los atributos que el programa ve son los mismos que el sistema le asignó.

12. Resumen

13. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Que es el UID efectivo de un proceso?

Es el UID que el núcleo usa para comprobar permisos de acceso a archivos y recursos. En un programa normal coincide con el UID real; en un programa setuid, es el UID del dueño del archivo.

2. ¿Para que sirve el UID guardado?

Es una copia del UID efectivo anterior. Permite que un proceso privilegiado baje su UID efectivo temporalmente (por ejemplo, al UID real) y lo recupere después, sin perder el privilegio original.

3. ¿Por que un programa no puede cambiar el directorio de la shell que lo lanzó?

Porque el directorio de trabajo es un atributo del proceso. El hijo recibe una copia del cwd del padre al crearse, y modificar la suya no afecta al padre. Solo los comandos internos de la shell (como cd) modifican el cwd de la shell misma.

4. ¿Como se accede a las variables de entorno desde C?

A través del parámetro envp de main o de la variable global environ, un arreglo de punteros a cadenas terminado en NULL. Para consultar una variable específica se usa getenv().

5. ¿Que pasa si un proceso modifica una variable de entorno?

Solo afecta a ese proceso y a sus hijos. El padre no se entera. Las variables de entorno no se «devuelven» al padre.

6. ¿Que son los descriptores 0, 1 y 2?

Son la entrada estándar (stdin), la salida estándar (stdout) y el error estándar (stderr). Son los tres primeros descriptores de la tabla de archivos abiertos, y se heredan del padre.

7. ¿Que diferencia hay entre el límite suave y el duro?

El límite suave es el que se aplica al proceso y puede ser cambiado por el propio proceso hasta el límite duro. El límite duro solo puede ser elevado por root. Bajar cualquiera de los dos siempre está permitido.

8. ¿Para que sirve la umask?

Es una máscara que se aplica a los permisos solicitados al crear un archivo. Los bits activos en la umask se quitan de los permisos finales. Por ejemplo, con umask 0022, un archivo creado con 0666 queda con 0644.

9. ¿Dónde se consultan los descriptores abiertos de un proceso?

En /proc/PID/fd/, donde cada entrada es un enlace simbólico al archivo o recurso que representa ese descriptor.

10. ¿Que columnas muestra /proc/PID/status para uid y gid?

Cuatro columnas en este orden: real, efectivo, guardado y fsuid (o fsgid). En un proceso normal de usuario las cuatro son iguales.

Para practicar

  1. Compila y ejecuta atributos.c y compara su salida con id, pwd, env y ulimit -a.
  2. Ejecuta descriptores.c y mientras corre, mira ls -l /proc/PID/fd desde otra terminal.
  3. Cambia la umask en la shell con umask 0077, crea un archivo y verifica sus permisos. Restaura después con umask 0022.
  4. Escribe un programa que suba el límite de archivos abiertos con setrlimit y verifique el cambio en /proc/self/limits.
  5. Usa env -i ./programa para ejecutar un programa con un entorno vacío y observa que variables necesita para funcionar.
  6. Busca en tu sistema programas con el bit setuid activado (find / -perm -4000) y verifica sus credenciales con ls -l.