seccomp (セキュアコンピューティング)は、プロセスが特定のシステムコールを行うことを制限する方法です。
一方 linux capabilities は、特定のユーザーまたはプロセスに特権を与える方法を提供します。
したがって、プロセスがrawネットワーク接続を行うのを無効にしたい場合。そのプロセスのNET_RAW Linux機能を削除するか、seccompを使用して、そのプロセスがrawネットワークに関連するシステムコールを作成するのを制限できます。
Linuxの機能とseccompの正確な違いを知りたい。
また、Linux機能は内部でseccompを使用しているのか、それとも逆になっているのか、または両方とも完全に異なっているのですか。
Linuxの機能とseccompの正確な違いを知りたい。
exactの違いについては以下で説明しますが、一般的な説明は次のとおりです。機能には、syscallsによって到達可能なカーネル関数のさまざまなチェックが含まれます。チェックが失敗した場合(つまり、プロセスに必要な機能が不足している場合)、syscallは通常、エラーを返します。このチェックは、特定のsyscallの最初、またはカーネル内の複数の異なるsyscall(特定の特権ファイルへの書き込みなど)を介して到達できる可能性のある領域のより深いところで行うことができます。
Seccompは、実行前にすべてのsyscallに適用されるsyscallフィルターです。プロセスは、特定のsyscall、または特定のsyscallの特定の引数を実行する権利を取り消すことを可能にするフィルターをセットアップできます。フィルターは通常、 eBPF バイトコードの形式であり、カーネルはそのプロセスに対してsyscallが許可されているかどうかを確認するために使用します。いったんフィルターが適用されると、フィルターを緩めることはできず、より厳密にするだけです(seccompポリシーのロードを担当するsyscallが引き続き許可されていると想定)。
また、Linux機能は内部でseccompを使用しているのか、それとも逆になっているのか、または両方とも完全に異なっているのですか。
それらは内部で完全に異なって実装されています。私は、さまざまなサンドボックステクノロジの現在の実装に関する他の場所に 別の答え を書いて、それらの違いとその目的を説明しました。
特権的なことを行う多くのシステムコールには、呼び出しプロセスに十分な機能があることを確認するための内部チェックが含まれている場合があります。カーネルは、プロセスが持つ機能のリストを保存します。プロセスが機能をドロップすると、元に戻すことはできません。たとえば、write()
システムコールを呼び出すプロセスに_/dev/cpu/*/msr
_がない限り、_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;
}
_
機能は、プロセスまたはユーザーから選択的に削除できる特権機能の幅広いクラスと考えることができます。機能チェックを行う特定の関数は、カーネルのバージョンによって異なり、多くの場合、特定の関数を実行するために機能が必要かどうかについて、カーネル開発者の間で議論が交わされています。 一般に、プロセスの機能を減らすと、実行できる特権動作の量が減り、セキュリティが向上します。一部の機能はroot-equivalentと見なされることに注意してください。これは、他のすべての機能を無効にしても、一部の条件では、それらを使用して回復できることを意味します完全な権限。 多くの例が示されています grsecurityの作成者であるBrad Spenglerによって。明白な例は、任意のカーネルモジュールをロードできる_CAP_SYS_MODULE
_です。もう1つは_CAP_SYS_ADMIN
_で、これはrootとほぼ同等のキャッチオール機能です。
Seccompには、モード1(厳密)とモード2(フィルター)の2つのタイプがあります。モード1は非常に制限的であり、一度有効にすると 4つのシステムコール のみが許可されます。これらのsyscallは、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は元のモードであり、生の計算で信頼できないバイトコードを実行できるようにするために追加されました。ブローカープロセスは子をフォークし(パイプを介して通信をセットアップする可能性があります)、子はseccompを有効にし、すでに開いているファイル記述子の読み取りと書き込み、および終了以外の処理を実行しないようにします。この子プロセスは、信頼されていないバイトコードを安全に実行する可能性があります。多くの人がこのモードを使用していませんが、Linusが 不平を言う 大声でそれを殺す前に、Google Chrome team 欲望を表明した を使用するこれにより、seccompへの関心が新たに高まり、早すぎる死から救われました。
2番目のモードであるフィルターは、seccomp-bpfとも呼ばれ、プロセスが細粒度のフィルターポリシーをカーネルに送信できるようにし、syscall全体、または特定のsyscall引数または引数の範囲を許可または拒否します。このポリシーは、違反が発生した場合の処理(たとえば、プロセスを強制終了するか、syscallを単に拒否するか)と、違反をログに記録するかどうかも指定します。 Linuxのsyscallはレジスターに保持されているため、整数しか使用できないため、syscall引数が指す可能性のあるメモリーの内容をフィルターにかけることはできません。たとえば、書き込み可能な_O_RDWR
_または_O_WRONLY
_フラグを使用してopen()
が呼び出されないようにすることはできますが、開かれている個々のパスをホワイトリストに登録することはできません。これは、seccompにとって、パスがnullで終了するファイルシステムパスを含むメモリへのポインタにすぎないためです。パスを保持するメモリが、seccompチェックのパスと逆参照されるポインターの間の兄弟スレッドによって変更されていないことを保証する方法はありません。パスをホワイトリストに登録するには、AppArmorのようなLSMを使用する必要があります。
これは、プログラムを保護し、現在のPIDのみを出力できるようにするためにモード2 seccompを使用するサンプルプログラムです。このプログラムはlibseccompライブラリを使用します。これにより、seccomp eBPFフィルターの作成が容易になりますが、抽象化ライブラリなしで難しい方法で行うこともできます。
_#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がこれに対する答えでした。はるかに多くの機能があり、その機能は徐々に拡張され続けています。ただし、それでも欠点があります。モード2 seccompを安全に使用するには、syscallを深く理解する必要があります(kill()
が他のプロセスを強制終了しないようにしたいですか?悪いことに、fcntl()
でもプロセスを強制終了できます!)。基盤となるlibcを変更すると破損する可能性があるため、脆弱です。たとえば、glibc open()
関数は、その名前のsyscallを常に使用するのではなく、代わりにopenat()
を使用して、前者のみをホワイトリストに登録したポリシーを破る可能性があります。
Seccompは基本的に、Linux上のsyscallのファイアウォールであり、syscallのグループとして見ることができる機能です。アプリケーションのsyscallを詳細に知る必要があるため、seccompを構成するのは難しいことと、場合によってはこれが難しいことを覚えておいてください。 rawソケットでは、seccompと機能の両方でそれを行うことができます。