Lección 9: EXEC
Tinguaro Tadeo del Rosario
Juan José Muñoz García
1
Introducción
Un fork crea un proceso hijo que ejecuta el
mismo programa, mientras que con un exec se
puede lanzar a ejecutar un nuevo programa.
El exec permite crear un proceso que ejecuta un
código distinto al del padre.
Si no existiera el fork y el exec, siempre estaría
ejecutándose el proceso init, porque el resto
serían simplemente hijos de él.
2
Familia de funciones
Realmente, no existe ninguna función llamada
exec.
Siempre estaremos haciendo referencia al
conjunto de funciones que poseen la misma
funcionalidad, pero que se diferencian en el
modo en el que se les pasan los argumentos.
La familia está formada por: execl, execle,
execve, execlp, execvp y execv.
3
Modo de uso
En caso de éxito en un exec no hay retorno,
porque el proceso que llama, ejecuta ahora un
nuevo programa.
Para crear nuevos procesos, generalmente se
siguen dos pasos:
– Se crea una copia del proceso padre mediante fork
o clone.
– El hijo lanza a ejecutar, mediante exec el nuevo
programa.
4
Modo de uso
if (fork())
{
// Esto lo ejecuta el padre
}
else
{
// Esto lo ejecuta el hijo
execl(“programa”, “programa”);
// Finaliza el proceso hijo
}
5
Implementación del exec
El código fuente de la implementación del exec se
encuentra en /fs/exec.c.
La función principal que lleva a cabo la implementación
es do_execve.
Otras funciones importantes son:
– prepare_binprm: prepara los parámetros del fichero
ejecutable.
– search_binary_handler: busca el manejador para el tipo de
ejecutable que se trate.
6
do_execve
La funcionalidad básica es la siguiente:
– Lee de memoria información característica del
ejecutable (prepare_binprm): de los primeros 128 bytes.
– Prepara el entorno y argumentos del nuevo programa.
– Busca el manejador para el tipo de ejecutable que se
esté tratando (search_binary_handler).
7
do_execve - linux_binprm
Estructura utilizada para configurar parámetros
de ejecución del proceso, antes de lanzarlo a
ejecutar
struct linux_binprm{
char buf[BINPRM_BUF_SIZE]; // Para leer los primeros 128 bytes
struct page *page[MAX_ARG_PAGES]; // Tabla pagina del proceso
unsigned long p; // Longitud de la memoria utilizada
struct dentry * dentry; // Identificador de fichero del ejecutable
int e_uid, e_gid;
// uid y gid efectivos
int argc, envc;
// Numero de argumentos y variables de entorno
char * filename;
/* Nombre del ejecutable */
};
8
do_execve - linux_binprm
•El UID real es el identificador del usuario que ha lanzado el
Estructura
utilizada
para
configurar
parámetros
proceso.
ejecución
del proceso,
antes
de lanzarlo
a
•El de
EUID
es el identificador
que usa
el sistema
para los controles
de ejecutar
acceso. Puede ser distinto al del usuario real (bit SETUID)
•Si el fichero ejecutado tiene activo el bit SETUID, se cambia el
e_uid del proceso que hizo la llamada al del propietario del fichero
ejecutable.
struct
linux_binprm{
char
// Para
los
primeros
bytes
•Si buf[BINPRM_BUF_SIZE];
no lo tiene activo, el e_uid
seráleer
el del
proceso
que128
hizo
la
struct page *page[MAX_ARG_PAGES]; // Tabla pagina del proceso
llamada.
unsigned
long p; // Longitud de la memoria utilizada
struct dentry * dentry; // Identificador de fichero del ejecutable
int e_uid, e_gid;
// uid y gid efectivos
int argc, envc;
// Numero de argumentos y variables de entorno
char * filename;
/* Nombre del ejecutable */
};
9
do_execve - Tabla de páginas
Número de
página
Tabla de
página
Marco en
memoria física
- Para reducir el tiempo de acceso a memoria física, se utilizan
tablas TLB
- El registro PTBR apunta a la dirección en memoria principal
que contiene la tabla de página
10
do_execve
10079 int do_execve(char * filename, char ** argv,
10080
char ** envp, struct pt_regs * regs)
10081 {
10082
struct linux_binprm bprm;
10083
struct dentry * dentry;
Inicializamos tabla de páginas
10084
int retval;
10085
int i;
10086
10087
bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
10088
for (i=0 ; i<MAX_ARG_PAGES ; i++) /* clear pg-tbl */
10089
bprm.page[i] = 0;
10090
10091
dentry = open_namei(filename, 0, 0);
10092
retval = PTR_ERR(dentry);
10093
if (IS_ERR(dentry))
Comprobamos existencia del ejecutable.
10094
return retval;
Se podría hacer antes para optimizar
11
do_execve
10096
10097
10098
10099
10100
10101
10102
10103
10104
10105
10106
10107
10108
10109
10110
10111
10112
bprm.dentry = dentry;
Count cuenta el número de punteros
bprm.filename = filename; no-nulos de los vectores argv y envp
bprm.sh_bang = 0;
que son los argumentos y las
bprm.java = 0;
variables de entorno.
bprm.loader = 0;
bprm.exec = 0;
if ((bprm.argc = count(argv)) < 0) {
dput(dentry);
return bprm.argc;
}
if ((bprm.envc = count(envp)) < 0) {
dput(dentry);
return bprm.envc;
}
retval = prepare_binprm(&bprm);
12
do_execve
10096
bprm.dentry = dentry;
Count cuenta el número de punteros
10097
bprm.filename = filename; no-nulos de los vectores argv y envp
10098
bprm.sh_bang = 0;
que son los argumentos y las
10099
= 0; de argumentos pasados
•argv
es unbprm.java
array de cadenas
al nuevo
programa.
variables
de entorno.
10100
bprm.loader
= 0;
•envp
es un array
de cadenas,
que se pasan como entorno al nuevo programa.
10101
bprm.exec = 0;
•Tanto
argv if
como
envp deben terminar en un puntero nulo.
10102
((bprm.argc = count(argv)) < 0) {
10103
dput(dentry);
10104
return bprm.argc;
10105
}
10106
10107
if ((bprm.envc = count(envp)) < 0) {
10108
dput(dentry);
10109
return bprm.envc;
10110
}
10111
10112
retval = prepare_binprm(&bprm);
13
do_execve
10114
if (retval >= 0) {
10115
bprm.p = copy_strings(1, &bprm.filename, bprm.page,
10116
bprm.p, 2);
10117
bprm.exec = bprm.p;
10118
bprm.p = copy_strings(bprm.envc,envp,bprm.page,
10119
bprm.p,0);
10120
bprm.p = copy_strings(bprm.argc,argv,bprm.page,
10121
bprm.p,0);
10122
if (!bprm.p)
Copia al espacio de memoria del proceso,
10123
retval = -E2BIG;
el nombre del fichero, las variables de
10124 Si }
no queda memoria libre, error
entorno y los argumentos.
10125
10126
if (retval >= 0)
10127
retval = search_binary_handler(&bprm,regs);
10128
if (retval >= 0)
10129
/* execve success */
10130
return retval;
14
do_execve
10132
10133
10134
10135
10136
10137
10138
10139
10140
10141 }
/* Something went wrong, return the inode and free the
* argument pages*/
if (bprm.dentry)
dput(bprm.dentry); Si algo va mal, devuelve el “inodo” y
libera los argumentos de las páginas.
for (i=0 ; i<MAX_ARG_PAGES ; i++)
free_page(bprm.page[i]);
return retval;
Liberamos memoria del
proceso
15
do_execve - count
9480 static int count(char ** argv)
9481 {
9482
int i = 0;
9484
if (argv != NULL) {
9485
for (;;) {
9486
char * p;
9487
int error;
Recoge los parámetros
9488
9489
error = get_user(p,argv);
9490
if (error)
9491
return error;
9492
if (!p)
9493
break;
Cuando encuentra un null sale
9494
argv++;
9495
i++;
Devuelve el número de parámetros, que se
9496
}
le pasan a la función que lanza el exec
9497
}
9498
return i;
9499 }
16
do_execve - prepare_binprm
9832 int prepare_binprm(struct linux_binprm *bprm)
9833 {
9834
int mode;
9835
int retval,id_change,cap_raised;
9836
struct inode * inode = bprm->dentry->d_inode;
9838
mode = inode->i_mode;
9839
if (!S_ISREG(mode))
Comprueba que es un fichero regular
9840
return -EACCES;
ejecutable, que tiene permiso de
9841
if (!(mode & 0111))
ejecución y que no se está escribiendo
9842
return -EACCES;
9843
if (IS_NOEXEC(inode))
9844
return -EACCES;
9845
if (!inode->i_sb)
9846
return -EACCES;
9847
if ((retval = permission(inode, MAY_EXEC)) != 0)
9848
return retval;
9850
if (inode->i_writecount > 0)
9851
return -ETXTBSY;
17
do_execve - prepare_binprm
9853
9854
9855
9856
9857
9858
9859
9860
9861
9862
9863
9868
9869
9870
9871
9872
9873
bprm->e_uid = current->euid;
bprm->e_gid = current->egid;
id_change = cap_raised = 0;
Si el setuid y/o setgid están
activos, los nuevos procesos
se tratarán como
usuarios / grupos diferentes
/* Set-uid? */
if (mode & S_ISUID) {
bprm->e_uid = inode->i_uid;
if (bprm->e_uid != current->euid)
id_change = 1;
}
/*Set-gid?*/
if ((mode & (S_ISGID | S_IXGRP)) ==
(S_ISGID | S_IXGRP)) {
bprm->e_gid = inode->i_gid;
if (!in_group_p(bprm->e_gid))
id_change = 1;
}
18
do_execve - prepare_binprm
9933
return read_exec(bprm->dentry,0,bprm->buf,128,1);
Finalmente, leemos los primeros 128 bytes del fichero, y lo introducimos en la
estructura bprm. Una posible optimización del kernel sería reemplazar 128 por
un define.
El buffer sirve para identificar el tipo de ejecutable que estamos tratando
19
do_execve - copy_string
9519 unsigned long copy_strings(
9520
int argc,char ** argv,
9521
unsigned long *page, unsigned long p, int from_kmem)
9522 {
9531
while (argc-- > 0) {
Traemos los argumentos de la memoria de
9537
get_user(str, argv+argc);
usuario, y calculamos la longitud
9542
len = strlen_user(str);
9549
while (len) {
9553
offset = pos % PAGE_SIZE;
9555
pag = (char *) page[pos/PAGE_SIZE] =
9556
(unsigned long *) get_free_page(GFP_USER))
9561
bytes_to_copy = PAGE_SIZE - offset;
9564
copy_from_user(pag + offset, str, bytes_to_copy);
9567
len -= bytes_to_copy;
9568
}
9569
}
9572
return p;
9573 }
20
do_execve - copy_string
9519 unsigned long copy_strings(
9520
int argc,char ** argv,
9521
unsigned long *page, unsigned long p, int from_kmem)
9522 {
9531
while (argc-- > 0) {
Traemos los argumentos de la memoria de
9537
get_user(str, argv+argc);
usuario, y calculamos la longitud
9542
len = strlen_user(str);
9549
while (len) {
9553
offset = pos % PAGE_SIZE;
9555
pag = (char *) page[pos/PAGE_SIZE] =
9556
(unsigned long *) get_free_page(GFP_USER))
9561
bytes_to_copy = PAGE_SIZE - offset;
9564
copy_from_user(pag + offset, str, bytes_to_copy);
9567
len -= bytes_to_copy;
9568
}
Calculamos una entrada en la tabla de páginas,
9569
}
9572
return p;le asignamos una dirección de memoria física y
copiamos en la memoria física el argumento.
9573 }
(Copiamos de página en página)
21
do_execve - search_binary_handler
9996 int search_binary_handler(struct linux_binprm *bprm,
9997
struct pt_regs *regs)
MANEJADOR
BINARIO:
9998 {
10036
for (try=0; try<2; try++) {
Mecanismo
del
tratar ;la fmt
variedad
de formatos
binarios {
de manera
10037
for núcleo
(fmt =para
formats
; fmt
= fmt->next)
consistente.
10038
int (*fn)(struct linux_binprm *, struct pt_regs *)
No
todos
los
programas
se almacenan en el mismo formato: un ejemplo “Java Handler”,
10039
= fmt->load_binary;
Llamamos a load_binary hasta que
“Scripts”.
10040
if (!fn)
retorne no-negativo. Para así poder utilizar
10041
continue;
las funciones que tratan cada ejecutable.
10042
retval = fn(bprm, regs);
10043
if (retval >= 0) {
10044
if (bprm->dentry)
10045
dput(bprm->dentry);
10046
bprm->dentry = NULL;
10047
current->did_exec = 1;
10048
return retval;
10049
}
22
do_execve - search_binary_handler
10050
if (retval != -ENOEXEC)
10051
break;
10052
/* We don't have the dentry anymore */
10053
if (!bprm->dentry)
10054
return retval;
10055
}
10056
if (retval != -ENOEXEC) {
10057
break;
10059
} else {
10068
sprintf(modname, "binfmt-%04x",
10069
*(unsigned short *)(&bprm->buf[2]));
10070
request_module(modname);
10072
}
Si no se encuentra ningún manejador
10073
}
se hace un segundo intento, obteniendo
10074
return retval;
primero un código de formato
10075 }
nuevo, que indique el posible tipo de
fichero binario
23
formatos ejecutables
No todos los programas están almacenados en
el mismo formato
Linux usa manejadores binarios para abstraer
las diferencias entre los distintos formatos
Para cada formato se usa un manejador
específico
ELF es el formato nativo de ejecutables Linux
24
formatos ejecutables
ELF sustituye a otro formato llamado a.out, que
es el que se utilizaba como nativo en versiones
anteriores del kernel
Los manejadores binarios reconocen cualquier
número mágico que se encuentre al comienzo
de un fichero
25
formatos ejecutables
También pueden realizar el reconocimiento del
formato por alguna propiedad del nombre
Por ejemplo, los binarios java se identifican por
su extensión class o por su número mágico
0xCAFEBADE
26
formatos ejecutables
El kernel 2.2 soporta los siguientes
manejadores:
– a.out: antiguo formato nativo de linux. Está en
desuso, pero aún se le proporciona soporte
– ELF: formato actual de los binarios Linux. A pesar
de ser el formato nativo, se incluye un manejador
para homogeneizar el núcleo, y hacerlo más sencillo
27
formatos ejecutables
– EM86: empleado para ejecutar binarios Linux en
máquinas con arquitectura Alpha
– Misc: el manejador de este formato puede
reconocer gran variedad de formatos por extensión,
o por número mágico. La gran ventaja que tiene, es
que es configurable en tiempo de ejecución, no solo
de compilación, y por lo tanto no es necesario
recompilar el núcleo para dar soporte a un nuevo
formato. En el futuro, los handlers de Java y EM86
serán de este tipo
28
formatos ejecutables
– Scripts: utilizado para ejecutar scripts del shell.
Trata los ficheros cuyos 2 primeros caracteres sean
#!
29
Un ejemplo: java handler
En fs/binfmt_java.c se encuentran las funciones
encargadas de manejar los ficheros java, tanto
ejecutables como applets
do_execve
Search_binary_handler
do_load_java
30
java handler - do_load_java
static int do_load_java(struct linux_binprm *bprm,struct pt_regs *regs)
{
unsigned char *ucp = (unsigned char *) bprm->buf;
do_load_java: Hace todo lo necesario para la carga de ficheros
.class if
de ((ucp[0]
Java != 0xca) || (ucp[1] != 0xfe) || (ucp[2] != 0xba) ||
(ucp[3] != 0xbe))
return -ENOEXEC;
if (bprm->java)
return -ENOEXEC;
bprm->java = 1;
dput(bprm->dentry);
bprm->dentry = NULL;
El fichero java
debe terminar
en “.class”
Comprueba el número
mágico
No se puede llamar
recursivamente
remove_arg_zero(bprm);
len = strlen (bprm->filename);
if (len >= 6 && !strcmp (bprm->filename + len - 6, ".class"))
bprm->filename[len - 6] = 0;
31
java handler - do_load_java
if ((i_name = strrchr (bprm->filename, '/')) != NULL)
i_name++;
else
i_name = bprm->filename;
bprm->p = copy_strings(1, &i_name, bprm->page, bprm->p, 2);
bprm->argc++;
i_name = binfmt_java_interpreter;
bprm->p = copy_strings(1, &i_name, bprm->page, bprm->p, 2);
bprm->argc++;
if (!bprm->p)
return -E2BIG;
Copia el nombre del intérprete
y del fichero .class a la zona de memoria
del proceso, para posteriormente
ejecutarlo
32
java handler - do_load_java
bprm->filename = binfmt_java_interpreter;
dentry = open_namei(binfmt_java_interpreter, 0, 0);
retval = PTR_ERR(dentry);
if (IS_ERR(dentry))
return retval;
bprm->dentry = dentry;
retval = prepare_binprm(bprm);
if (retval < 0)
return retval;
return search_binary_handler(bprm,regs);
}
Se ejecuta el intérprete, como se hacía en do_execve:
- Comprueba que exista el ejecutable del intérprete
- Prepara los argumentos con prepare_binprm
- Se busca el manejador para el intérprete (probablemente sea do_load_elf_binary)
33
java handler - load_java
De cara al exterior, puede parecer que es la
función que realiza la ejecución del .class, pero
realmente solo llama a do_load_java
static int load_java(struct linux_binprm *bprm,struct pt_regs *regs)
{
int retval;
MOD_INC_USE_COUNT;
retval = do_load_java(bprm,regs);
MOD_DEC_USE_COUNT;
return retval;
}
34
FIN
35
Descargar

Familia de funciones exec