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 seccomp
filtro 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_KILL
per terminare il processo se si tenta la syscall, SCMP_ACT_ERRNO(val)
per far sì che la syscall non riesca a restituire il errno
valore specificato o qualsiasi altro action
valore 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_KILL
e 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.