Il problema si verifica perché la libreria GNU C utilizza il exit_group
syscall, se è disponibile, in Linux anziché exit
, per la funzione _exit()
(vedi sysdeps/unix/sysv/linux/_exit.c
per verifica), e come documentato nel man 2 prctl
, il exit_group
syscall non è consentito dal severo filtro seccomp.
Poiché la chiamata alla funzione _exit()
si verifica all'interno della libreria C, non è possibile inserirla con la nostra versione (che farebbe semplicemente il syscall exit
). (Il normale pulizia processo è fatto altrove;. In Linux, la funzione _exit()
fa solo la chiamata di sistema finale che termina il processo)
Potremmo chiedere agli sviluppatori della libreria GNU C di utilizzare la exit_group
syscall in Linux solo quando ci sono più di un thread nel processo corrente, ma sfortunatamente, non sarebbe facile, e anche se aggiunto in questo momento, ci vorrebbe un po 'di tempo prima che la funzione sia disponibile sulla maggior parte delle distribuzioni Linux.
Fortunatamente, possiamo eliminare il filtro rigoroso predefinito e invece definire il nostro. C'è una piccola differenza di comportamento: il segnale apparente che uccide il processo cambierà da SIGKILL
a SIGSYS
. (Il segnale non viene effettivamente consegnato, poiché il kernel uccide il processo, solo il numero di segnale apparente che ha causato la morte del processo cambia.)
Inoltre, non è nemmeno così difficile. Ho sprecato un po 'di tempo a esaminare alcuni macro trucchi della GCC che avrebbero reso banale la gestione dell'elenco delle syscalls consentite, ma ho deciso che non sarebbe stato un buon approccio: l'elenco delle syscalls permesse dovrebbe essere attentamente considerato - noi solo aggiungi exit_group()
rispetto al filtro rigoroso, qui! - Quindi renderlo un bit difficile è okay.
Il codice seguente, dire example.c
, è stato verificato funzionare su un kernel 4.4 (dovrebbe funzionare su kernel 3.5 o successiva) su x86-64 (sia x86 e x86-64, cioè 32 bit e 64 binari bit rate). Dovrebbe funzionare su tutte le architetture Linux, tuttavia, e richiede non o utilizzare la libreria libseccomp.
#define _GNU_SOURCE
#include <stdlib.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <stdio.h>
static const struct sock_filter strict_filter[] = {
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof (struct seccomp_data, nr))),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_rt_sigreturn, 5, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_read, 4, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_write, 3, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_exit, 2, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, SYS_exit_group, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
};
static const struct sock_fprog strict = {
.len = (unsigned short)(sizeof strict_filter/sizeof strict_filter[0]),
.filter = (struct sock_filter *)strict_filter
};
int main(void)
{
/* To be able to set a custom filter, we need to set the "no new privs" flag.
The Documentation/prctl/no_new_privs.txt file in the Linux kernel
recommends this exact form: */
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
fprintf(stderr, "Cannot set no_new_privs: %m.\n");
return EXIT_FAILURE;
}
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &strict)) {
fprintf(stderr, "Cannot install seccomp filter: %m.\n");
return EXIT_FAILURE;
}
/* The seccomp filter is now active.
It differs from SECCOMP_SET_MODE_STRICT in two ways:
1. exit_group syscall is allowed; it just terminates the
process
2. Parent/reaper sees SIGSYS as the killing signal instead of
SIGKILL, if the process tries to do a syscall not in the
explicitly allowed list
*/
return EXIT_SUCCESS;
}
Compilare utilizzando ad es.
gcc -Wall -O2 example.c -o example
e di esecuzione utilizzando
./example
o sotto strace
per vedere le chiamate di sistema e le chiamate di libreria fatto;
strace ./example
Il programma strict_filter
BPF è davvero banale. Il primo codice operativo carica il numero di syscall nell'accumulatore. I successivi cinque opcode lo confrontano con un numero di syscall accettabile e, se trovato, passa all'opcode finale che consente a syscall. Altrimenti il penultimo codice operativo uccide il processo.
Si noti che sebbene la documentazione faccia riferimento a sigreturn
come syscall consentito, il nome effettivo di syscall in Linux è rt_sigreturn
. (sigreturn
è stato ritirato a favore di rt_sigreturn
anni fa.)
Inoltre, quando il filtro è installato, gli opcode vengono copiati nella memoria del kernel (vedere kernel/seccomp.c
nelle origini del kernel di Linux), quindi non influisce in alcun modo sul filtro se i dati vengono modificati in seguito. Avere le strutture static const
ha zero impatto sulla sicurezza, in altre parole.
Ho utilizzato static
poiché non è necessario che i simboli siano visibili all'esterno dell'unità di compilazione (o in un file binario non sottoposto a stripping) e const
per inserire i dati nella sezione di dati di sola lettura del file binario ELF.
Il modulo di BPF_JUMP(BPF_JMP | BPF_JEQ, nr, equals, differs)
è semplice: l'accumulatore (il numero di syscall) viene confrontato con nr
. Se sono uguali, i successivi codici opzionali equals
vengono saltati. In caso contrario, i successivi codici operativi differs
vengono saltati.
Dal momento che i casi di uguali si spostano sull'opcode finale, è possibile aggiungere nuovi codici opzionali nella parte superiore (ovvero, subito dopo l'opcode iniziale), incrementando il numero di salti uguali per ciascuno.
noti che printf()
non funziona dopo l'installazione del filtro seccomp, dato che internamente, la libreria C vuole fare un fstat
syscall (standard output), e una chiamata di sistema brk
per allocare della memoria per un buffer.
Non puoi semplicemente restituire EXIT_SUCCESS? (Woops: non importa - non ha guardato il tuo codice abbastanza da vicino.) – Steven
Ho avuto lo stesso problema, il mio processo viene ucciso. – Lev
È molto strano che '_exit (EXIT_SUCCESS)' non funzioni, poiché la manpage indica chiaramente che, in modalità seccomp strict, "Le uniche chiamate di sistema che il thread chiamante è autorizzato a fare sono read (2), write (2)), _exit (2) (ma non exit_group (2)) e sigreturn (2). " (dove i numeri tra parentesi sono ovviamente sezioni manuali). –