Actualmente estoy trabajando en un proyecto en el que tengo un proceso principal que configura un par de sockets, se bifurca y luego usa este par de sockets para comunicarse. El hijo, si quiere abrir un archivo (o cualquier otro recurso basado en un descriptor de archivo) siempre debe ir al padre, solicitar el recurso y obtener el fd enviado a través del par de sockets. Además, quiero evitar que el niño abra cualquier descriptor de archivo por sí mismo.
Tropecé con setrlimit que evita con éxito que el niño abra nuevos descriptores de archivo, pero también parece invalidar cualquier descriptor de archivo enviado a través de la conexión de socket inicial. ¿Hay algún método en Linux que permita que un solo proceso abra cualquier archivo, envíe su descriptor de archivo a otros procesos y les permita usarlos sin permitir que estos otros procesos abran cualquier descriptor de archivo por sí mismos?
Para mi caso de uso, puede ser cualquier configuración del kernel, llamada al sistema, etc. siempre que se pueda aplicar después de la bifurcación y siempre que se aplique a todos los descriptores de archivos (no solo archivos sino también sockets, pares de sockets, etc.).
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 desea hacer en esta situación es, justo después de fork() , instalar un filtro seccomp que no permita el uso de open(2) , openat(2) , socket(2) (y más). Para lograr esto, puede hacer lo siguiente:
seccomp_init(3) con el comportamiento predeterminado de SCMP_ACT_ALLOW .seccomp_rule_add(3) para cada llamada al sistema que desee denegar. Puede usar SCMP_ACT_KILL para finalizar el proceso si se intenta la llamada al sistema, SCMP_ACT_ERRNO(val) para hacer que la llamada al sistema falle y devuelva el valor errno especificado o cualquier otro valor de action definido en la página del manual.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 desautorizada y podría resultar en una omisión 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é llamadas al sistema necesitarán los elementos secundarios, un enfoque de lista blanca es mejor y debe hacer lo contrario de lo anterior: cree un filtro con la acción predeterminada de SCMP_ACT_KILL y permita 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 (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() , así:
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:
fork(2) o clone(2) , todos los procesos secundarios estarán restringidos por el mismo filtro.execve(2) , el filtro existente se conservará en una llamada a execve(2) .prctl(2) , el proceso puede aplicar más filtros.