To, co masz tutaj, to dokładnie przypadek użycia seccomp .
Za pomocą seccomp możesz filtrować połączenia systemowe na różne sposoby. Co chcesz robić w tej sytuacji jest to, zaraz po fork()
, aby zainstalować seccomp
filtr, który wykluczy stosowanie open(2)
, openat(2)
, socket(2)
(i więcej). Aby to zrobić, możesz wykonać następujące czynności:
- Najpierw utwórz kontekst seccomp przy użyciu
seccomp_init(3)
domyślnego zachowania SCMP_ACT_ALLOW
.
- Następnie dodaj regułę do kontekstu, używając
seccomp_rule_add(3)
dla każdego połączenia systemowego, którego chcesz odmówić. Możesz użyć SCMP_ACT_KILL
do zabicia procesu, jeśli próba syscall zostanie podjęta, SCMP_ACT_ERRNO(val)
aby syscall nie zwrócił określonej errno
wartości lub innej action
wartości zdefiniowanej na stronie podręcznika.
- Załaduj kontekst za pomocą,
seccomp_load(3)
aby był skuteczny.
Zanim przejdziesz dalej, ZAUWAŻ, że takie podejście do czarnej listy jest ogólnie słabsze niż podejście do białej listy. Pozwala na każde wywołanie systemowe, które nie jest wyraźnie niedozwolone i może spowodować obejście filtra . Jeśli uważasz, że proces potomny, który chcesz wykonać, może złośliwie próbować ominąć filtr lub jeśli już wiesz, które wywołania systemowe będą potrzebne dzieciom, lepiej zastosować podejście z białej listy i powinieneś zrobić coś przeciwnego: utwórz filtr z domyślną akcją SCMP_ACT_KILL
i zezwól na wymagane wywołania systemowe za pomocą SCMP_ACT_ALLOW
. Pod względem kodu różnica jest minimalna (biała lista jest prawdopodobnie dłuższa, ale kroki są takie same).
Oto przykład powyższego (robię to exit(-1)
w przypadku błędu tylko dla uproszczenia):
#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);
}
Teraz w swoim programie możesz wywołać powyższą funkcję, aby zastosować filtr seccomp zaraz po fork()
:
child_pid = fork();
if (child_pid == -1)
exit(-1);
if (child_pid == 0) {
secure();
// Child code here...
exit(0);
} else {
// Parent code here...
}
Kilka ważnych uwag na temat seccomp:
- Raz zastosowanego filtra seccomp nie można usunąć ani zmienić w procesie.
- Jeśli filtr zezwala
fork(2)
lub clone(2)
jest dozwolony, wszelkie procesy potomne będą ograniczone przez ten sam filtr.
- Jeśli
execve(2)
jest to dozwolone, istniejący filtr zostanie zachowany podczas połączenia z execve(2)
.
- Jeśli
prctl(2)
zezwolenie na syscall jest dozwolone, proces może zastosować kolejne filtry.