QUESTION
root 권한을 버리면 process의 syscall 공격 표면도 자동으로 줄어드는가?
UID/capability는 어떤 privileged operation을 허용할지 kernel permission check에 참여한다. seccomp filter는 syscall 번호와 일부 raw argument를 기준으로 호출 자체를 allow, errno, trap, kill, notify 등으로 처리한다. 두 기능은 서로 보완하지만 대체하지 않는다.
filter를 설치한 뒤 필요한 open/socket syscall이 막힐 수 있으므로 resource를 먼저 열고 fd를 전달하거나 broker를 둔다. no_new_privs는 exec를 통한 새 privilege 획득을 막고 unprivileged filter 설치의 전제가 된다.
STRUCTURE
구조 그림
capability와 seccomp는 다른 질문에 답한다. capability는 operation 권한을, seccomp는 syscall 진입 자체를 제한한다.
CALL PATH
호출 흐름
초기화 단계와 제한 후 steady-state syscall 집합을 분리한다. filter는 architecture와 ABI를 확인하고 default deny에서 필요한 호출을 계측해 추가한다.
함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.
SOURCE COORDINATES
Linux 6.18.37 LTS 소스 위치
glibc 함수에서 멈추지 않고 syscall 구현과 커널 객체가 만나는 파일까지 내려간다. 링크는 동일한 태그의 원본 파일을 가리킨다.
| 파일 | 함수·구조체 | 여기서 볼 것 |
|---|---|---|
| kernel/seccomp.c | do_seccomp(), seccomp_run_filters() | filter 설치와 syscall entry action 결정 |
| kernel/capability.c | cap_capable(), capable() | capability permission check 기본 경로 |
| kernel/sys.c | prctl_set_seccomp(), set_user() | process attribute와 credential 변경 |
COMPLETE PROGRAM
실행 예제 원본
아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.
cc -std=c17 -Wall -Wextra -O2 seccomp_strict.c -o seccomp_strict01#define _GNU_SOURCE
02#include <linux/seccomp.h>
03#include <stdio.h>
04#include <sys/prctl.h>
05#include <sys/syscall.h>
06#include <unistd.h>
07
08int main(void)
09{
10 const char message[] = "strict mode allows write\n";
11 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0)
12 return 1;
13 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT) < 0)
14 return 1;
15
16 write(STDOUT_FILENO, message, sizeof(message) - 1);
17 syscall(SYS_exit, 0);
18 __builtin_unreachable();
19}
CODE NOTES
코드 조각별 설명
const char message[]strict mode 설치 전에 필요한 data를 준비한다. filter 뒤 malloc/stdio/open 같은 호출은 허용 목록에 없을 수 있다.
PR_SET_NO_NEW_PRIVS이 thread와 이후 child가 exec를 통해 setuid/file capability로 새 privilege를 얻지 못하게 하는 단방향 속성이다.
SECCOMP_MODE_STRICTread, write, _exit, sigreturn만 허용하는 고정 mode다. 실용 service는 BPF filter mode와 libseccomp를 사용한다.
write(STDOUT_FILENOstrict allowlist 안의 syscall을 사용한다. glibc wrapper 내부에서 다른 syscall을 추가로 부르는 API는 주의한다.
syscall(SYS_exit, 0)glibc _exit wrapper는 exit_group을 사용할 수 있지만 strict mode는 SYS_exit만 허용한다. raw syscall로 현재 단일 thread를 종료한다.
DETAILS
세부 동작
syscall 번호만으로 pointer 내용을 검사할 수 없다
classic seccomp BPF는 syscall argument raw value를 볼 수 있지만 pathname 문자열이나 pointer가 가리키는 mutable memory를 안전하게 dereference하지 않는다. open path policy는 dirfd 사전 개방, namespace, broker, LSM과 결합한다.
argument가 64-bit일 때 endian/word 분해도 architecture별로 정확히 처리한다.
capability set은 여러 종류다
permitted, effective, inheritable, bounding, ambient set이 exec 규칙에 참여한다. 단순 capset 한 번으로 모든 future privilege 경로가 사라진다고 가정하지 않는다.
supplementary group, securebits, user namespace, file capability도 함께 기록한다.
filter 배포에는 관찰 모드가 필요하다
allowlist 누락은 정상 workload의 드문 error path에서만 나타날 수 있다. SECCOMP_RET_LOG, audit, test corpus로 실제 syscall을 모으고 architecture별 ABI를 포함한다.
SIGSYS/core 정보에는 syscall 번호와 architecture가 있어 실패 분석에 활용한다.
OBJECTS
객체와 수명
| 대상 | 언제 생기고 없어지는가 | 확인할 값 |
|---|---|---|
seccomp filter | thread에 설치되고 TSYNC로 thread group에 동기화할 수 있으며 제거할 수 없다 | BPF program, action precedence |
credential/cap sets | fork에서 복제되고 set*id/capset/exec에서 바뀐다 | effective/permitted/bounding/ambient |
pre-opened fd | 제한 전 확보하고 service loop가 capability처럼 사용한다 | access mode, CLOEXEC, owner |
FAILURE PATH
실패 조건과 오해하기 쉬운 부분
| 겉으로 보이는 현상 | 실제 원인 후보 | 확인 방법 |
|---|---|---|
| SIGSYS/즉시 종료 | allowlist 누락 또는 arch mismatch | audit log, siginfo syscall/arch |
| 권한이 예상보다 남음 | capability bounding/ambient/group 미정리 | /proc/PID/status Cap*, Groups |
| filter 뒤 초기화 실패 | 필요 resource를 늦게 open | syscall timeline과 phase 분리 |
LAB
직접 확인
- strict mode 뒤 getpid를 호출해 process 종료 동작을 격리된 shell에서 확인한다.
- libseccomp filter로 read/write/exit/futex 정도만 허용하고 strace/audit로 누락 syscall을 보완한다.
- capsh --print와 /proc/self/status로 UID 변경 전후 capability set을 표로 기록한다.
./seccomp_strictstrace -e trace=prctl,write,exit_group ./seccomp_strictPRIMARY REFERENCES