드림핵 강의를 보고 이해한대로 요약한 글이다
(자세한 내용은 강의 참조)
SECure COMPuting mode (SECCOMP)
리눅스 커널에서 프로그램의 샌드박싱 매커니즘을 제공하는 컴퓨터 보안 기능이다.
SECCOMP를 사용하면 애플리케이션에서 불필요한 시스템 콜의 호출을 방지할 수 있다.
(execve와 같은 시스템 콜이 굳이 실행될 필요가 없다)
해당 시스템 콜은 공격 시에 일반적으로 임의의 명령어를 실행하기 위해 사용된다.
만약 execve 시스템 콜의 실행을 방지하는 정책을 적용하면
외부의 공격으로부터 execve 시스템 콜이 실행될 경우 애플리케이션을 즉시 종료한다.
따라서 애플리케이션의 취약점이 존재해도 외부의 공격으로부터 피해를 최소화할 수 있다.
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);
...
}
}
STRICT_MODE
read, write, exit, sigreturn 시스템 콜의 호출만을 허용하여
이외의 시스템 콜의 호출 요청이 들어오면 SIGKILL 시그널을 발생하고 프로그램을 종료한다.
(매우 적은 시스템 콜만을 허용해 다양한 기능을 수행하는 애플리케이션에서 적용할 수 없음)
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);
}
model_syscalls는 read, write, exit, sigreturn 시스템 콜의 번호를 저장하고 있는 변수이며
애플리케이션의 호환 모드에 따라서 각 비트에 맞는 시스템 콜 번호를 저장한다.
시스템 콜이 호출되면 __secure_computing_strict 함수에 먼저 진입한다.
해당 함수는 전달된 시스템 콜 번호가 model_syscalls 또는 model_syscalls_32에 미리 정의된 번호와 일치하는지 검사하고
일치하지 않는다면 SIGKILL 시그널을 전달하고 SECCOMP_RET_KILL을 반환한다.
FILTER_MODE
원하는 시스템 콜의 호출을 허용하거나 거부할 수 있다.
라이브러리 함수를 이용하거나 필터링에 주로 쓰이는 Berkeley Packet Filter (BPF) 문법을 통해 적용하는 방법 두 가지로 나뉘어진다.
이 모드는 유연하게 시스템 콜 실행을 허용 및 거부할 수 있는 장점이 있다.
다음은 SECCOMP에서 제공하는 함수를 정리한 표이다.
함수 | 설명 |
seccomp_init | SECCOMP 모드의 기본 값을 설정하는 함수이다. 임의의 시스템 콜이 호출되면 이에 해당하는 이벤트가 발생한다. |
seccomp_rule_add | SECCOMP의 규칙을 추가한다. 임의의 시스템 콜을 허용하거나 거부할 수 있다. |
seccomp_load | 앞서 적용한 규칙을 애플리케이션에 반영한다. |
ALLOW LIST
seccomp 라이브러리 함수를 사용해 지정한 시스템 콜의 호출만을 허용할 수 있다.
SCMP_ACT_KILL을 통해 모든 시스템 콜의 호출을 허용하지 않는 규칙을 생성할 수 있다.
이렇게 생성된 규칙에 seccomp_rule_add 함수를 통해 세 번째 인자로 전달된 시스템 콜의 호출을 허용하는 코드를 명시하고
해당 규칙을 적용할 수 있다.
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);
}
DENY LIST
seccomp 라이브러리 함수를 사용해 지정한 시스템 콜을 호출하지 못하도록 할 수 있다.
SCMP_ACT_ALLOW를 통해 모든 시스템 콜의 호출을 허용하는 규칙을 생성할 수 있다.
이렇게 생성된 규칙에 seccomp_rule_add 함수를 통해 세 번째 인자로 전달된 시스템 콜의 호출을 거부하는 규칙을 생성할 수 있다.
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);
}
FILTER_MODE : BPF
Berkeley Packet Filter (BPF)를 사용해서 SECCOMP를 적용할 수 있다.
BPF는 커널에서 지원하는 Virtual Machine으로 본래에는 네트워크 패킷을 분석하고 필터링하는 목적으로 사용했다.
BPF는 임의 데이터를 비교하고 결과에 따라 특정 구문으로 분기하는 명령어를 제공한다.
라이브러리 함수를 통해 규칙을 정의한 것과 같이 특정 시스템 콜 호출 시에 어떻게 처리할지 명령어를 통해 구현할 수 있다.
BPF는 VM인만큼 다양한 명령어와 타입이 존재한다.
BPF_LD | 인자로 전달된 값을 누산기에 복사한다. 이를 통해 값을 복사한 후 비교 구문에서 해당 값을 비교할 수 있다. |
BPF_JMP | 지정한 위치로 분기한다. |
BPF_JEQ | 설정한 비교 구문이 일치할 경우 지정한 위치로 분기한다. |
BPF_RET | 인자로 전달된 값을 반환한다. |
BPF Macro
BPF 코드를 직접 입력하지 않고 편리하게 원하는 코드를 실행할 수 있게끔 매크로를 제공한다.
BPF_STMT
operand에 해당하는 값을 명시한 opcode로 값을 가져온다.
opcode는 인자로 전달된 값에서 몇 번째 인덱스에서 몇 바이트를 가져올 것인지를 지정할 수 있다.
BPF_STMT(opcode, operand)
BPF_JUMP
BPF_STMT 매크로를 통해 저장한 값과 operand를 opcode에 정의한 코드로 비교하고
비교 결과에 따라 특정 오프셋으로 분기한다.
BPF_JUMP(opcode, operand, true_offset, false_offset)
ALLOW LIST
BPF를 통해 지정한 시스템 콜의 호출만을 허용할 수 있다.
아키텍처 검사
현재 아키텍처가 X86_64라면 아래 BPF_STMT를 건너뛰고
다른 아키텍처라면 아래 BPF_STMT를 수행해 SECCOMP_RET_KILL을 반환하고 프로그램을 종료한다.
#define arch_nr (offsetof(struct seccomp_data, arch))
#define ARCH_NR AUDIT_ARCH_X86_64
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),
시스템 콜 검사
호출된 시스템 콜의 번호를 저장하고 ALLOW_SYSCALL 매크로를 호출한다.
해당 매크로는 호출된 시스템 콜이 인자로 전달된 시스템 콜(rt_sigreturn, open, ... exit_group)과 일치하는지 비교하고 같다면 SECCOMP_RET_ALLOW를 반환한다.
만약 다른 시스템 콜이라면 KILL_PROCESS를 호출해 SECCOMP_RET_KILL을 반환하고 프로그램을 종료한다.
#define ALLOW_SYSCALL(name) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
#define KILL_PROCESS \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
ALLOW_SYSCALL(rt_sigreturn),
ALLOW_SYSCALL(open),
ALLOW_SYSCALL(openat),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(exit_group),
KILL_PROCESS,
DENY LIST
BPF를 통해 지정한 시스템 콜을 호출하지 못하도록 할 수 있다.
아키텍처 검사
현재 아키텍처가 X86_64라면 다음 코드로 분기하고
다른 아키텍처라면 SECCOMP_RET_KILL을 반환하고 프로그램을 종료한다.
(allow list와 동일)
#define arch_nr (offsetof(struct seccomp_data, arch))
#define ARCH_NR AUDIT_ARCH_X86_64
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),
시스템 콜 검사
호출된 시스템 콜의 번호를 저장하고 DENY_SYSCALL 매크로를 호출한다.
해당 매크로는 호출된 시스템 콜이 인자로 전달된 시스템 콜과 일치하는지 비교하고
같다면 SECCOMP_RET_KILL을 반환해 프로그램을 종료한다.
#define DENY_SYSCALL(name) \
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL)
#define MAINTAIN_PROCESS \
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
DENY_SYSCALL(open),
DENY_SYSCALL(openat),
MAINTAIN_PROCESS,
(일단 대충 여기까지 이해함..)
레퍼런스
https://learn.dreamhack.io/263
'background > linux' 카테고리의 다른 글
SigReturn-Oriented Programming(SROP) (0) | 2022.12.30 |
---|---|
Master Canary (0) | 2022.12.08 |
cyclic pattern for buffer overflow attack (0) | 2022.11.11 |
Double Free Bug & Tcache Poisoning (0) | 2022.11.03 |
ptmalloc2 (0) | 2022.11.01 |