Linux の機能と seccomp の正確な違いを知りたいです。
正確を説明します 以下に違いがありますが、一般的な説明は次のとおりです。機能には、システムコールによって到達可能なカーネル関数のさまざまなチェックが含まれます。チェックが失敗した場合 (つまり、プロセスに必要な機能がない場合)、システムコールは通常、エラーを返すように作成されます。このチェックは、特定の syscall の開始時に行うことも、複数の異なる syscall (特定の特権ファイルへの書き込みなど) によって到達可能な領域のカーネルのより深い部分で行うこともできます。
Seccomp は、実行前にすべてのシステムコールに適用されるシステムコール フィルタです。プロセスは、特定の syscall を実行する権利、または特定の syscall の特定の引数を取り消すことができるフィルターを設定できます。通常、フィルターは eBPF バイトコードの形式であり、カーネルはこれを使用して、そのプロセスに対して syscall が許可されているかどうかを確認します。フィルターが適用されると、それを緩めることはまったくできず、厳密にするだけです (seccomp ポリシーの読み込みを担当するシステムコールがまだ許可されていると仮定して)。
一部のシステムコールは、実際のシステムコールではないため、seccomp または機能によって制限できないことに注意してください。これは、厳密にはカーネルを必要としないいくつかの syscall のユーザー空間実装である vDSO 呼び出しの場合です。 getcpu()
をブロックしようとしています または gettimeofday()
いずれにせよ、プロセスはネイティブ syscall の代わりに vDSO を使用するため、この理由で無駄です。ありがたいことに、これらの syscall (および関連する仮想実装) はほとんど無害です。
また、Linux の機能が内部で seccomp を使用しているのか、それとも逆なのか、それともまったく異なるものなのか.
それらは内部で完全に異なる方法で実装されています。さまざまなサンドボックス テクノロジの現在の実装に関する別の回答を別の場所に書き、それらの違いとその目的を説明しています。
機能
特権的なことを行う多くのシステムコールには、呼び出しプロセスに十分な機能があることを確認するための内部チェックが含まれる場合があります。カーネルは、プロセスが持っている機能のリストを保存します。プロセスが削除した機能は、元に戻すことはできません。たとえば、/dev/cpu/*/msr
に書き込もうとすると、 open()
を呼び出しているプロセスでない限り、失敗します。 システムコールには CAP_SYS_RAWIO
があります .これは、MSR (低レベルの CPU 機能) の変更を担当するカーネル ソース コードで確認できます。
static int msr_open(struct inode *inode, struct file *file)
{
unsigned int cpu = iminor(file_inode(file));
struct cpuinfo_x86 *c;
if (!capable(CAP_SYS_RAWIO))
return -EPERM;
if (cpu >= nr_cpu_ids || !cpu_online(cpu))
return -ENXIO; /* No such CPU */
c = &cpu_data(cpu);
if (!cpu_has(c, X86_FEATURE_MSR))
return -EIO; /* MSR not supported */
return 0;
}
一部のシステム コールがまったく実行されない vhangup()
などの正しい機能が存在しない場合 :
SYSCALL_DEFINE0(vhangup)
{
if (capable(CAP_SYS_TTY_CONFIG)) {
tty_vhangup_self();
return 0;
}
return -EPERM;
}
機能は、プロセスまたはユーザーから選択的に削除できる特権機能の広範なクラスと考えることができます。機能チェックを持つ特定の機能は、カーネルのバージョンごとに異なり、特定の機能を実行するために機能を必要とするかどうかについて、カーネル開発者の間で論争がしばしばあります。 一般 、プロセスから機能を減らすと、実行できる特権アクションの数が減るため、セキュリティが向上します。一部の機能はルートと同等と見なされることに注意してください 、つまり、他のすべての機能を無効にしても、状況によっては、それらを使用して完全な権限を取り戻すことができます。多くの例は、grsecurity の作成者である Brad Spengler によって示されています。明らかな例は CAP_SYS_MODULE
です これにより、任意のカーネル モジュールをロードできます。もう 1 つは CAP_SYS_ADMIN
です。 これは、root とほぼ同等の包括的な機能です。
モード 1 秒圧縮
seccomp には、モード 1 (厳格) とモード 2 (フィルター) の 2 種類があります。モード 1 は非常に制限的で、一度有効にすると 4 つのシステムコールしか許可されません。これらのシステムコールは read()
です 、 write()
、 exit()
、および rt_sigreturn()
.プロセスに致命的な SIGKILL
がすぐに送信されます ホワイトリストにないシステムコールを使用しようとすると、カーネルからシグナルが送信されます。このモードは元の seccomp モードであり、eBPF バイトコードを生成してカーネルに送信する必要はありません。特別な syscall が行われ、その後、モード 1 がプロセスの存続期間中アクティブになります:seccomp(SECCOMP_SET_MODE_STRICT)
または prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT)
.一度アクティブになると、オフにすることはできません。
以下は、42 を返すバイトコードを安全に実行するプログラムの例です:
#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>
/* "mov al,42; ret" aka "return 42" */
static const unsigned char code[] = "\xb0\x2a\xc3";
void main(void)
{
int fd[2], ret;
/* spawn child process, connected by a pipe */
pipe(fd);
if (fork() == 0) {
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 {
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);
}
}
モード 1 は元のモードであり、生の計算で信頼できないバイトコードを実行できるようにする目的で追加されました。ブローカ プロセスは子プロセスを fork し (パイプを介して通信をセットアップする可能性もあります)、子プロセスは seccomp を有効にして、既に開いているファイル記述子の読み取りと書き込み以外は何も実行できないようにし、終了します。この子プロセスは、信頼されていないバイトコードを安全に実行できます。このモードを使用する人はあまり多くありませんでしたが、Linus がこのモードを停止するほど大声で文句を言う前に、Google Chrome チームは自分のブラウザーでこのモードを使用したいという要望を表明しました。これにより、seccomp への新たな関心が生まれ、早すぎる死から救われました。
モード 2 秒圧縮
2 番目のモードであるフィルターは、seccomp-bpf とも呼ばれ、プロセスがきめ細かいフィルター ポリシーをカーネルに送信し、syscall 全体、または特定の syscall 引数または引数の範囲を許可または拒否できます。このポリシーでは、違反が発生した場合にどうするか (たとえば、プロセスを強制終了するか、システム コールを単に拒否するか) と、違反をログに記録するかどうかも指定します。 Linux syscall はレジスターに保持されるため、整数しか使用できないため、syscall 引数が指すメモリの内容をフィルター処理することは不可能です。たとえば、 open()
を防ぐことはできますが 書き込み可能な O_RDWR
で呼び出されないようにする または O_WRONLY
開いている個々のパスをホワイトリストに登録することはできません。この理由は、seccomp にとって、パスは null で終わるファイルシステム パスを含むメモリへのポインタにすぎないためです。パスを保持しているメモリが、seccomp チェックのパスと逆参照されるポインタの間で、兄弟スレッドによって変更されていないことを保証する方法はありません。それを読み取り専用メモリに配置し、メモリ関連の syscalls へのアクセスを拒否する以外にはありません。多くの場合、AppArmor などの LSM を使用する必要があります。
これは、モード 2 の seccomp を使用して、現在の PID のみを表示できるようにするプログラムの例です。このプログラムは、seccomp eBPF フィルターの作成を容易にする libseccomp ライブラリーを使用しますが、抽象化ライブラリーなしで難しい方法で作成することも可能です。
#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());
}
モード 1 には明らかに制限があったため、モード 2 の seccomp が作成されました。すべてのタスクが、子プロセスで実行され、パイプまたは共有メモリを介して通信できる純粋なバイトコード プロセスに分割できるわけではありません。このモードにははるかに多くの機能があり、その機能はゆっくりと拡張され続けています。ただし、まだ欠点があります。モード 2 の seccomp を安全に使用するには、syscall を深く理解する必要があります (kill()
をブロックしたい) 他のプロセスを殺すことから?悪いことに、fcntl()
でプロセスを強制終了できます それも!)。また、基礎となる libc を変更すると破損する可能性があるため、脆弱です。 glibc open()
たとえば、関数は常にその名前のシステムコールを使用するのではなく、代わりに openat()
を使用する場合があります 、前者のみをホワイトリストに登録したポリシーを破ります。