Seccomp(secure computing mode)는 리눅스에서 sandbox 기반으로 시스템콜을 허용 및 차단하여 공격의 가능성을 막는 리눅스 보안 메커니즘이다.
Seccomp 기능은 prctl()
이라는 함수로 사용할 수 있다.
prctl
prctl()
은 프로세스를 관리하는 함수이다.
정의는 다음과 같다.
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
중요한 것은 모드를 결정하는 option
이다.
이 값에 따라서 나머지 전달되는 인자값이 달라지는데 이 인자에 PR_SET_SECCOMP 상수를 전달하면 seccomp를 설정할 수 있다.
prctl
로 seccomp를 설정하는 방법은 두 가지가 있다.
첫 번째가 SECCOMP_SET_MODE_STRICT 이다.
허용되는 시스템콜을 read(2)
, write(2)
, _exit(2)
, sigreturn(2)
(but not exit_group(2)
)로 제한하는 것이다.
그 외 시스템 콜에 대해서는 SIGKILL
을 발생시킨다.
설정방법은 다음과 같다.
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
Berkeley Packet Filter (BPF)라는 필터식을 이용해 시스템콜을 관리하는 모드이다.
BPF는 리눅스 커널에서 네트워크 패킷을 필터링할 때 사용하는 VM이다. 자세한 개념은 다음 링크를 참조
이 모드를 사용하기 위해선 쓰레드의 no_new_privs
비트가 설정되어 있어야 하며 다음의 코드로 설정가능하다.
prctl(PR_SET_NO_NEW_PRIVS, 1);
그 후 아래의 코드로 필터식을 전달하면 된다.
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, args);
args
는 prctl
이 필터를 처리하기 위해 사용하는 sock_fprog
라는 구조체의 주소값이다.
struct sock_fprog {
unsigned short len; /* Number of BPF instructions */
struct sock_filter *filter; /* Pointer to array of
BPF instructions */
};
배열로 구성한 필터식(filter
)을 저장하고 있다.
이 filter
에 대해 좀 더 알아보자.
리눅스의 seccomp에서 시스템콜을 관리하기 위해 사용하며 prctl
가 사용하는 sock_fprog
구조체 안에 저장되는 값이다.
필터식은 시스템콜이 호출될 때 실행되고, 필터식의 결과로 반환되는 값에 의해 실행여부가 결정된다.
sock_fprog
안의 filter
의 정의를 보자.
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
BPF는 어셈블리 언어와 비슷하다.
code
값으로 인스트럭션을 결정하고, jt
와 jf
는 조건에 따른 분기점, k
는 사용되는 상수값이나 기타 다양한 용도로 사용된다.
예시를 확인해보자.
bobctf2017의 megabox
문제에서 사용하는 필터식이다.
root@kali:/work/ctf/BOBCTF2017/megabox_d# seccomp-tools dump ./megabox > dump
root@kali:/work/ctf/BOBCTF2017/megabox_d# cat dump
your name... this step is for performance :) line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x01 0x00 0xc000003e if (A == ARCH_X86_64) goto 0003
0002: 0x06 0x00 0x00 0x00000000 return KILL
0003: 0x20 0x00 0x00 0x00000000 A = sys_number
0004: 0x15 0x00 0x01 0x0000000f if (A != rt_sigreturn) goto 0006
0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0006: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0008
0007: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0008: 0x15 0x00 0x01 0x0000003c if (A != exit) goto 0010
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x15 0x00 0x01 0x00000000 if (A != read) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0014
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0014: 0x06 0x00 0x00 0x00000000 return KILL
일부만 짚어서 보면, code
가 0x20을 가질 때는 대입 연산을 나타내며 마지막 k
가 대입되는 값을 가리킨다.
k
가 4일 때는 아키텍쳐가 저장되고, 0일 때는 sys_number
가 저장되는 것을 알 수 있다.
마찬가지로, code
가 0x15일 때는 if
문을 나타낸다. k
는 비교대상 값이며 비교 후에 참일 경우 jt
에 해당하는 벡터만큼, 거짓일 경우 jf
에 해당하는 벡터만큼 분기하는 것을 확인할 수 있다.
마지막으로 return
명령어이다. code
가 0x06을 가질 때 return
을 나타내며 이것은 최종적으로 실행여부를 결정하는 명령이다.
return ALLOW
는 호출된 시스템콜을 허용하겠다는 말이며
return KILL
은 종료하겠다는 의미이다.
https://chromium.googlesource.com/chromium/src.git/+/master/docs/linux/sandboxing.md
: 샌드박싱 개념
https://www.kernel.org/doc/html/v4.16/userspace-api/seccomp_filter.html
: bpf
http://man7.org/linux/man-pages/man2/seccomp.2.html
: seccomp manpage
https://www.kernel.org/doc/Documentation/networking/filter.txt
: 필터식에 대해 자세히