Lo que tienes aquí es exactamente el caso de uso de seccomp .
Usando seccomp, puede filtrar las llamadas al sistema de diferentes maneras. Lo que se quiere hacer en esta situación es, justo después fork()
, la instalación de un seccomp
filtro que no permite el uso de open(2)
, openat(2)
, socket(2)
(y más). Para lograr esto, puede hacer lo siguiente:
- Primero, cree un contexto seccomp
seccomp_init(3)
con el comportamiento predeterminado de SCMP_ACT_ALLOW
.
- Luego agregue una regla al contexto usando
seccomp_rule_add(3)
para cada llamada al sistema que desee negar. Puede usar SCMP_ACT_KILL
para matar el proceso si se intenta la llamada al sistema, SCMP_ACT_ERRNO(val)
para hacer que la llamada al sistema no devuelva el errno
valor especificado o cualquier otro action
valor definido en la página del manual.
- Cargue el contexto usando
seccomp_load(3)
para hacerlo efectivo.
Antes de continuar, TENGA EN CUENTA que un enfoque de lista negra como este es en general más débil que un enfoque de lista blanca. Permite cualquier llamada al sistema que no esté explícitamente prohibida, y podría provocar un bypass del filtro . Si cree que el proceso secundario que desea ejecutar podría estar tratando maliciosamente de evitar el filtro, o si ya sabe qué syscalls necesitarán los menores, un enfoque de lista blanca es mejor, y debe hacer lo contrario de lo anterior: crear filtro con la acción predeterminada de SCMP_ACT_KILL
y permitir las llamadas al sistema necesarias con SCMP_ACT_ALLOW
. En términos de código, la diferencia es mínima (la lista blanca probablemente sea más larga, pero los pasos son los mismos).
Aquí hay un ejemplo de lo anterior (lo estoy haciendo exit(-1)
en caso de error solo por simplicidad):
#include <stdlib.h>
#include <seccomp.h>
static void secure(void) {
int err;
scmp_filter_ctx ctx;
int blacklist[] = {
SCMP_SYS(open),
SCMP_SYS(openat),
SCMP_SYS(creat),
SCMP_SYS(socket),
SCMP_SYS(open_by_handle_at),
// ... possibly more ...
};
// Create a new seccomp context, allowing every syscall by default.
ctx = seccomp_init(SCMP_ACT_ALLOW);
if (ctx == NULL)
exit(-1);
/* Now add a filter for each syscall that you want to disallow.
In this case, we'll use SCMP_ACT_KILL to kill the process if it
attempts to execute the specified syscall. */
for (unsigned i = 0; i < sizeof(blacklist) / sizeof(blacklist[0]); i++) {
err = seccomp_rule_add(ctx, SCMP_ACT_KILL, blacklist[i], 0);
if (err)
exit(-1);
}
// Load the context making it effective.
err = seccomp_load(ctx);
if (err)
exit(-1);
}
Ahora, en su programa, puede llamar a la función anterior para aplicar el filtro seccomp justo después de fork()
, de esta manera:
child_pid = fork();
if (child_pid == -1)
exit(-1);
if (child_pid == 0) {
secure();
// Child code here...
exit(0);
} else {
// Parent code here...
}
Algunas notas importantes sobre seccomp:
- Un filtro seccomp, una vez aplicado, no puede ser eliminado o alterado por el proceso.
- Si el filtro lo permite
fork(2)
o clone(2)
no, los procesos secundarios estarán restringidos por el mismo filtro.
- Si
execve(2)
está permitido, el filtro existente se conservará en una llamada a execve(2)
.
- Si
prctl(2)
se permite la llamada al sistema, el proceso puede aplicar más filtros.