Quello che hai qui è esattamente il caso d'uso di seccomp .
Usando seccomp, puoi filtrare syscalls in diversi modi. Che cosa si vuole fare in questa situazione è, subito dopo fork(), l'installazione di un seccompfiltro che non consente l'uso di open(2), openat(2), socket(2)(e più). A tale scopo, è possibile effettuare le seguenti operazioni:
- Innanzitutto, crea un contesto seccomp usando
seccomp_init(3)il comportamento predefinito di SCMP_ACT_ALLOW.
- Quindi aggiungi una regola al contesto usando
seccomp_rule_add(3)per ogni syscall che vuoi negare. È possibile utilizzare SCMP_ACT_KILLper terminare il processo se si tenta la syscall, SCMP_ACT_ERRNO(val)per far sì che la syscall non riesca a restituire il errnovalore specificato o qualsiasi altro actionvalore definito nella pagina del manuale.
- Carica il contesto usando
seccomp_load(3)per renderlo efficace.
Prima di continuare, NOTA che un approccio di lista nera come questo è in generale più debole di un approccio di lista bianca. Consente qualsiasi syscall che non è esplicitamente vietato e potrebbe comportare un bypass del filtro . Se ritieni che il processo figlio che desideri eseguire potrebbe tentare di evitare in modo dannoso il filtro o se sai già quali syscalls saranno necessari ai bambini, un approccio alla whitelist è migliore e dovresti fare l'opposto di quanto sopra: crea un filtro con l'azione predefinita di SCMP_ACT_KILLe consenti le syscalls necessarie con SCMP_ACT_ALLOW. In termini di codice la differenza è minima (la whitelist è probabilmente più lunga, ma i passaggi sono gli stessi).
Ecco un esempio di quanto sopra (lo sto facendo exit(-1)in caso di errore solo per semplicità):
#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);
}
Ora, nel tuo programma, puoi chiamare la funzione sopra per applicare il filtro seccomp subito dopo fork(), in questo modo:
child_pid = fork();
if (child_pid == -1)
exit(-1);
if (child_pid == 0) {
secure();
// Child code here...
exit(0);
} else {
// Parent code here...
}
Alcune note importanti su seccomp:
- Un filtro seccomp, una volta applicato, non può essere rimosso o modificato dal processo.
- Se
fork(2)o clone(2)sono consentiti dal filtro, tutti i processi figlio saranno vincolati dallo stesso filtro.
- Se
execve(2)consentito, il filtro esistente verrà conservato durante una chiamata a execve(2).
- Se la
prctl(2)syscall è consentita, il processo è in grado di applicare ulteriori filtri.