Linuxサーバーで信頼できない計算専用の実行可能ファイルを起動したいです。プロセスは、以下を除いてstdin
システムとファイルにアクセスできません。stdout
私の考えは、ptrace
Linuxカーネルへのシステムコールをキャッチしてブロックすることです。また、プロセスの内部状態(レジスタ+ RAM)の取得と設定にも使用します。サンドボックスは安全ですか?どのブレーキ方法を知っていますか?
また、DOSを避けるためにRAMとCPU時間の使用量を制限したいと思います。
答え1
これがseccompの目的です。ほとんどの最新のLinuxカーネルは、システムコールをフィルタリングするように設計されたSeccompをサポートしています。モード1とモード2の2つの形式で提供されます。
モード1秒圧縮
このプロセスではread()
、、、および4つのシステム呼び出しのみを許可します(これらのシステム呼び出しは関数ではなくシステム呼び出しwrite()
です。glibc関数はホワイトリストにないシステム呼び出しを使用します)。別の呼び出しを試みると応答しなくなり、プログラムが終了します。これは、セキュリティエージェントプロセス内で信頼できないバイトコードを評価するためのものです。信頼できるコードは、モード1 seccompが有効になっている場合に潜在的に危険なバイトコードを実行し、パイプを介して親プロセスと通信できる信頼できないプロセスを生成できます。rt_sigreturn()
exit()
exit()
exit_group()
モード2秒計算
これは、eBPFバイトコードを使用して数値とパラメータに基づいてシステムコールを制限する動的フィルタを生成するため、seccomp-bpfとも呼ばれます。また、プロセスを強制終了することからシステムコールを拒否し、プロセスを終了せずに捕捉するように信号をエクスポートすること、カスタムエラー番号を返すこと、単にシステムコールを拒否するまで、違反に対してさまざまなアクションを取るように設定しますできます。テスト目的で。 libseccompライブラリはこれらの多くを抽象化するため、eBPFバイトコードを直接作成する必要はありません。
どちらの方法も、かなりのオーバーヘッドをもたらすptraceベースのサンドボックス化よりもはるかに高速です。また、ptrace-sandboxはフィルタをサブプロセスに送信するわけではないため、TOCTOU競合execve()
状態に脆弱ではないように、などの呼び出しを無効にする必要があります。一方、2つのseccompモードは、すべての実行またはフォークでフィルタを保持します。fork()
vfork()
clone()
バイトコードで「return 42」を安全に実行するためにモード1 seccompを使用する例:
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
void main(void)
{
/* "mov al,42; ret" aka "return 42" */
static const unsigned char code[] = "\xb0\x2a\xc3";
int fd[2], ret;
/* spawn child process, connected by a pipe */
pipe(fd);
if (fork() == 0) {
/* we're the child, so let's close this end of the pipe */
close(fd[0]);
/* enter mode 1 seccomp and execute untrusted bytecode */
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
ret = (*(uint8_t(*)())code)();
/* send result over pipe, and exit */
write(fd[1], &ret, sizeof(ret));
syscall(SYS_exit, 0);
} else {
/* we're the parent, so let's close this end of the pipe */
close(fd[1]);
/* read the result from the pipe, and print it */
read(fd[0], &ret, sizeof(ret));
printf("untrusted bytecode returned %d\n", ret);
}
}
複数のランダムシステムコールフィルタでモード2 seccompを使用する例:
#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
void main(void)
{
/* initialize the libseccomp context */
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
/* allow exiting */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
/* allow getting the current pid */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
/* allow changing data segment size, as required by glibc */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
/* allow writing up to 512 bytes to fd 1 */
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
SCMP_A0(SCMP_CMP_EQ, 1),
SCMP_A2(SCMP_CMP_LE, 512));
/* if writing to any other fd, return -EBADF */
seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(write), 1,
SCMP_A0(SCMP_CMP_NE, 1));
/* load and enforce the filters */
seccomp_load(ctx);
seccomp_release(ctx);
printf("this process is %d\n", getpid());
}
seccompを使用するときに覚えておくべきいくつかの重要なことがあります。
「実際の」システムコールではありませんが、vDSO(および
gettimeofday()
)呼び出しtime()
はフィルタリングできません。パフォーマンスを向上させるためにユーザースペースで実行されるため、高価なコンテキスト切り替えを防ぎます。ただし、これによりseccompが実行されているかどうかは不明になります。 vDSOで実装できる唯一のシステムコールは、一般に非常に単純であり、攻撃面をほとんどまたはまったく露出させないため、一般的に問題になりません。Linux 4.8(?)以前は、
ptrace()
呼び出しが許可されてから実際に実行される前にレジスタを変更して、ホワイトリスト呼び出しを使用してサンドボックスをエスケープすることができました。 4.8以前のカーネルの場合、解決策はこの呼び出しをホワイトリストに追加しないことです。システムコールはレジスタをカーネルに渡して動作するため、seccomp(およびすべてのptraceベースのサンドボックス)は、レジスタ自体の内容に基づいてのみフィルタリングできます。これは
open()
、指定されたファイル名などのメモリポインタを含む引数をフィルタリングできないことを意味します。 Seccompは、メモリではなくレジスタの内容のみを確認します。フィルタを適用した後は、元に戻したり変更したりすることはできません。サンドボックスの複数の手順を使用するには、より寛大なポリシーから始めて新しいフィルタを追加する必要があるため、次の手順がロードされるまでホワイトリストに追加されることを確認してください(
seccomp()
> = Linux 3.17で)。prctl()
2番目のステップサンドボックスは、最初のステップと同じシステムコールをホワイトリストに追加し、無効にしたいシステムコールを除外するか、オプションで無効にしたいシステムコールをブラックリストに追加する必要があります。