O que você tem aqui é exatamente o caso de uso do seccomp .
Usando o seccomp, você pode filtrar os syscalls de maneiras diferentes. O que você quer fazer nesta situação é, logo depois fork()
, instalar um seccomp
filtro que não permite o uso de open(2)
, openat(2)
, socket(2)
(e mais). Para fazer isso, você pode fazer o seguinte:
- Primeiro, crie um contexto seccomp usando
seccomp_init(3)
o comportamento padrão de SCMP_ACT_ALLOW
.
- Em seguida, adicione uma regra ao contexto usando
seccomp_rule_add(3)
para cada syscall que você deseja negar. Você pode usar SCMP_ACT_KILL
para interromper o processo se tentar o syscall, SCMP_ACT_ERRNO(val)
fazer com que o syscall falhe ao retornar o errno
valor especificado ou qualquer outro action
valor definido na página do manual.
- Carregue o contexto usando
seccomp_load(3)
para torná-lo eficaz.
Antes de continuar, NOTE que uma abordagem de lista negra como esta é geralmente mais fraca que uma abordagem de lista de permissões. Ele permite qualquer syscall que não seja explicitamente proibido e pode resultar em um desvio do filtro . Se você acredita que o processo filho que você deseja executar pode estar tentando maliciosamente evitar o filtro, ou se você já sabe quais syscalls serão necessários pelos filhos, uma abordagem da lista de desbloqueio é melhor e você deve fazer o oposto do acima: crie um filtro com a ação padrão de SCMP_ACT_KILL
e permita os syscalls necessários com SCMP_ACT_ALLOW
. Em termos de código, a diferença é mínima (a lista de permissões provavelmente é mais longa, mas as etapas são as mesmas).
Aqui está um exemplo do exposto acima (estou fazendo exit(-1)
em caso de erro apenas por uma questão de simplicidade):
#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);
}
Agora, no seu programa, você pode chamar a função acima para aplicar o filtro seccomp logo após o fork()
seguinte:
child_pid = fork();
if (child_pid == -1)
exit(-1);
if (child_pid == 0) {
secure();
// Child code here...
exit(0);
} else {
// Parent code here...
}
Algumas notas importantes sobre o seccomp:
- Um filtro seccomp, uma vez aplicado, não pode ser removido ou alterado pelo processo.
- Se
fork(2)
ou clone(2)
for permitido pelo filtro, qualquer processo filho será restringido pelo mesmo filtro.
- Se
execve(2)
for permitido, o filtro existente será preservado em uma chamada para execve(2)
.
- Se o
prctl(2)
syscall for permitido, o processo poderá aplicar outros filtros.