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:
- Credenciales: usuario y grupo (UID y GID).
- Contexto del sistema de archivos: directorio de trabajo, directorio raíz y máscara de creación (umask).
- Entorno: las variables de entorno.
- Recursos: descriptores de archivo abiertos y límites de recursos.
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.
$ 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:
| Atributo | Nombre | Para que sirve |
|---|---|---|
| UID real | ruid | Quién lanzó el proceso. No cambia salvo que el proceso tenga privilegios para cambiarlo. Sirve para saber quién es el usuario «verdadero». |
| UID efectivo | euid | El que se usa para comprobar permisos de acceso a archivos y recursos. Es el que importa cuando el proceso intenta leer o escribir. |
| UID guardado | suid | Una 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.
Las funciones de POSIX para consultar estas credenciales, declaradas en <unistd.h>, son:
| Función | Devuelve |
|---|---|
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ón | Que cambia | Quié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, setregid | Lo 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:
/* 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;
}
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():
#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:
| Variable | significado |
|---|---|
PATH | Lista de directorios donde buscar ejecutables. |
HOME | Directorio personal del usuario. |
USER / LOGNAME | Nombre del usuario. |
SHELL | Shell por omisión. |
LANG / LC_ALL | Idioma y formato de locale. |
PWD | Directorio de trabajo actual (mantenido por la shell). |
TERM | Tipo de terminal. |
TMPDIR | Directorio 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:
#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():
#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:
$ 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:
| Descriptor | Nombre | Uso por omisión |
|---|---|---|
| 0 | stdin | Entrada estándar (el teclado o un archivo redirigido). |
| 1 | stdout | Salida estándar (la terminal o un archivo redirigido). |
| 2 | stderr | Error 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.
#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:
$ 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 controla | Comando shell equivalente |
|---|---|---|
RLIMIT_NOFILE | Número máximo de descriptores de archivo abiertos. | ulimit -n |
RLIMIT_NPROC | Número máximo de procesos por usuario. | ulimit -u |
RLIMIT_FSIZE | Tamaño máximo de un archivo que puede crear. | ulimit -f |
RLIMIT_STACK | Tamaño máximo de la pila. | ulimit -s |
RLIMIT_CPU | Tiempo de CPU máximo en segundos. | ulimit -t |
RLIMIT_AS | Espacio de direcciones máximo. | ulimit -v |
RLIMIT_CORE | Tamaño máximo del archivo core (para depuración). | ulimit -c |
RLIMIT_MEMLOCK | Memoria que se puede bloquear en RAM (sin swap). | ulimit -l |
Para consultar y modificar los límites desde C se usan getrlimit() y setrlimit():
#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:
$ 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).
$ 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:
#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:
| Archivo | Atributo que expone |
|---|---|
/proc/PID/status | UID real, efectivo, guardado y fsuid; GID real, efectivo, guardado y fsgid (líneas Uid: y Gid:). |
/proc/PID/cwd | Enlace al directorio de trabajo actual. |
/proc/PID/root | Enlace al directorio raíz. |
/proc/PID/environ | Las variables de entorno, separadas por bytes nulos. |
/proc/PID/fd/ | Los descriptores de archivo abiertos, como enlaces. |
/proc/PID/limits | Los límites de recursos. |
/proc/PID/umask | La umask del proceso (en kernels recientes). |
$ 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:
#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
- Los atributos de un proceso definen como interactúa con el sistema: identidad, contexto de archivos, entorno y recursos.
- Cada proceso tiene tres UIDs (real, efectivo, guardado) y tres GIDs, que permiten el mecanismo setuid.
- El UID efectivo es el que se usa para comprobar permisos; el real identifica a quien lanzó el proceso; el guardado permite recuperar privilegios.
- Un programa setuid debe bajar sus privilegios con
seteuid(getuid())lo antes posible. - El directorio de trabajo (cwd) se hereda del padre y se puede cambiar con
chdir(); cambiarlo en un hijo no afecta al padre. - Las variables de entorno se heredan del padre; un proceso puede modificar las suyas pero no las del padre.
- Los descriptores de archivo (0, 1, 2 por omisión) forman una tabla que se hereda del padre.
- Los límites de recursos (rlimit) tienen un valor suave y uno duro; se consultan con
getrlimit()y se cambian consetrlimit(). - La umask filtra los permisos de los archivos nuevos; se hereda del padre y se cambia con
umask(). - Todo se puede consultar en
/proc/PID:status,cwd,root,environ,fd/,limits.
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
- Compila y ejecuta
atributos.cy compara su salida conid,pwd,envyulimit -a. - Ejecuta
descriptores.cy mientras corre, mirals -l /proc/PID/fddesde otra terminal. - Cambia la umask en la shell con
umask 0077, crea un archivo y verifica sus permisos. Restaura después conumask 0022. - Escribe un programa que suba el límite de archivos abiertos con
setrlimity verifique el cambio en/proc/self/limits. - Usa
env -i ./programapara ejecutar un programa con un entorno vacío y observa que variables necesita para funcionar. - Busca en tu sistema programas con el bit setuid activado (
find / -perm -4000) y verifica sus credenciales conls -l.