← System Programming DUJINLABS.COM

Observe / Harden · Linux userspace / kernel ABI

seccomp와 capability 경계

syscall allowlist와 privilege 분해를 같은 보안 기능으로 뭉치지 않고, no_new_privs·filter 설치·fd 선확보 순서를 설명합니다.

Series
36 / 37
Build
cc -std=c17 -Wall -Wextra -O2 seccomp_strict.c -o seccomp_strict
Run
./seccomp_strict
Kernel
Linux 6.18.37 LTS

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 설치의 전제가 된다.

구조 그림

그림 1. 서비스 process에 겹쳐 적용되는 권한 제한 층
Pre-opened resourceslistener · config fd · log fd
UID/GID + capabilitieseffective · permitted · bounding · ambient
no_new_privsexec를 통한 privilege 상승 차단
seccomp BPFarch · syscall nr · raw args → action
LSM / namespace / mount policyobject 단위 추가 permission

capability와 seccomp는 다른 질문에 답한다. capability는 operation 권한을, seccomp는 syscall 진입 자체를 제한한다.

호출 흐름

그림 2. 사용자 코드에서 관찰 가능한 결과까지
initialize config/fd/resource 확보
drop groups/uid/caps privilege 축소
no_new_privs exec privilege 차단
seccomp filter syscall allowlist
service loop 제한된 상태 실행

초기화 단계와 제한 후 steady-state syscall 집합을 분리한다. filter는 architecture와 ABI를 확인하고 default deny에서 필요한 호출을 계측해 추가한다.

그림 3. 커널 내부에서 지나가는 주요 지점
secure_computing syscall entry hook
BPF filter nr/arch/args 검사
action ALLOW/ERRNO/KILL
capable hook operation permission
LSM 추가 policy

함수 이름을 외우기 위한 그림이 아니다. 반환값, 파일 디스크립터, 메모리 매핑, 대기 큐 가운데 무엇이 다음 단계로 전달되는지 확인한다.

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 변경

실행 예제 원본

아래 코드는 설명을 위해 중간 줄을 생략한 의사 코드가 아니다. 파일로 빌드해 실행할 수 있는 최소 예제다.

빌드cc -std=c17 -Wall -Wextra -O2 seccomp_strict.c -o seccomp_strict
01#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}

코드 조각별 설명

실제 코드 10행const char message[]

strict mode 설치 전에 필요한 data를 준비한다. filter 뒤 malloc/stdio/open 같은 호출은 허용 목록에 없을 수 있다.

실제 코드 11행PR_SET_NO_NEW_PRIVS

이 thread와 이후 child가 exec를 통해 setuid/file capability로 새 privilege를 얻지 못하게 하는 단방향 속성이다.

실제 코드 13행SECCOMP_MODE_STRICT

read, write, _exit, sigreturn만 허용하는 고정 mode다. 실용 service는 BPF filter mode와 libseccomp를 사용한다.

실제 코드 16행write(STDOUT_FILENO

strict allowlist 안의 syscall을 사용한다. glibc wrapper 내부에서 다른 syscall을 추가로 부르는 API는 주의한다.

실제 코드 17행syscall(SYS_exit, 0)

glibc _exit wrapper는 exit_group을 사용할 수 있지만 strict mode는 SYS_exit만 허용한다. raw syscall로 현재 단일 thread를 종료한다.

세부 동작

01

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별로 정확히 처리한다.

02

capability set은 여러 종류다

permitted, effective, inheritable, bounding, ambient set이 exec 규칙에 참여한다. 단순 capset 한 번으로 모든 future privilege 경로가 사라진다고 가정하지 않는다.

supplementary group, securebits, user namespace, file capability도 함께 기록한다.

03

filter 배포에는 관찰 모드가 필요하다

allowlist 누락은 정상 workload의 드문 error path에서만 나타날 수 있다. SECCOMP_RET_LOG, audit, test corpus로 실제 syscall을 모으고 architecture별 ABI를 포함한다.

SIGSYS/core 정보에는 syscall 번호와 architecture가 있어 실패 분석에 활용한다.

객체와 수명

대상언제 생기고 없어지는가확인할 값
seccomp filterthread에 설치되고 TSYNC로 thread group에 동기화할 수 있으며 제거할 수 없다BPF program, action precedence
credential/cap setsfork에서 복제되고 set*id/capset/exec에서 바뀐다effective/permitted/bounding/ambient
pre-opened fd제한 전 확보하고 service loop가 capability처럼 사용한다access mode, CLOEXEC, owner

실패 조건과 오해하기 쉬운 부분

겉으로 보이는 현상실제 원인 후보확인 방법
SIGSYS/즉시 종료allowlist 누락 또는 arch mismatchaudit log, siginfo syscall/arch
권한이 예상보다 남음capability bounding/ambient/group 미정리/proc/PID/status Cap*, Groups
filter 뒤 초기화 실패필요 resource를 늦게 opensyscall timeline과 phase 분리

직접 확인

  1. strict mode 뒤 getpid를 호출해 process 종료 동작을 격리된 shell에서 확인한다.
  2. libseccomp filter로 read/write/exit/futex 정도만 허용하고 strace/audit로 누락 syscall을 보완한다.
  3. capsh --print와 /proc/self/status로 UID 변경 전후 capability set을 표로 기록한다.
실행./seccomp_strict
추적strace -e trace=prctl,write,exit_group ./seccomp_strict

원문