Sandbox?
SECCOMP?
Modes
SECCOMP 구성 코드
int __secure_computing(const struct seccomp_data *sd) {
int mode = current->seccomp.mode;
int this_syscall;
...
this_syscall = sd ? sd->nr : syscall_get_nr(current, task_pt_regs(current));
switch (mode) {
case SECCOMP_MODE_STRICT:
__secure_computing_strict(this_syscall); /* may call do_exit */
return 0;
case SECCOMP_MODE_FILTER:
return __seccomp_filter(this_syscall, sd, false);
...
}
}
mode에 따라서 STRICT 검사 혹은 FILTER 검사를 할 것인지 선택하는 것을 알 수 있다.
STRICT_MODE?
static const int mode1_syscalls[] = {
__NR_seccomp_read,
__NR_seccomp_write,
__NR_seccomp_exit,
__NR_seccomp_sigreturn,
-1, /* negative terminated */
};
#ifdef CONFIG_COMPAT
static int mode1_syscalls_32[] = {
__NR_seccomp_read_32,
__NR_seccomp_write_32,
__NR_seccomp_exit_32,
__NR_seccomp_sigreturn_32,
0, /* null terminated */
};
#endif
static void __secure_computing_strict(int this_syscall) {
const int *allowed_syscalls = mode1_syscalls;
#ifdef CONFIG_COMPAT
if (in_compat_syscall()) allowed_syscalls = get_compat_mode1_syscalls();
#endif
do {
if (*allowed_syscalls == this_syscall) return;
} while (*++allowed_syscalls != -1);
#ifdef SECCOMP_DEBUG
dump_stack();
#endif
seccomp_log(this_syscall, SIGKILL, SECCOMP_RET_KILL_THREAD, true);
do_exit(SIGKILL);
}
FILTER_MODE?
두 가지의 방법으로 적용가능하다.
라이브러리 함수 사용
#include <seccomp.h>
typedef void * scmp_filter_ctx;
scmp_filter_ctx seccomp_init(uint32_t def_action);
int seccomp_reset(scmp_filter_ctx otx, uint32_t def_action);
-lseccomp를 링크해주어야 한다.
int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...);
int seccomp_load(scmp_filter_ctx ctx);
void sandbox() {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
seccomp_load(ctx);
}
dreamhack에서 가져온 코드.
- seccomp_init(SCMP_ACT_KILL) : 필터에 해당하지 않는 경우 종료.
- seccomp_rule_add : 각 system call을 SCMP_ACT_ALLOW를 통해 허용시키는 필터를 추가한다.
- seccomp_load : 변경사항이 적용된 ctx를 실제 seccomp에 적용시킨다.
void sandbox() {
scmp_filter_ctx ctx;
ctx = seccomp_init(SCMP_ACT_ALLOW);
if (ctx == NULL) {
exit(0);
}
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(openat), 0);
seccomp_load(ctx);
}
위와 비슷하다.
- seccomp_init(SCMP_ACT_ALLOW) : 필터에 해당하지 않는 경우 허용.
- seccomp_rule_add : 각 system call을 SCMP_ACT_KILL를 통해 금지시키는 필터를 추가한다.
- seccomp_load : 변경사항이 적용된 ctx를 실제 seccomp에 적용시킨다.
BPF?
BPF 사용 방법.
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
일반적으로 prctl함수는 option인자를 통해 어떤 설정을 바꿀 것인지 결정하고, 그 이후의 인자들로 calling thread나 process의 여러 설정값들을 조정하는 역할을 한다. 여기서는 seccomp 기능에 대해서만 알아본다.BPF를 담는 구조체
cBPF(classic BPF)
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
struct sock_fprog { /* Required for SO_ATTACH_FILTER. */
unsigned short len; /* Number of filter blocks */
struct sock_filter __user *filter;
};
BPF를 사용하는 방법.
#ifndef BPF_STMT
#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k }
#endif
#ifndef BPF_JUMP
#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k }
#endif
이 부분을 보면 알겠지만, BPF_STMT, BPF_JUMP는 어떠한 구조체의 정의에 해당하는 매크로임을 알 수 있다.
실제로 struct sock_filter
배열을 선언할 때 이 매크로를 다음과 같이 사용한다 :
struct sock_filter filter[] = {
/* Validate architecture. */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),
/* Get system call number. */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
};
그래서 각각의 매크로들은 배열의 인자 하나에 해당하게 된다.
여기서 첫 번째 인자의 + 연산은 도대체 무엇일까? 다음 소스코드를 보자.
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _UAPI__LINUX_BPF_COMMON_H__
#define _UAPI__LINUX_BPF_COMMON_H__
/* Instruction classes */
#define BPF_CLASS(code) ((code) & 0x07)
#define BPF_LD 0x00
#define BPF_LDX 0x01
#define BPF_ST 0x02
#define BPF_STX 0x03
#define BPF_ALU 0x04
#define BPF_JMP 0x05
#define BPF_RET 0x06
#define BPF_MISC 0x07
/* ld/ldx fields */
#define BPF_SIZE(code) ((code) & 0x18)
#define BPF_W 0x00 /* 32-bit */
#define BPF_H 0x08 /* 16-bit */
#define BPF_B 0x10 /* 8-bit */
/* eBPF BPF_DW 0x18 64-bit */
#define BPF_MODE(code) ((code) & 0xe0)
#define BPF_IMM 0x00
#define BPF_ABS 0x20
#define BPF_IND 0x40
#define BPF_MEM 0x60
#define BPF_LEN 0x80
#define BPF_MSH 0xa0
/* alu/jmp fields */
#define BPF_OP(code) ((code) & 0xf0)
#define BPF_ADD 0x00
#define BPF_SUB 0x10
#define BPF_MUL 0x20
#define BPF_DIV 0x30
#define BPF_OR 0x40
#define BPF_AND 0x50
#define BPF_LSH 0x60
#define BPF_RSH 0x70
#define BPF_NEG 0x80
#define BPF_MOD 0x90
#define BPF_XOR 0xa0
#define BPF_JA 0x00
#define BPF_JEQ 0x10
#define BPF_JGT 0x20
#define BPF_JGE 0x30
#define BPF_JSET 0x40
#define BPF_SRC(code) ((code) & 0x08)
#define BPF_K 0x00
#define BPF_X 0x08
#ifndef BPF_MAXINSNS
#define BPF_MAXINSNS 4096
#endif
#endif /* _UAPI__LINUX_BPF_COMMON_H__ */
from /include/uapi/linux/bpf_common.h
너무 길긴 하지만 일단 보면은
비트영역의 분할 기준 (eBPF를 기준으로 설명, cBPF도 크게 다르지 않을 듯.)
앞의 헤더파일을 참고로 다시 돌아가서 보면,
struct sock_filter filter[] = {
/* Validate architecture. */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),
/* Get system call number. */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
};
그래서 이거 왜함?
2편에 이어서...