PlaidCTF 2014のjackshitを読む際に, libseccompのseccomp_rule_addでsyscallに負数を渡していることが気になったのでメモ書きとして残しておく.
Secure Computing(Seccomp)は, プロセスのサンドボックス化のためのLinuxカーネルの機構である. read/write/exit/sigreturn以外を制限するMode 1はLinuxの2.6.12でマージされた機能である[1].
Linux 3.5ではMode 2が追加され, 任意のシステムコールの制限が可能となり, 記述もBSD Packet Filter(BPF)を利用することとなった[2].
libseccomp[3]は, Seccompを使う上で生じるアーキテクチャ毎の差異などの吸収を行うライブラリである. 現在, x86, ARM, MIPSがサポートされている[4].
マニュアルにあるCでのサンプル[5]をFig. 1に示す.
Fig. 1 An example of seccomp code
#include <fcntl.h>
#include <seccomp.h>
#include <sys/stat.h>
#include <sys/types.h>
#define BUF_SIZE 256
int main(int argc, char *argv[])
{
int rc = -1;
scmp_filter_ctx ctx;
struct scmp_arg_cmp arg_cmp[] = { SCMP_A0(SCMP_CMP_EQ, 2) };
int fd;
unsigned char buf[BUF_SIZE];
ctx = seccomp_init(SCMP_ACT_KILL); // 全システムコールの制限をデフォルトとする.
if (ctx == NULL)
goto out;
/* ... */
fd = open("file.txt", 0);
/* ... */
// closeを許可.
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
if (rc < 0)
goto out;
// readを第一引数がfd, 第二引数がbuf, 第三引数がBUF_SIZE以下の場合にのみ許可.
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 3,
SCMP_A0(SCMP_CMP_EQ, fd),
SCMP_A1(SCMP_CMP_EQ, (scmp_datum_t)buf),
SCMP_A2(SCMP_CMP_LE, BUF_SIZE));
if (rc < 0)
goto out;
// writeを第一引数がfdの場合にのみ許可.
rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
SCMP_CMP(0, SCMP_CMP_EQ, fd));
if (rc < 0)
goto out;
// writeをarg_cmpに指定した条件(SCMP_A0(SCMP_CMP_EQ, 2): 第一引数が2)にのみ許可.
rc = seccomp_rule_add_array(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
arg_cmp);
if (rc < 0)
goto out;
rc = seccomp_load(ctx); // ctxをロード.
if (rc < 0)
goto out;
/* ... */
out:
seccomp_release(ctx); // allowされなかったシステムコールを全て制限.
return -rc;
}
PlaidCTF 2014のPwnable200 jackshit[6]での例を元に, Pseudo syscallについて説明する.
jackshitの逆アセンブルの結果とそのデコンパイル例をFig. 2, Fig. 3に示す.
Fig. 2 The curious disassembly of jackshit
8048c56: 83 ec 0c sub $0xc,%esp
8048c59: 6a ff push $0xffffffff
8048c5b: e8 e0 fb ff ff call 8048840 <exit@plt>
...
8048ca2: 6a 00 push $0x0
8048ca4: 6a 9b push $0xffffff9b
8048ca6: 68 00 00 ff 7f push $0x7fff0000
8048cab: 53 push %ebx
8048cac: e8 2f fb ff ff call 80487e0 <seccomp_rule_add@plt>
8048cb1: 83 c4 10 add $0x10,%esp
8048cb4: 85 c0 test %eax,%eax
8048cb6: 75 9e jne 8048c56 <close@plt+0x346>
Fig. 3 A sample of decompile of the part.
if(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, -101), 0) != 0) {
exit(-1);
}
Linuxでのシステムコール番号は一般的に0以上なので, -101ではエラーになると考えられる.
結果から述べると, この「seccomp_rule_addのsyscallに負数を設定していた」というものはlibseccompのseccomp.hに定義されているPseudo syscallによるものである.
通常はシステムコール名とシステムコール番号とは/usr/includeのどこかのヘッダファイルにマクロとして定義されている. 筆者の環境では/usr/include/asm/unistd_32.hに在った. このヘッダファイルを利用することで, システムコール番号で直接的に指定すること無く対象とするシステムコールを指すことができる.
しかし, すべての環境にそのヘッダファイルがあるとは限らないため, そのような場合にも対応できるようにlibseccomp/include/seccomp.hにPseudo syscallとしていくつかのシステムコール番号が補助的に記述されているのである[7]. socket関連(-100番台)やIPC関連(-200番台)のシステムコールをはじめ, その他一般的なシステムコール番号が用意されている.
以下の3点がわかった.
- LinuxにはSeccompというサンドボックス化機構があり, Mode 2では全てのシステムコールについて制限/許可を行える.
- Pseudo syscallは環境の差異を埋めるための仕組みである.
- /usr/includeのどこにもunistd_32.hが無い環境が有り得る.
今後はlibseccompを読み進め, Linux本体のSeccompの実装にも触れていきたい.
[1] [PATCH] seccomp: secure computing support
[2] The seccomp-bpf sandbox
[3] seccomp/libseccomp
[4] libseccomp/seccomp.h.in at d1019115acdc8460c9a1f8a878768001a3c32431 · seccomp/libseccomp
[5] Ubuntu Manpage: seccomp_rule_add, seccomp_rule_add_exact - Add a seccomp filter rule
[6] write-ups-2014/plaid-ctf-2014/jackshit at master · ctfs/write-ups-2014
[7] libseccomp/seccomp.h.in at master · seccomp/libseccomp