커널 보안 (Kernel Security)

LSM 프레임워크, SELinux, AppArmor, Landlock, seccomp-bpf, Credentials & Capabilities, 커널 하드닝, Audit 서브시스템.

관련 표준: ISO/IEC 15408 (Common Criteria, IT 보안 평가), POSIX.1-2017 (capabilities, DAC), X.509 (인증서 기반 검증) — 커널 보안 프레임워크가 참조하는 보안 평가 및 인증 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

1. 커널 보안 아키텍처 개요

Linux 커널의 보안 모델은 전통적인 DAC(Discretionary Access Control)에서 출발하여, MAC(Mandatory Access Control), 그리고 이를 유연하게 지원하는 LSM(Linux Security Modules) 프레임워크로 발전해왔습니다.

계층메커니즘설명
DACUNIX 퍼미션 (rwx), ACL파일 소유자가 접근 권한 결정. 전통적 UNIX 모델
CapabilitiesCAP_* 비트마스크root 권한을 세분화하여 최소 권한 원칙 적용
MAC (LSM)SELinux, AppArmor, SMACK, Landlock시스템 정책이 접근 권한 강제. 사용자가 변경 불가
seccompBPF 필터프로세스별 시스템 콜 제한 (샌드박싱)
Auditkauditd, auditctl보안 관련 이벤트 감사 로깅
하드닝KASLR, KPTI, CFI, SMEP/SMAP커널 익스플로잇 완화 기술

security/ 소스 트리 구조

security/ selinux/ apparmor/ smack/ landlock/ tomoyo/ yama/ security.c lsm_hooks.h commoncap.c LSM 프레임워크 코어 훅 정의 (~230개) POSIX Capabilities integrity/ keys/ lockdown/ safesetid/ IMA/EVM 키링 서브시스템 Lockdown LSM UID/GID 매핑

2. LSM (Linux Security Modules) 프레임워크

LSM은 커널에 보안 정책을 플러그인 형태로 삽입할 수 있는 프레임워크입니다. 시스템 콜 경로의 핵심 지점에 보안 훅(security hook)을 배치하여, 접근 제어 결정을 LSM 모듈에 위임합니다.

LSM 아키텍처

/* include/linux/lsm_hooks.h */
struct security_hook_heads {
    struct hlist_head bprm_creds_for_exec;
    struct hlist_head bprm_creds_from_file;
    struct hlist_head task_alloc;
    struct hlist_head task_free;
    struct hlist_head inode_permission;
    struct hlist_head file_open;
    struct hlist_head socket_create;
    /* ... 약 230개의 훅 포인트 ... */
};

/* 각 LSM 모듈이 훅을 등록하는 구조체 */
struct security_hook_list {
    struct hlist_node         list;
    struct hlist_head         *head;
    union security_list_options hook;
    const struct lsm_id      *lsmid;
};

/* security/security.c - 글로벌 훅 헤드 */
struct security_hook_heads security_hook_heads __ro_after_init;

LSM 호출 흐름:

시스템 콜 DAC 검사 (uid/gid/mode) LSM 훅 호출 security_*() SELinux: selinux_*() AppArmor: apparmor_*() Landlock: hook_*()

주요 LSM 훅 카테고리

카테고리주요 훅호출 시점
프로세스 (bprm)bprm_creds_for_exec, bprm_check_securityexecve() 시 실행 파일 검증
파일 (file)file_permission, file_open, mmap_file파일 접근, mmap 시
inodeinode_permission, inode_create, inode_unlinkinode 생성/삭제/접근
태스크 (task)task_alloc, task_setscheduler, task_kill프로세스 생성, 시그널 전송
소켓 (socket)socket_create, socket_connect, socket_bind네트워크 소켓 연산
키 (key)key_alloc, key_permission키링 접근
IPCmsg_queue_msgsnd, shm_shmatSysV IPC 접근

LSM 스태킹 (Kernel 6.x+)

커널 6.x부터 여러 major LSM을 동시에 활성화할 수 있습니다 (LSM stacking). 이전에는 SELinux, AppArmor, SMACK 중 하나만 major로 사용할 수 있었습니다.

# 현재 활성 LSM 확인
cat /sys/kernel/security/lsm
# lockdown,capability,landlock,yama,apparmor

# 부트 커맨드라인으로 LSM 순서 지정
# lsm=lockdown,capability,landlock,yama,selinux

# 빌드 시 기본 LSM 순서 설정
# CONFIG_LSM="lockdown,capability,landlock,yama,apparmor,selinux"

capability는 항상 포함되는 minor LSM입니다. lockdown, yama, landlock도 minor LSM으로 major LSM과 함께 동작합니다. LSM 스태킹에서 모든 LSM이 허용해야 접근이 승인됩니다 (AND 로직).

3. SELinux

SELinux(Security-Enhanced Linux)는 NSA가 개발한 MAC 시스템으로, Type Enforcement(TE), RBAC(Role-Based Access Control), MLS/MCS(Multi-Level/Multi-Category Security) 모델을 결합한 가장 포괄적인 리눅스 보안 프레임워크입니다. RHEL, Fedora, CentOS 등에서 기본 활성화되며, 커널 소스 security/selinux/ 디렉토리에 약 30,000줄 이상의 코드로 구현됩니다.

Type Enforcement (TE) 모델

TE는 SELinux의 핵심 모델로, 모든 프로세스(subject)와 자원(object)에 보안 컨텍스트(security context)를 부여하고, 정책 규칙에 따라 접근을 허용/거부합니다. 프로세스의 타입을 도메인(domain), 자원의 타입을 타입(type)이라 부릅니다.

# 보안 컨텍스트 확인
ls -Z /etc/passwd
# system_u:object_r:passwd_file_t:s0 /etc/passwd

ps -eZ | grep sshd
# system_u:system_r:sshd_t:s0-s0:c0.c1023  1234 ?  sshd

# 컨텍스트 형식: user:role:type:level
# user   - SELinux 사용자 (system_u, unconfined_u 등)
# role   - 역할 (system_r, object_r, unconfined_r)
# type   - 타입/도메인 (핵심 - TE 정책의 단위)
# level  - MLS/MCS 레벨 (s0, s0:c0.c1023)

TE 모델에서 attribute는 여러 타입을 그룹화하는 메타 타입으로, 정책 작성을 간소화합니다. 예를 들어 domain attribute는 모든 프로세스 도메인을, file_type은 모든 파일 타입을 묶습니다.

# attribute 정의 및 사용
attribute domain;
attribute file_type;

# httpd_t를 domain attribute에 연결
typeattribute httpd_t domain;

# domain attribute를 사용한 일괄 규칙 (모든 도메인에 적용)
allow domain self : process { fork signal_perms };
allow domain proc_t : dir { search getattr };

보안 컨텍스트와 정책 규칙

TE 정책은 allow 외에도 여러 종류의 규칙을 지원하며, 각각 다른 목적으로 사용됩니다.

# TE 정책 규칙 형식
# allow source_type target_type : object_class { permissions };

# 예: httpd가 http_port_t에 TCP 연결 허용
allow httpd_t http_port_t : tcp_socket { name_connect };

# 예: httpd가 httpd_sys_content_t 파일을 읽기 허용
allow httpd_t httpd_sys_content_t : file { read open getattr };

# neverallow — 정책 컴파일 시 위반 검사 (런타임이 아님)
# 커널 도메인이 사용자 홈 파일에 쓰기 금지
neverallow kernel_t user_home_t : file write;

# auditallow — 허용하면서 감사 로그 기록
# (기본적으로 allow된 접근은 로그하지 않음)
auditallow admin_t shadow_t : file { read };

# dontaudit — 거부하되 감사 로그 생략 (로그 노이즈 감소)
dontaudit httpd_t etc_t : dir { getattr search };

# 타입 전이 (type transition)
# init_t가 httpd_exec_t를 실행하면 httpd_t 도메인으로 전이
type_transition init_t httpd_exec_t : process httpd_t;
Object Class대표 Permission설명
fileread, write, execute, open, create, unlink, getattr, setattr, lock, ioctl, append일반 파일 접근
dirsearch, add_name, remove_name, rmdir, create, getattr, setattr, mounton디렉토리 탐색/수정
processfork, signal, ptrace, transition, setcurrent, execmem, execstack프로세스 제어
tcp_socketname_connect, name_bind, node_bind, send_msg, recv_msgTCP 소켓 연산
unix_stream_socketconnectto, sendto, recvfrom, accept, listenUnix 도메인 소켓
capabilitynet_admin, sys_admin, sys_rawio, mknod, setuid, setgidLinux Capability 사용
filesystemmount, remount, unmount, getattr, associate파일시스템 마운트
sem / shm / msgcreate, destroy, read, write, associateSystem V IPC 객체

RBAC (Role-Based Access Control)

RBAC는 SELinux 사용자가 어떤 역할(role)을 맡을 수 있는지, 각 역할이 어떤 도메인으로 전이할 수 있는지를 제어합니다. TE가 "프로세스가 자원에 무엇을 할 수 있는가"를 결정한다면, RBAC는 "사용자가 어떤 프로세스 도메인으로 진입할 수 있는가"를 결정합니다.

# 역할 정의
role system_r;
role sysadm_r;
role staff_r;
role user_r;

# 역할이 진입할 수 있는 도메인 지정
role system_r types { kernel_t init_t sshd_t httpd_t };
role sysadm_r types { sysadm_t passwd_t su_t };
role user_r   types { user_t user_home_t };

# 역할 전이 허용 (role allow)
# sysadm_r → system_r 전이 허용
allow sysadm_r system_r;

# 역할 전이 규칙 (role_transition)
# staff_r 사용자가 sudo_exec_t를 실행하면 sysadm_r로 전이
role_transition staff_r sudo_exec_t sysadm_r;

# SELinux 사용자 → 역할 매핑
# staff_u는 staff_r과 sysadm_r 역할을 사용 가능
user staff_u roles { staff_r sysadm_r };
user user_u  roles { user_r };
# 현재 사용자의 SELinux 컨텍스트 확인
id -Z
# staff_u:staff_r:staff_t:s0-s0:c0.c1023

# SELinux 사용자와 Linux 사용자 매핑 관리
semanage login -l
# Login Name    SELinux User   MLS/MCS Range      Service
# root          unconfined_u   s0-s0:c0.c1023     *
# __default__   unconfined_u   s0-s0:c0.c1023     *

# 특정 Linux 사용자를 제한된 SELinux 사용자에 매핑
semanage login -a -s staff_u devuser

MLS/MCS (Multi-Level Security)

MLS(Multi-Level Security)는 군사/정부 등급의 보안 모델(Bell-LaPadula)을 구현하며, sensitivity(민감도)와 category(범주) 레벨로 구성됩니다. MCS(Multi-Category Security)는 MLS의 간소화 버전으로, sensitivity를 단일 레벨(s0)로 고정하고 category만 사용합니다.

# MLS 레벨 형식: sensitivity[:category,...]
# sensitivity: s0 (가장 낮음) ~ s15 (가장 높음)
# category: c0 ~ c1023 (자원 분류)

# 예: s0         → 최저 민감도, 범주 없음
# 예: s0:c0.c255 → 최저 민감도, 범주 0~255
# 예: s3:c5,c9   → 민감도 3, 범주 5와 9

# MLS 정책 규칙 (range_transition)
# sshd가 로그인 프로세스 생성 시 사용자 MLS 범위 적용
range_transition sshd_t login_exec_t : process s0 - s15:c0.c1023;

# MLS 제약 (constraints)
# "No Write Down": 높은 레벨이 낮은 레벨에 쓰기 금지
mlsconstrain file { write append }
    ( l1 domby l2 or t1 == mlsfilewritedown );

# "No Read Up": 낮은 레벨이 높은 레벨 읽기 금지
mlsconstrain file { read getattr execute }
    ( l1 dom l2 or t1 == mlsfilereadtoclr );
MCS 실무 활용 — 컨테이너 격리: Docker/Podman은 MCS category를 활용하여 컨테이너 간 격리를 구현합니다. 각 컨테이너에 고유한 category 쌍(예: s0:c123,c456)을 할당하여, 동일 타입(container_t)이라도 서로 다른 컨테이너의 파일에 접근할 수 없도록 합니다. semanage login으로 가상 머신/컨테이너별 category를 관리할 수 있습니다.

도메인 전이 (Domain Transition)

도메인 전이는 프로세스가 다른 프로그램을 execve()할 때 새로운 SELinux 도메인으로 전환되는 메커니즘입니다. 이를 위해 세 가지 조건이 모두 충족되어야 합니다.

# 도메인 전이를 위한 3가지 필수 규칙

# 1. type_transition: 자동 전이 규칙 정의
# init_t가 httpd_exec_t를 실행하면 → httpd_t로 전이
type_transition init_t httpd_exec_t : process httpd_t;

# 2. allow (process transition): 소스 도메인이 대상 도메인으로 전이 허용
allow init_t httpd_t : process transition;

# 3. allow (file execute + entrypoint): 실행 파일에 대한 접근
allow init_t httpd_exec_t : file { execute open read getattr };
allow httpd_t httpd_exec_t : file entrypoint;
init_t (소스 도메인) execve() httpd_exec_t (entrypoint 파일) transition httpd_t (대상 도메인) ① type_transition init_t httpd_exec_t → httpd_t ② process transition allow init_t httpd_t : process ③ file entrypoint allow httpd_t httpd_exec_t : file 커널 내부 처리 (security/selinux/hooks.c) selinux_bprm_committing_creds() → security_transition_sid() → sidtab 조회 → AVC 캐시 갱신 → 새 도메인 SID 설정

커널 내부 구현

SELinux의 커널 구현은 security/selinux/ 디렉토리에 위치하며, LSM 훅을 통해 커널의 모든 접근 제어 지점에 개입합니다.

/* security/selinux/include/security.h */
/* selinux_state: SELinux 전역 상태 관리 구조체 */
struct selinux_state {
    bool disabled;
    bool initialized;
    bool policycap[__POLICYDB_CAP_MAX];

    struct selinux_avc    *avc;       /* Access Vector Cache */
    struct selinux_policy __rcu *policy; /* 활성 정책 (RCU 보호) */
};

/* security/selinux/ss/policydb.h */
/* policydb: 로드된 정책의 전체 데이터베이스 */
struct policydb {
    struct symtab symtab[SYM_NUM]; /* 심볼 테이블: type, role, user, bool, ... */
    struct avtab  te_avtab;       /* TE Access Vector 테이블 */
    struct avtab  te_cond_avtab;  /* 조건부(boolean) TE 규칙 */
    struct role_trans *role_tr;   /* 역할 전이 규칙 */
    struct range_trans *range_tr; /* MLS 범위 전이 규칙 */
    struct ocontext *ocontexts[OCON_NUM]; /* 초기 SID, 포트, 네트워크 등 */
    struct genfs *genfs;        /* genfscon - 파일시스템 컨텍스트 */
    /* ... */
};

/* security/selinux/hooks.c - 주요 LSM 훅 */
/* 파일 열기 시 접근 검사 */
static int selinux_file_open(struct file *file)
{
    struct inode_security_struct *isec = selinux_inode(file_inode(file));
    return file_has_perm(current_cred(), file, file_to_av(file));
}

/* 프로세스 생성(fork) 시 보안 컨텍스트 상속 */
static int selinux_task_alloc(struct task_struct *task,
                               unsigned long clone_flags)
{
    return avc_has_perm(current_sid(), task_sid_obj(task),
                        SECCLASS_PROCESS, PROCESS__FORK, NULL);
}

/* SID (Security Identifier) 테이블 */
struct sidtab {
    union {
        struct sidtab_node  *leaf;
        struct sidtab_node **inner;
    } roots[SIDTAB_MAX_LEVEL + 1];
    u32 count;
    struct sidtab_convert_params *convert; /* 정책 리로드 시 변환 */
    spinlock_t lock;
    /* radix tree 구조 — O(1) SID → context 조회 */
};
커널 CONFIG 옵션기본값설명
CONFIG_SECURITY_SELINUXy (RHEL)SELinux 모듈 빌드
CONFIG_SECURITY_SELINUX_BOOTPARAMy부트 파라미터 selinux=0/1 활성화
CONFIG_SECURITY_SELINUX_DEVELOPy개발 모드 (permissive 허용)
CONFIG_SECURITY_SELINUX_AVC_STATSyAVC 통계 (/sys/fs/selinux/avc/cache_stats)
CONFIG_SECURITY_SELINUX_SIDTAB_HASH_BITS9SID 테이블 해시 비트 수 (512 버킷)
CONFIG_DEFAULT_SECURITY_SELINUXn기본 LSM으로 SELinux 선택

AVC (Access Vector Cache) 심화

AVC는 SELinux 성능의 핵심으로, 정책 결정 결과를 캐싱하여 반복적인 policydb 조회를 방지합니다. 해시 테이블 기반으로 구현되며, seqlock을 사용하여 읽기 경로의 동기화 오버헤드를 최소화합니다.

/* security/selinux/avc.c */
/* AVC 엔트리 구조 */
struct avc_entry {
    u32 ssid;       /* Source SID */
    u32 tsid;       /* Target SID */
    u16 tclass;     /* Object class */
    struct av_decision avd; /* 캐싱된 결정 */
};

struct av_decision {
    u32 allowed;    /* 허용된 permission 비트마스크 */
    u32 auditallow; /* 감사 로그 대상 허용 */
    u32 auditdeny;  /* 감사 로그 대상 거부 */
    u32 seqno;      /* 정책 시퀀스 번호 */
    u32 flags;
};

/* AVC 접근 검사 흐름 */
int avc_has_perm(u32 ssid, u32 tsid,
                 u16 tclass, u32 requested,
                 struct common_audit_data *auditdata)
{
    struct av_decision avd;
    int rc;

    /* 1. AVC 해시 테이블에서 캐시 조회 (seqlock read) */
    rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested,
                              0, &avd);

    /* 2. 캐시 미스 → security_compute_av()로 policydb 조회 */
    /*    결과를 AVC에 삽입 */

    /* 3. 감사 로그 출력 (거부 시 또는 auditallow 매칭 시) */
    avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata);

    return rc;
}
AVC 성능 주의: AVC 캐시 크기는 /sys/fs/selinux/avc/cache_threshold로 조정 가능합니다 (기본 512). 과도한 정책 복잡도나 빈번한 정책 리로드는 AVC 무효화(flush)를 유발하여 일시적 성능 저하를 초래합니다. avc/cache_statslookups, hits, misses 비율을 모니터링하여 캐시 효율을 확인하세요.
# AVC 캐시 통계 확인
cat /sys/fs/selinux/avc/cache_stats
# lookups hits misses allocations reclaims frees
# 234891  231205  3686  3686  1024  960

# 캐시 히트율 계산: hits / lookups × 100
# 위 예시: 231205 / 234891 × 100 ≈ 98.4% (양호)

# AVC 캐시 수동 플러시 (정책 디버깅 시)
echo 1 > /sys/fs/selinux/avc/cache_stats

정책 컴파일 파이프라인

SELinux 정책은 텍스트 소스에서 바이너리 정책으로 컴파일되어 커널에 로드됩니다. Reference Policy를 기반으로 모듈화된 정책 관리가 가능합니다.

.te .if .fc Type Enforcement Interface / File Context checkmodule → .mod semodule_package → .pp semodule -i 정책 로드 커널 policydb CIL (Common Intermediate Language) SELinux 3.0+ 에서 .te 대신 .cil 직접 사용 가능 secilc (CIL 컴파일러) .cil → 바이너리 정책 직접 컴파일
# 커스텀 정책 모듈 빌드 워크플로우

# 1. Type Enforcement 소스 작성 (mymodule.te)
cat > mymodule.te << 'EOF'
policy_module(mymodule, 1.0.0)
require {
    type httpd_t;
    type myapp_data_t;
    class file { read open getattr };
}
allow httpd_t myapp_data_t : file { read open getattr };
EOF

# 2. 파일 컨텍스트 정의 (mymodule.fc)
cat > mymodule.fc << 'EOF'
/opt/myapp(/.*)?  system_u:object_r:myapp_data_t:s0
EOF

# 3. 컴파일 및 패키징
checkmodule -M -m -o mymodule.mod mymodule.te
semodule_package -o mymodule.pp -m mymodule.mod -f mymodule.fc

# 4. 커널에 로드
semodule -i mymodule.pp

# 5. 로드된 모듈 확인
semodule -l | grep mymodule

# CIL 직접 사용 (SELinux 3.0+)
# .cil 파일은 Lisp-like 문법으로 checkmodule 없이 직접 로드
semodule -i mymodule.cil

SELinux 파일시스템 (selinuxfs)

/sys/fs/selinux/에 마운트되는 selinuxfs는 커널 SELinux 서브시스템과의 사용자 공간 인터페이스를 제공합니다. 정책 로드, 상태 조회, AVC 관리 등의 모든 상호작용이 이 가상 파일시스템을 통해 이루어집니다.

# selinuxfs 주요 인터페이스
ls /sys/fs/selinux/

# 상태 및 제어
/sys/fs/selinux/enforce        # 0=permissive, 1=enforcing (읽기/쓰기)
/sys/fs/selinux/disable        # SELinux 비활성화 (부팅 후 1회 쓰기)
/sys/fs/selinux/policyvers     # 지원하는 정책 바이너리 버전
/sys/fs/selinux/status         # mmap 가능한 상태 페이지 (변경 알림)

# 정책 관리
/sys/fs/selinux/load           # 바이너리 정책 로드 (쓰기 전용)
/sys/fs/selinux/policy         # 현재 로드된 정책 바이너리 (읽기 전용)
/sys/fs/selinux/checkreqprot   # mmap/mprotect 검사 방식 (deprecated)

# AVC 관리
/sys/fs/selinux/avc/
    cache_stats                # lookups, hits, misses 통계
    cache_threshold            # 캐시 크기 임계값
    hash_stats                 # 해시 체인 길이 분포

# 컨텍스트 조회/변환
/sys/fs/selinux/context        # 컨텍스트 유효성 검사
/sys/fs/selinux/access         # 접근 결정 조회 (selinux_check_access)
/sys/fs/selinux/member         # 멤버 결정 (type 결합)
/sys/fs/selinux/create         # 새 객체 컨텍스트 계산
/sys/fs/selinux/relabel        # 릴레이블 허용 여부 확인

# Boolean 관리
/sys/fs/selinux/booleans/      # 디렉토리 내 boolean별 파일
/sys/fs/selinux/commit_pending_bools # boolean 변경 커밋

# 초기 SID 매핑
/sys/fs/selinux/initial_contexts/
    kernel                     # 커널 초기 SID 컨텍스트
    unlabeled                  # 레이블 없는 객체 기본 컨텍스트
    file                       # 레이블 없는 파일 기본 컨텍스트
/* security/selinux/selinuxfs.c */
/* 사용자 공간 라이브러리(libselinux)는 이 인터페이스를 사용 */

/* 예: libselinux의 is_selinux_enabled() 구현 */
/* → /sys/fs/selinux/ 마운트 여부 확인 */
/* → /sys/fs/selinux/enforce 읽기 */

/* 예: security_compute_av() 사용자 공간 호출 */
/* → /sys/fs/selinux/access에 ssid, tsid, tclass 쓰기 */
/* → 커널이 avc_has_perm 결과 반환 */

네트워크 레이블링

SELinux는 네트워크 패킷에도 보안 레이블을 부여하여 호스트 간 MAC을 확장합니다. CIPSO(Commercial IP Security Option), NetLabel, labeled IPsec 세 가지 메커니즘을 지원합니다.

# 네트워크 레이블링 정책 규칙

# portcon — 포트에 기본 컨텍스트 할당
portcon tcp 80 system_u:object_r:http_port_t:s0
portcon tcp 443 system_u:object_r:http_port_t:s0

# nodecon — 네트워크 노드에 컨텍스트 할당
nodecon 192.168.1.0 255.255.255.0 system_u:object_r:intranet_node_t:s0

# netifcon — 네트워크 인터페이스에 컨텍스트 할당
netifcon eth0 system_u:object_r:netif_t:s0 system_u:object_r:netmsg_t:s0

# peer 레이블링 — IPsec/CIPSO 기반 원격 피어 레이블 인식
allow myapp_t remote_peer_t : peer recv;
# NetLabel 설정 (CIPSO 기반 네트워크 레이블링)

# CIPSO DOI (Domain of Interpretation) 설정
netlabelctl cipsov4 add local doi:1

# MLS 레벨을 CIPSO 태그로 매핑
netlabelctl map add domain:httpd_t protocol:cipsov4,1

# 현재 NetLabel 설정 확인
netlabelctl -p map list

# labeled IPsec 설정 (IPsec SA에 보안 레이블 연결)
# /etc/ipsec.conf에서 policy-label 지정
# → 커널 xfrm 프레임워크가 SA의 SELinux 컨텍스트 관리

# SELinux 네트워크 관련 boolean
getsebool -a | grep network
# selinuxuser_ping → off
# httpd_can_network_connect → off
# httpd_can_network_relay → off

관리 명령

# 동작 모드 확인/변경
getenforce              # Enforcing / Permissive / Disabled
setenforce 0            # Permissive로 전환 (런타임)
sestatus                # 상세 상태 정보

# AVC 거부 로그 분석 → 정책 모듈 생성
ausearch -m avc --start today | audit2allow -M mypolicy
semodule -i mypolicy.pp

# Boolean (정책 조건부 활성화)
getsebool -a | grep httpd
setsebool -P httpd_can_network_connect on

# 파일 컨텍스트 관리
semanage fcontext -a -t httpd_sys_content_t "/web(/.*)?"
restorecon -Rv /web

# 포트 관리
semanage port -a -t http_port_t -p tcp 8080

# 특정 도메인을 permissive 모드로 설정 (전체 시스템 영향 없이)
semanage permissive -a httpd_t
# 해제
semanage permissive -d httpd_t

SELinux 디버깅 및 트러블슈팅

SELinux 문제 해결의 핵심은 AVC 거부 메시지 분석입니다. 체계적인 접근법으로 대부분의 문제를 해결할 수 있습니다.

# 1단계: AVC 거부 메시지 수집
ausearch -m avc,user_avc -ts recent
# type=AVC msg=audit(1234567890.123:456): avc:  denied  { read }
# for  pid=1234 comm="httpd" name="index.html"
# dev="sda1" ino=67890
# scontext=system_u:system_r:httpd_t:s0
# tcontext=unconfined_u:object_r:default_t:s0
# tclass=file permissive=0

# 2단계: 원인 분석 (audit2why)
ausearch -m avc -ts recent | audit2why
# 가능한 원인:
#   - Missing allow rule → 정책 규칙 부재
#   - Wrong file context → restorecon 필요
#   - Boolean off        → setsebool 필요
#   - Constrained        → MLS/RBAC 제약

# 3단계: 해결 방안 적용

# Case A: 잘못된 파일 컨텍스트
ls -Z /opt/myapp/index.html
# → default_t (잘못됨)
restorecon -Rv /opt/myapp/
# → httpd_sys_content_t (올바름)

# Case B: Boolean 비활성화
audit2why 출력에 "boolean httpd_can_network_connect" 표시 시:
setsebool -P httpd_can_network_connect on

# Case C: 커스텀 정책 필요
ausearch -m avc -ts recent | audit2allow -M myfix
# 반드시 생성된 .te 파일 검토 후 로드!
cat myfix.te     # 불필요하게 넓은 권한이 아닌지 확인
semodule -i myfix.pp

# 4단계: dontaudit 규칙이 숨긴 거부 확인
# dontaudit 비활성화하여 숨겨진 거부 메시지 표시
semodule -DB      # 모든 dontaudit 규칙 비활성화
# (문제 재현 후 로그 확인)
semodule -B       # dontaudit 규칙 복원

# 유용한 디버깅 도구
sesearch -A -s httpd_t -t httpd_sys_content_t  # 허용 규칙 검색
sesearch -D -s httpd_t                          # dontaudit 규칙 검색
seinfo -t | wc -l                               # 전체 타입 수
seinfo -r                                       # 정의된 역할 목록
seinfo -u                                       # SELinux 사용자 목록
커널 모듈 개발 시 SELinux 고려사항: 커스텀 커널 모듈이 새로운 객체 타입을 생성하거나 기존 보안 검사를 우회하는 경우, SELinux 정책과의 호환성을 반드시 검증해야 합니다. security_ 접두사 API(예: security_inode_permission())를 통해 LSM 훅을 호출하고, 모듈 전용 object class가 필요하면 security/selinux/include/classmap.h에 등록해야 합니다. 개발 초기에는 semanage permissive -a로 모듈 도메인만 permissive로 설정하여 테스트하는 것을 권장합니다.

4. AppArmor

AppArmor는 경로(path) 기반 MAC 시스템으로, 프로파일 파일에 프로그램별 접근 규칙을 정의합니다. Ubuntu, SUSE, Debian 등에서 기본 사용됩니다.

프로파일 기반 접근 제어

# /etc/apparmor.d/usr.sbin.nginx
# AppArmor 프로파일 예제

/usr/sbin/nginx {
  # 네트워크 접근
  network inet stream,
  network inet6 stream,

  # 파일 접근 규칙
  /etc/nginx/** r,           # 설정 읽기
  /var/log/nginx/** w,       # 로그 쓰기
  /var/www/** r,             # 웹 콘텐츠 읽기
  /run/nginx.pid rw,         # PID 파일
  /usr/sbin/nginx mr,        # 자기 자신 실행/mmap

  # capability
  capability net_bind_service,
  capability setuid,
  capability setgid,

  # 시그널
  signal (receive) peer=unconfined,
}
# 프로파일 관리
aa-status                    # 활성 프로파일 목록
aa-enforce /etc/apparmor.d/usr.sbin.nginx   # enforce 모드
aa-complain /etc/apparmor.d/usr.sbin.nginx  # complain(학습) 모드
aa-disable /etc/apparmor.d/usr.sbin.nginx   # 비활성화

# 새 프로파일 자동 생성 (학습 모드)
aa-genprof /usr/bin/myapp

AppArmor vs SELinux 비교

항목SELinuxAppArmor
접근 제어 모델라벨 기반 (Type Enforcement)경로(path) 기반
정책 단위보안 컨텍스트 (SID/type)프로파일 (프로그램 경로)
파일시스템 의존성xattr 라벨 필요 (ext4, XFS)경로 기반, xattr 불필요
복잡도높음 (Type/Role/MLS)낮음 (경로 규칙)
네트워크 제어세밀 (포트/타입 매핑)기본적 (프로토콜 수준)
기본 배포판RHEL, Fedora, CentOSUbuntu, SUSE, Debian
학습 모드Permissive (전역)Complain (프로파일별)

커널 구현

/* security/apparmor/ 핵심 구조체 */

struct aa_profile {
    struct aa_policy      base;
    struct aa_profile     *parent;
    struct aa_ns          *ns;
    const char           *rename;
    enum audit_mode       audit;
    long                  mode;
    struct aa_policydb    file;    /* 파일 접근 규칙 */
    struct aa_policydb    policy;  /* 정책 규칙 */
    struct aa_caps        caps;    /* capability 규칙 */
    struct aa_net_compat  *net_compat; /* 네트워크 규칙 */
};

struct aa_label {
    struct kref          count;
    int                  size;   /* 프로파일 스택 크기 */
    struct aa_profile    *vec[];  /* 프로파일 배열 (stacking) */
};

5. SMACK (Simplified Mandatory Access Control Kernel)

SMACK은 라벨 기반 MAC으로, SELinux보다 훨씬 단순한 규칙 모델을 제공합니다. IoT 디바이스, 임베디드 시스템(Tizen 등)에서 주로 사용됩니다.

# SMACK 라벨 확인
attr -S -g SMACK64 /path/to/file
# 또는
getfattr -n security.SMACK64 /path/to/file

# SMACK 규칙 형식: subject object access
# access: r(read) w(write) x(execute) a(append) t(transmute) l(lock)
echo "WebApp DataFiles rwx" > /sys/fs/smackfs/load2
echo "System WebApp rx"     > /sys/fs/smackfs/load2

# 특수 라벨
# _     : 최소 권한 (floor label)
# ^     : 최대 권한 (hat/ceiling)
# *     : 범용 접근 (star label - 모든 읽기 허용)
💡

SMACK은 정책 규칙이 단순한 subject object access 3-tuple이므로, SELinux의 복잡한 Type Enforcement 정책을 사용하기 어려운 임베디드 환경에 적합합니다.

6. Landlock

Landlock은 커널 5.13에서 도입된 비특권(unprivileged) 샌드박싱 LSM입니다. 일반 사용자가 root 권한 없이 프로세스의 파일시스템/네트워크 접근을 제한할 수 있습니다.

Unprivileged Sandboxing 아키텍처

Landlock은 3단계 API로 동작합니다:

  1. landlock_create_ruleset() — 규칙셋 생성 (어떤 접근 유형을 제어할지 정의)
  2. landlock_add_rule() — 규칙 추가 (특정 파일/디렉터리에 대한 허용 규칙)
  3. landlock_restrict_self() — 현재 프로세스에 규칙셋 적용 (되돌릴 수 없음)

Landlock API

#include <linux/landlock.h>
#include <sys/syscall.h>
#include <sys/prctl.h>

/* 1단계: 규칙셋 생성 */
struct landlock_ruleset_attr ruleset_attr = {
    .handled_access_fs =
        LANDLOCK_ACCESS_FS_READ_FILE |
        LANDLOCK_ACCESS_FS_READ_DIR  |
        LANDLOCK_ACCESS_FS_WRITE_FILE |
        LANDLOCK_ACCESS_FS_EXECUTE,
};

int ruleset_fd = syscall(SYS_landlock_create_ruleset,
                         &ruleset_attr, sizeof(ruleset_attr), 0);

/* 2단계: 허용 규칙 추가 (화이트리스트) */
struct landlock_path_beneath_attr path_attr = {
    .allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
                      LANDLOCK_ACCESS_FS_READ_DIR,
    .parent_fd = open("/usr", O_PATH | O_CLOEXEC),
};

syscall(SYS_landlock_add_rule, ruleset_fd,
        LANDLOCK_RULE_PATH_BENEATH, &path_attr, 0);
close(path_attr.parent_fd);

/* /tmp에 읽기+쓰기 허용 */
path_attr.allowed_access = LANDLOCK_ACCESS_FS_READ_FILE |
                           LANDLOCK_ACCESS_FS_WRITE_FILE;
path_attr.parent_fd = open("/tmp", O_PATH | O_CLOEXEC);
syscall(SYS_landlock_add_rule, ruleset_fd,
        LANDLOCK_RULE_PATH_BENEATH, &path_attr, 0);
close(path_attr.parent_fd);

/* 3단계: 프로세스에 적용 (되돌릴 수 없음) */
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
syscall(SYS_landlock_restrict_self, ruleset_fd, 0);
close(ruleset_fd);

/* 이후 /usr 읽기, /tmp 읽기/쓰기만 가능
   다른 경로 접근 시 -EACCES 반환 */

Landlock ABI 버전은 커널 버전마다 확장됩니다. ABI v1(5.13): 파일시스템, ABI v2(5.19): 파일 참조, ABI v3(6.2): 파일 truncate, ABI v4(6.7): 네트워크 포트 제어. landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)으로 ABI 버전을 확인할 수 있습니다.

7. seccomp-bpf 심화

seccomp(Secure Computing)은 프로세스가 호출할 수 있는 시스템 콜을 제한하는 커널 보안 메커니즘입니다. 기초적인 BPF 필터 예제는 시스템 콜Linux Containers 페이지를 참조하세요. 여기서는 커널 내부 구현, 필터 체인 메커니즘, 고급 기능을 심층적으로 다룹니다.

동작 모드

모드상수설명도입
DisabledSECCOMP_MODE_DISABLED (0)seccomp 비활성. 기본 상태-
StrictSECCOMP_MODE_STRICT (1)read, write, exit, sigreturn 4개만 허용. 그 외 즉시 SIGKILL2.6.12
FilterSECCOMP_MODE_FILTER (2)cBPF 프로그램으로 시스템 콜별 세밀한 정책 적용3.5
/* Strict 모드 활성화 */
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
/* 이후 read/write/exit/sigreturn 외 시스템 콜 → SIGKILL */

/* Filter 모드: seccomp() 시스템 콜 사용 (Linux 3.17+) */
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);  /* 필수: 권한 상승 방지 */
seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog);
/* 또는 prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog); */

PR_SET_NO_NEW_PRIVS가 설정되지 않은 경우, CAP_SYS_ADMIN capability가 있어야 필터를 설치할 수 있습니다. 이는 setuid 바이너리 실행 시 seccomp 필터를 우회하여 권한을 상승시키는 것을 방지합니다.

커널 내부 구조

/* include/linux/seccomp.h - task_struct에 포함 */
struct seccomp {
    int                    mode;    /* SECCOMP_MODE_DISABLED/STRICT/FILTER */
    atomic_t               filter_count;  /* 필터 체인 길이 */
    struct seccomp_filter  *filter; /* BPF 필터 체인 (linked list) */
};

/* kernel/seccomp.c - 필터 노드 */
struct seccomp_filter {
    refcount_t             refs;     /* 참조 카운트 (GC용) */
    refcount_t             users;    /* task 참조 수 (fork 시 증가) */
    bool                   log;      /* SECCOMP_FILTER_FLAG_LOG */
    bool                   wait_killable_recv; /* WAIT_KILLABLE_RECV 플래그 */
    struct seccomp_filter  *prev;    /* 이전 필터 (체인) */
    struct bpf_prog        *prog;    /* 컴파일된 BPF 프로그램 */
    struct notification    *notif;   /* USER_NOTIF 알림 큐 */
    struct mutex           notify_lock;
    u32                    cache[SECCOMP_ARCH_NATIVE_NR]; /* bitmap 캐시 */
};

/* include/uapi/linux/seccomp.h - BPF 필터에 전달되는 데이터 */
struct seccomp_data {
    int      nr;            /* 시스템 콜 번호 */
    __u32    arch;          /* AUDIT_ARCH_* 아키텍처 식별자 */
    __u64    instruction_pointer; /* 호출 시점 IP 레지스터 */
    __u64    args[6];      /* 시스템 콜 인자 (최대 6개) */
};

커널 실행 경로

x86_64에서 시스템 콜 진입 시 seccomp 필터는 다음 경로로 실행됩니다:

entry_SYSCALL_64 SYSCALL 진입 do_syscall_64() 시스템 콜 디스패치 __seccomp_filter() seccomp 검사 진입점 seccomp_run_filters() 모든 필터 순회 bpf_prog_run() cBPF 실행 (JIT) RET_ALLOW syscall 실행 RET_KILL SIGKILL/SIGSYS RET_ERRNO errno 반환 RET_TRACE ptrace 알림 RET_USER_NOTIF 감독자 위임 RET_LOG 로그 후 허용
/* kernel/seccomp.c - 핵심 필터 실행 로직 */
static u32 seccomp_run_filters(const struct seccomp_data *sd,
                                struct seccomp_filter **match)
{
    u32 ret = SECCOMP_RET_ALLOW;
    struct seccomp_filter *f;

    /* 필터 체인을 역순으로 순회 (가장 최근 필터부터) */
    for (f = READ_ONCE(current->seccomp.filter); f; f = f->prev) {
        u32 cur_ret = bpf_prog_run_pin_on_cpu(f->prog, sd);

        /* 가장 제한적인(높은 우선순위) 결과 선택 */
        if ((cur_ret & SECCOMP_RET_ACTION_FULL) < (ret & SECCOMP_RET_ACTION_FULL)) {
            ret = cur_ret;
            *match = f;
        }
    }
    return ret;
}

/* __seccomp_filter() - 최종 액션 처리 */
int __seccomp_filter(int this_syscall,
                     const struct seccomp_data *sd,
                     const bool recheck_after_trace)
{
    u32 action = seccomp_run_filters(sd, &match);
    switch (action & SECCOMP_RET_ACTION_FULL) {
    case SECCOMP_RET_KILL_PROCESS:  /* 프로세스 전체 SIGKILL */
    case SECCOMP_RET_KILL_THREAD:   /* 스레드만 SIGSYS */
    case SECCOMP_RET_TRAP:          /* SIGSYS + siginfo */
    case SECCOMP_RET_ERRNO:         /* errno 반환 (하위 16비트) */
    case SECCOMP_RET_USER_NOTIF:    /* 감독자 프로세스 알림 */
    case SECCOMP_RET_TRACE:         /* ptrace 트레이서 알림 */
    case SECCOMP_RET_LOG:           /* 로깅 후 허용 */
    case SECCOMP_RET_ALLOW:         /* 허용 */
    }
}

SECCOMP_RET_* 반환 액션 우선순위

필터 체인에서 여러 필터가 서로 다른 결과를 반환하면, 가장 제한적인(숫자가 작은) 액션이 최종 적용됩니다. 아래 목록은 우선순위 순서(높은 것부터)입니다:

우선순위반환 액션동작
1 (최고)SECCOMP_RET_KILL_PROCESS0x80000000프로세스 전체 즉시 종료 (SIGKILL). 코어 덤프 생성 가능
2SECCOMP_RET_KILL_THREAD0x00000000호출 스레드만 종료 (SIGSYS). 멀티스레드 앱에서 위험
3SECCOMP_RET_TRAP0x00030000SIGSYS 시그널 전송. siginfo_t에 syscall 정보 포함
4SECCOMP_RET_ERRNO0x00050000시스템 콜 실행하지 않고 하위 16비트를 errno로 반환
5SECCOMP_RET_USER_NOTIF0x7fc00000감독자 프로세스에 알림. 감독자가 결과 결정 (5.0+)
6SECCOMP_RET_TRACE0x7ff00000ptrace 트레이서에 알림. 트레이서가 시스템 콜 변경 가능
7SECCOMP_RET_LOG0x7ffc0000감사 로그에 기록 후 시스템 콜 허용 (4.14+)
8 (최저)SECCOMP_RET_ALLOW0x7fff0000시스템 콜 정상 실행
/* SECCOMP_RET_ERRNO 사용 예: mount()를 EPERM으로 거부 */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & SECCOMP_RET_DATA));
/* 하위 16비트(SECCOMP_RET_DATA = 0xffff)에 errno 값 인코딩 */

/* SECCOMP_RET_TRAP: 시그널 핸들러에서 정보 확인 */
void sigsys_handler(int sig, siginfo_t *info, void *ctx) {
    /* info->si_signo  = SIGSYS */
    /* info->si_code   = SYS_SECCOMP */
    /* info->si_call_addr = 호출 위치 */
    /* info->si_syscall   = 시스템 콜 번호 */
    /* info->si_arch      = AUDIT_ARCH_* */
    /* info->si_errno     = SECCOMP_RET_DATA (필터 반환 하위 16비트) */
}

필터 체인 메커니즘

seccomp 필터는 누적(additive)됩니다. 새로운 필터를 설치하면 기존 필터 앞에 추가되며, 모든 필터가 순회됩니다. 필터는 제거할 수 없으므로 점점 더 제한적으로만 변경됩니다.

task_struct .seccomp.filter → 필터 #3 (최신) .prev → 필터 #2 .prev → 필터 #1 (최초) .prev = NULL 부모 프로세스 필터 #1 → #2 자식 (fork) 필터 #1 → #2 (공유) 자식이 필터 #3 추가 #3 → #1 → #2 (부모 무영향) fork() 필터 체인은 refcount로 공유. 자식이 필터 추가 시 기존 체인 앞에 새 노드 연결. 부모 필터에는 영향 없음 (COW 유사).
/* 필터 체인 동작 원리 */

/* 1. 첫 번째 필터 설치: {read, write, exit_group} 허용 */
seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog_allow_rwe);
/* 체인: [필터#1] → NULL */

/* 2. 두 번째 필터 추가: write를 stdout(fd=1)으로만 제한 */
seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog_stdout_only);
/* 체인: [필터#2] → [필터#1] → NULL */

/* 3. 실행 시: 필터#2와 필터#1 모두 실행 */
/*    결과 = min(필터#2 결과, 필터#1 결과) */
/*    → 가장 제한적인 액션이 최종 적용 */

/* 4. fork() 시: 자식은 부모 필터 체인을 참조 공유 */
/*    자식이 새 필터를 설치하면 체인 앞에 추가 */
/*    부모 필터는 변경되지 않음 */

seccomp 플래그 상세

플래그커널설명
SECCOMP_FILTER_FLAG_TSYNC3.17프로세스의 모든 스레드에 필터를 원자적으로 적용. 다른 스레드가 이미 더 제한적인 필터를 가지면 실패
SECCOMP_FILTER_FLAG_LOG4.14필터에 의해 거부된 시스템 콜을 audit 로그에 기록
SECCOMP_FILTER_FLAG_SPEC_ALLOW4.17Spectre v2 완화(indirect branch restriction)를 비활성화. 필터가 적용된 프로세스의 성능 오버헤드 감소
SECCOMP_FILTER_FLAG_NEW_LISTENER5.0필터 설치 시 notification fd를 반환. SECCOMP_RET_USER_NOTIF와 함께 사용
SECCOMP_FILTER_FLAG_TSYNC_ESRCH5.7TSYNC 실패 시 ESRCH 대신 충돌 스레드의 TID 반환 (디버깅용)
SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV5.19USER_NOTIF 대기 중 치명적 시그널(SIGKILL)만 수신. SIGSTOP 등에 의한 중단 방지
/* TSync: 모든 스레드에 동시 적용 */
int ret = seccomp(SECCOMP_SET_MODE_FILTER,
                  SECCOMP_FILTER_FLAG_TSYNC |       /* 스레드 동기화 */
                  SECCOMP_FILTER_FLAG_LOG,           /* 거부 시 로깅 */
                  &prog);
/* ret > 0: TSYNC 실패, 반환값 = 충돌 스레드 TID (TSYNC_ESRCH) */
/* ret = 0: 성공 */

/* NEW_LISTENER: notification fd 획득 */
int listener_fd = seccomp(SECCOMP_SET_MODE_FILTER,
                          SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog);
/* listener_fd를 감독자 프로세스에 전달 (Unix domain socket 등) */

/* Spectre 완화 제어 */
seccomp(SECCOMP_SET_MODE_FILTER,
        SECCOMP_FILTER_FLAG_SPEC_ALLOW,  /* IBRS/STIBP 해제 허용 */
        &prog);

SECCOMP_RET_USER_NOTIF 심화 (사용자 공간 알림)

커널 5.0+에서 SECCOMP_RET_USER_NOTIF는 시스템 콜을 사용자 공간 감독자(supervisor)에게 위임합니다. 컨테이너 런타임(runc, containerd)에서 mount, mknod 등 특정 시스템 콜을 안전하게 에뮬레이션하는 데 사용됩니다.

대상 프로세스 mount() 호출 seccomp 필터 RET_USER_NOTIF 반환 알림 큐 notif fd 감독자 프로세스 NOTIF_RECV 감독자: 처리 NOTIF_SEND 응답 커널: 응답 처리 반환값/errno 설정 대상: 재개 mount() 결과 수신 프로세스 블록(sleep)
/* === 감독자(supervisor) 프로세스 구현 예제 === */

/* 1. 필터 설치 시 notification fd 획득 */
int notif_fd = seccomp(SECCOMP_SET_MODE_FILTER,
                       SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog);

/* 2. 알림 구조체 할당 */
struct seccomp_notif *req;
struct seccomp_notif_resp *resp;
seccomp_notify_alloc(&req, &resp);

/* 3. 알림 처리 루프 */
while (1) {
    /* 대상 프로세스의 시스템 콜 알림 수신 */
    ioctl(notif_fd, SECCOMP_IOCTL_NOTIF_RECV, req);

    /* req->id   = 고유 알림 ID */
    /* req->pid  = 호출 프로세스 PID */
    /* req->data = seccomp_data (nr, arch, args[6]) */

    /* TOCTOU 방지: 대상 프로세스가 아직 블록 상태인지 확인 */
    if (ioctl(notif_fd, SECCOMP_IOCTL_NOTIF_ID_VALID, &req->id) != 0) {
        continue;  /* 대상이 이미 종료/시그널됨 */
    }

    /* 감독자가 대신 처리하고 결과 반환 */
    resp->id    = req->id;
    resp->val   = 0;     /* 시스템 콜 반환값 */
    resp->error = 0;     /* errno (0 = 성공) */
    resp->flags = 0;     /* SECCOMP_USER_NOTIF_FLAG_CONTINUE (5.5+) */
    ioctl(notif_fd, SECCOMP_IOCTL_NOTIF_SEND, resp);
}

seccomp_notify_free(req, resp);

SECCOMP_IOCTL_NOTIF_ADDFD (5.9+)

감독자가 대상 프로세스의 fd 테이블에 파일 디스크립터를 주입할 수 있습니다. openat() 같은 시스템 콜을 에뮬레이션할 때, 감독자가 파일을 열고 그 fd를 대상 프로세스에 전달하는 데 사용됩니다.

/* 감독자가 대상 프로세스에 fd 주입 */
struct seccomp_notif_addfd addfd = {
    .id        = req->id,          /* 알림 ID */
    .flags     = SECCOMP_ADDFD_FLAG_SETFD, /* 특정 fd 번호 지정 */
    .srcfd     = supervisor_fd,    /* 감독자가 열은 fd */
    .newfd     = 3,                /* 대상 프로세스에서의 fd 번호 */
    .newfd_flags = O_CLOEXEC,      /* fd 플래그 */
};

int target_fd = ioctl(notif_fd, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd);
/* target_fd = 대상 프로세스에 할당된 실제 fd 번호 */

/* 이후 resp->val = target_fd로 응답하면 */
/* 대상 프로세스는 openat()의 결과로 이 fd를 받음 */
TOCTOU(Time-of-Check-to-Time-of-Use) 주의:

감독자가 /proc/[pid]/mem을 통해 대상 프로세스의 메모리를 읽을 때, 대상이 다른 스레드에서 해당 메모리를 변경할 수 있습니다. 반드시 SECCOMP_IOCTL_NOTIF_ID_VALID로 알림이 아직 유효한지 확인하고, /proc/[pid]/mem 읽기 후 다시 검증하세요.

SECCOMP_USER_NOTIF_FLAG_CONTINUE (5.5+)

감독자가 resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE을 설정하면, 커널이 원래의 시스템 콜을 정상적으로 실행합니다. 이를 통해 감독자는 시스템 콜을 모니터링하되, 선택적으로 허용/거부할 수 있습니다.

/* 감독자: mount()를 조건부 허용 */
if (is_safe_mount(req)) {
    resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE; /* 원래 syscall 실행 */
    resp->error = 0;
    resp->val   = 0;  /* CONTINUE 시 val/error 무시됨 */
} else {
    resp->flags = 0;
    resp->error = -EPERM;  /* 거부 */
    resp->val   = 0;
}
ioctl(notif_fd, SECCOMP_IOCTL_NOTIF_SEND, resp);

Bitmap 최적화와 성능

커널은 seccomp 성능을 위해 여러 최적화를 적용합니다:

최적화설명
Bitmap 캐시필터 체인 설치 시, 각 시스템 콜 번호에 대해 결과가 항상 ALLOW인지 사전 계산. ALLOW 확정인 syscall은 BPF 실행 생략
cBPF JITBPF 프로그램을 네이티브 머신 코드로 JIT 컴파일. net.core.bpf_jit_enable=1 시 활성화
Architecture bitmap아키텍처별 시스템 콜 번호 범위가 정해져 있어, 범위 밖 번호는 즉시 처리
/* kernel/seccomp.c - Bitmap 캐시 구현 */

/* 필터 설치 시 bitmap 사전 계산 */
static void seccomp_cache_prepare_bitmap(
    struct seccomp_filter *sfilter,
    void *bitmap, const void *bitmap_prev,
    int event_nr, int syscall_nr, struct seccomp_data *sd)
{
    /* 각 syscall 번호에 대해 BPF 프로그램 실행 */
    /* 결과가 항상 SECCOMP_RET_ALLOW이면 bitmap에 기록 */
    /* 런타임에 bitmap 검사 → BPF 실행 생략으로 빠른 경로 */
}

/* 런타임 빠른 경로 검사 */
static inline bool seccomp_cache_check_allow(
    const struct seccomp_filter *sfilter,
    const struct seccomp_data *sd)
{
    int nr = sd->nr;
    /* bitmap에서 해당 syscall이 ALLOW 확정인지 검사 */
    /* 확정이면 true 반환 → 필터 체인 순회 생략 */
}
💡

성능 팁: 필터 체인이 길어질수록 오버헤드가 증가합니다. 가능하면 하나의 BPF 프로그램에 모든 규칙을 포함하세요. Bitmap 최적화 덕분에 SECCOMP_RET_ALLOW를 반환하는 시스템 콜은 거의 오버헤드가 없습니다. 차단/제한하는 시스템 콜만 BPF 실행 비용이 발생합니다.

cBPF 프로그램 구조

seccomp 필터는 classic BPF(cBPF) 명령어를 사용합니다. eBPF가 아닌 cBPF를 사용하는 이유는 seccomp이 도입될 당시(3.5) eBPF가 존재하지 않았고, 보안상 cBPF의 제한된 기능이 검증에 유리하기 때문입니다.

/* cBPF 명령어 구조 */
struct sock_filter {
    __u16  code;   /* 명령어 코드 (LD/ST/ALU/JMP/RET) */
    __u8   jt;     /* true 점프 오프셋 */
    __u8   jf;     /* false 점프 오프셋 */
    __u32  k;      /* 상수값 */
};

/* seccomp 전용 제약사항 */
/* 1. BPF_LD_ABS만 허용 (포인터 역참조 불가) */
/*    → seccomp_data 구조체 필드만 읽기 가능 */
/* 2. 반환값은 반드시 SECCOMP_RET_* 상수 */
/* 3. 프로그램 크기 제한: BPF_MAXINSNS (4096 명령어) */
/* 4. 루프 불가 (forward jump만 허용) → 정지 보장 */

/* 실용적인 seccomp BPF 필터 예제 */
struct sock_filter filter[] = {
    /* 아키텍처 검사 (x86_64 전용) */
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
            offsetof(struct seccomp_data, arch)),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 1, 0),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),

    /* 시스템 콜 번호 로드 */
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
            offsetof(struct seccomp_data, nr)),

    /* mount(2) 차단 → EPERM */
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_mount, 0, 1),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM),

    /* umount2(2) 차단 → EPERM */
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_umount2, 0, 1),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM),

    /* kexec_load(2) 차단 → KILL */
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_kexec_load, 0, 1),
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),

    /* 나머지 허용 */
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};

struct sock_fprog prog = {
    .len    = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
    .filter = filter,
};

libseccomp 고수준 API

raw cBPF 대신 libseccomp 라이브러리를 사용하면 가독성 높은 필터를 작성할 수 있습니다. 아키텍처 차이도 자동 처리됩니다.

#include <seccomp.h>   /* libseccomp 헤더 */

int setup_seccomp(void) {
    /* 기본 액션: 허용 (화이트리스트 방식은 SCMP_ACT_KILL 사용) */
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);
    if (!ctx) return -1;

    /* 특정 시스템 콜 차단 (블랙리스트) */
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(mount), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(umount2), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL_PROCESS, SCMP_SYS(kexec_load), 0);
    seccomp_rule_add(ctx, SCMP_ACT_KILL_PROCESS, SCMP_SYS(init_module), 0);

    /* 인자 조건부 필터링: write()는 fd=1,2만 허용 */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
                     SCMP_A0(SCMP_CMP_EQ, STDOUT_FILENO));
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
                     SCMP_A0(SCMP_CMP_EQ, STDERR_FILENO));

    /* 필터 적용 */
    seccomp_load(ctx);
    seccomp_release(ctx);
    return 0;
}

디버깅 및 모니터링

# /proc/[pid]/status에서 seccomp 상태 확인
grep Seccomp /proc/self/status
# Seccomp:        0    (disabled)
# Seccomp:        1    (strict)
# Seccomp:        2    (filter)
# Seccomp_filters: 3   (필터 체인 길이, 5.8+)

# 로깅 가능한 액션 확인/설정
cat /proc/sys/kernel/seccomp/actions_logged
# kill_process kill_thread trap errno user_notif trace log
# (ALLOW는 기본 미포함 — 로깅하면 성능 심각 저하)

echo "kill_process kill_thread trap errno log" > \
    /proc/sys/kernel/seccomp/actions_logged

# audit 로그에서 seccomp 이벤트 검색
ausearch -m SECCOMP --start today
# type=SECCOMP msg=audit(1706000000.000:789):
#   auid=1000 uid=1000 pid=12345 subj=... comm="myapp"
#   exe="/usr/bin/myapp" sig=0 arch=c000003e
#   syscall=165 compat=0 ip=0x7f... code=0x50001
#   ↑ syscall=165 = mount, code=0x50001 = ERRNO|EPERM

# strace의 seccomp 지원 (5.3+, strace 5.3+)
strace --seccomp-bpf -e trace=open,read,write ./myapp
# --seccomp-bpf: strace가 seccomp 필터를 자동 설치하여
# 추적 대상 시스템 콜만 ptrace 중단 → 오버헤드 대폭 감소

# seccomp 필터를 BPF 디스어셈블리로 확인 (drgn 디버거)
# drgn -p [pid] -c 'task = find_task(pid); ...'
💡

SECCOMP_RET_LOG(4.14+)는 개발 단계에서 유용합니다. 시스템 콜을 허용하면서 감사 로그에 기록하므로, 프로덕션 환경에서 차단하기 전에 어떤 시스템 콜이 사용되는지 파악할 수 있습니다. Docker는 --security-opt seccomp=my-profile.json에서 "SCMP_ACT_LOG" 액션으로 이를 활용합니다.

8. Credentials & Capabilities

struct cred 상세

/* include/linux/cred.h */
struct cred {
    atomic_long_t  usage;
    kuid_t         uid;     /* real UID */
    kgid_t         gid;     /* real GID */
    kuid_t         suid;    /* saved set-UID */
    kgid_t         sgid;    /* saved set-GID */
    kuid_t         euid;    /* effective UID */
    kgid_t         egid;    /* effective GID */
    kuid_t         fsuid;   /* filesystem UID */
    kgid_t         fsgid;   /* filesystem GID */
    unsigned       securebits;
    kernel_cap_t   cap_inheritable; /* 상속 가능 */
    kernel_cap_t   cap_permitted;   /* 허용된 전체 */
    kernel_cap_t   cap_effective;   /* 현재 유효 */
    kernel_cap_t   cap_bset;        /* bounding set */
    kernel_cap_t   cap_ambient;     /* ambient (4.3+) */
    struct user_struct   *user;
    struct user_namespace *user_ns;
    struct ucounts        *ucounts;
    struct group_info    *group_info;
    void           *security;       /* LSM security blob */
};

Credential 생명주기

/* Credential은 COW(Copy-on-Write) 방식으로 관리 */

/* 1. 새 credential 준비 (현재 cred 복사) */
struct cred *new = prepare_creds();
if (!new)
    return -ENOMEM;

/* 2. 필드 수정 */
new->uid = make_kuid(new->user_ns, 1000);
new->cap_effective = cap_drop(new->cap_effective, CAP_SYS_ADMIN);

/* 3. 원자적 커밋 (이전 cred 해제) */
commit_creds(new);

/* 또는 변경 취소 */
abort_creds(new);

/* 현재 credential 읽기 (RCU 보호) */
const struct cred *cred = current_cred();
kuid_t uid = cred->uid;

Capability 시스템 심화

Linux capability는 전통적인 root 특권을 약 41개의 독립 비트로 분리합니다. 기초 개념은 Linux Containers 페이지를 참조하세요.

Capability설명
CAP_CHOWN파일 소유자 변경
CAP_DAC_OVERRIDE파일 DAC 권한 무시
CAP_DAC_READ_SEARCH파일 읽기/디렉터리 검색 DAC 무시
CAP_FOWNER소유자 아닌 파일에 대한 연산 허용
CAP_KILL다른 사용자 프로세스에 시그널 전송
CAP_NET_ADMIN네트워크 관리 (인터페이스, 라우팅, 방화벽 등)
CAP_NET_BIND_SERVICE1024 이하 포트 바인드
CAP_NET_RAWRAW/PACKET 소켓 사용
CAP_SYS_ADMIN시스템 관리 (mount, swapon, ioctl 등) — 가장 넓은 범위
CAP_SYS_PTRACE임의 프로세스 ptrace
CAP_SYS_RAWIOraw I/O (iopl, ioperm)
CAP_SYS_MODULE커널 모듈 로드/언로드
CAP_SYS_BOOTreboot() 시스템 콜
CAP_SYS_CHROOTchroot()
CAP_SYS_NICE프로세스 우선순위 변경 (nice, 스케줄러 정책)
CAP_SYS_RESOURCE리소스 제한 초과 (rlimit, 디스크 쿼터 등)
CAP_SYS_TIME시스템 시간 변경
CAP_SETUIDUID 변경
CAP_SETGIDGID 변경
CAP_SETPCAPcapability bounding set 변경
CAP_BPFBPF 프로그램 로드 (5.8+)
CAP_PERFMON성능 모니터링 (perf_event_open) (5.8+)
CAP_CHECKPOINT_RESTORECRIU 체크포인트/복원 (5.9+)

파일 Capability

# 파일에 capability 부여 (setuid 대체)
sudo setcap cap_net_bind_service=ep /usr/bin/myserver
# e = effective, p = permitted, i = inheritable

# 파일 capability 확인
getcap /usr/bin/myserver
# /usr/bin/myserver cap_net_bind_service=ep

# 프로세스의 현재 capability 확인
cat /proc/self/status | grep Cap
# CapInh: 0000000000000000  (inheritable)
# CapPrm: 0000000000000000  (permitted)
# CapEff: 0000000000000000  (effective)
# CapBnd: 000001ffffffffff  (bounding set)
# CapAmb: 0000000000000000  (ambient)

# capsh로 디코딩
capsh --decode=000001ffffffffff

Ambient Capabilities (4.3+)

/* Ambient capability는 execve() 시 보존되는 capability */
/* 파일 capability(xattr) 없이도 non-root 프로세스에 capability 전달 */

/* 조건: ambient cap은 permitted ∩ inheritable의 부분집합이어야 함 */
prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_NET_BIND_SERVICE, 0, 0);

/* no_new_privs와 함께 사용하여 권한 상승 방지 */
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

/* execve 후 capability 계산:
   new_permitted   = (file_permitted & cap_bset) | (file_inheritable & cap_inheritable) | cap_ambient
   new_effective   = file_effective ? new_permitted : cap_ambient
   new_inheritable = cap_inheritable
   new_ambient     = (no_new_privs || !file_caps) ? cap_ambient : 0
*/

9. 커널 하드닝

KASLR (Kernel Address Space Layout Randomization)

커널 코드, 모듈, 물리 메모리 매핑의 베이스 주소를 부팅 시 랜덤으로 배치하여, 메모리 레이아웃을 예측할 수 없게 합니다. 공격자가 커널 심볼의 정확한 주소를 알아야 하는 ROP/JOP 공격을 근본적으로 어렵게 만드는 1차 방어선입니다.

KASLR은 x86_64에서 커널 3.14, arm64에서 4.6부터 지원됩니다. CONFIG_RANDOMIZE_BASE=y로 활성화하며, 대부분의 배포판 커널에서 기본 활성화되어 있습니다.

KASLR의 랜덤화 대상 영역

영역CONFIG 옵션랜덤화 범위 (x86_64)설명
커널 텍스트CONFIG_RANDOMIZE_BASE1GB 범위 내vmlinux 로드 주소를 2MB 정렬로 랜덤 배치
모듈 영역CONFIG_RANDOMIZE_BASE1GB 범위 내모듈 로드 베이스 주소 랜덤화
물리 메모리 매핑CONFIG_RANDOMIZE_MEMORY1TB 범위 내direct map, vmalloc, vmemmap 영역 랜덤화
스택CONFIG_RANDOMIZE_KSTACK_OFFSET엔트리마다syscall 진입 시 스택 오프셋을 랜덤 조정

KASLR 부팅 시 랜덤화 과정

/* arch/x86/boot/compressed/kaslr.c */
/* 부트로더 → 압축 해제기(decompressor)에서 KASLR 처리 */

/* 1단계: 엔트로피 소스 수집 */
static unsigned long get_boot_seed(void)
{
    /* RDTSC (CPU 타임스탬프 카운터) */
    /* RDRAND/RDSEED (하드웨어 RNG, 지원 시) */
    /* i8254 타이머 카운터 */
    /* 부트로더가 전달한 시드 (EFI RNG Protocol 등) */
}

/* 2단계: 사용 가능한 물리 메모리 슬롯 탐색 */
/* e820 메모리 맵에서 커널 크기를 수용할 수 있는 영역 목록 생성 */
/* 이미 사용된 영역(initrd, 부트 파라미터 등)은 제외 */

/* 3단계: 랜덤 슬롯 선택 → 커널 재배치 */
static unsigned long find_random_virt_addr(
    unsigned long minimum,
    unsigned long image_size)
{
    /* slots = 사용 가능 슬롯 수 */
    /* random_addr = minimum + (kaslr_get_random_long() % slots) * alignment */
    /* x86_64: 2MB 정렬, arm64: 64KB 또는 2MB 정렬 */
}

FKASLR (Function-Granular KASLR)

기본 KASLR은 커널 전체를 하나의 블록으로 이동하므로, 하나의 주소만 유출되면 나머지 모든 심볼의 오프셋을 계산할 수 있습니다. FKASLR은 함수 단위로 재배치하여 이 한계를 극복합니다.

/* FKASLR: CONFIG_FG_KASLR (x86_64, 실험적) */
/* 컴파일 시 -ffunction-sections 플래그로 함수별 별도 섹션 생성 */
/* 부팅 시 각 함수 섹션을 독립적으로 랜덤 재배치 */

/* 장점: 단일 주소 유출로 다른 함수 위치 추론 불가 */
/* 단점: 부팅 시간 증가, 캐시 지역성 저하, 코드 크기 증가 */
/*        /proc/kallsyms 심볼 순서가 매 부팅마다 달라짐 */

KASLR 우회 공격과 대응

우회 기법원리대응 기술
커널 정보 누출초기화 안 된 메모리에서 커널 포인터 유출CONFIG_INIT_ON_ALLOC_DEFAULT_ON, HARDENED_USERCOPY
/proc/kallsyms심볼 주소 직접 읽기kptr_restrict=1/2로 비루트 사용자 차단
dmesg 주소 유출%pK 대신 %p로 포인터 출력dmesg_restrict=1, %pK 포맷 사용 강제
타이밍 사이드채널캐시/TLB 타이밍으로 주소 추론KPTI, 사이트 격리
Spectre 변종투기적 실행으로 커널 메모리 읽기Retpoline, IBRS, STIBP
하드웨어 DMAIOMMU 없이 DMA로 커널 메모리 접근IOMMU(Intel VT-d, AMD-Vi) 필수 활성화

KASLR 상태 확인 및 관련 sysctl

# KASLR 상태 확인
cat /proc/cmdline | grep -o 'nokaslr\|kaslr'
dmesg | grep "KASLR"
# KASLR enabled (커널 기본 활성화)

# 커널 포인터 보호 수준
cat /proc/sys/kernel/kptr_restrict
# 0: 모든 사용자에게 심볼 주소 노출 (위험)
# 1: 비루트 사용자에게 0x0000000000000000으로 마스킹
# 2: 루트 포함 모든 사용자에게 마스킹 (권장)

# dmesg 접근 제한
sysctl kernel.dmesg_restrict=1

# 커널 텍스트 오프셋 확인 (디버깅 용도, 루트 필요)
# /proc/kallsyms에서 _text 심볼 주소 확인
grep " _text$" /proc/kallsyms
# 매 부팅마다 다른 주소가 출력됨

# 스택 오프셋 랜덤화 확인
cat /proc/sys/kernel/randomize_kstack_offset
# 1 = 활성화 (syscall 진입 시 스택 오프셋 랜덤)
nokaslr 커널 파라미터:

디버깅 시 KASLR을 비활성화하면 커널 주소가 고정되어 심볼 디버깅이 쉬워집니다. 하지만 프로덕션 환경에서는 절대 사용하지 마십시오. QEMU/GDB 커널 디버깅 시에만 nokaslr을 사용하는 것이 일반적입니다.

KPTI (Kernel Page Table Isolation)

Meltdown (CVE-2017-5754) 취약점 완화 기술입니다. 사용자 공간에서 커널 페이지 테이블을 분리하여, 사용자 모드에서 커널 메모리를 투기적 실행으로 읽을 수 없게 합니다.

/* arch/x86/mm/pti.c */
/* 사용자 공간용과 커널 공간용 두 벌의 페이지 테이블 유지 */
/* - 사용자 PGD: 최소한의 커널 매핑 (entry/exit 코드만) */
/* - 커널 PGD: 전체 커널 메모리 매핑 */
/* syscall/인터럽트 진입 시 PGD 전환 → CR3 레지스터 교체 */

/* KPTI 성능 영향: 시스템 콜 집중 워크로드에서 1~5% 오버헤드 */
/* PCID (Process Context Identifier) 사용 시 TLB 플러시 감소 */
# KPTI 상태 확인
dmesg | grep "page tables isolation"
# Kernel/User page tables isolation: enabled

cat /sys/devices/system/cpu/vulnerabilities/meltdown
# Mitigation: PTI

SMEP/SMAP

기술설명방어 대상
SMEP (Supervisor Mode Execution Prevention)Ring 0에서 사용자 공간 코드 실행 차단ret2usr 공격
SMAP (Supervisor Mode Access Prevention)Ring 0에서 사용자 공간 메모리 접근 차단커널이 의도치 않게 사용자 데이터 읽기
/* SMAP 일시 비활성화 (사용자 데이터 복사 시) */
stac();  /* Set AC flag → SMAP 일시 해제 */
copy_from_user(kbuf, ubuf, len);
clac();  /* Clear AC flag → SMAP 재활성화 */

/* copy_from_user/copy_to_user 내부에서 자동 처리 */

Stack Protector

# 스택 버퍼 오버플로 감지
# CONFIG_STACKPROTECTOR=y       (기본 보호 - 취약한 함수만)
# CONFIG_STACKPROTECTOR_STRONG=y (강한 보호 - 배열/주소 사용 함수)

# 함수 프롤로그에 canary 값 삽입
# 함수 에필로그에서 canary 검증 → 변조 시 panic
# canary는 per-CPU 변수로 스레드마다 다름

CFI (Control Flow Integrity)

간접 호출(함수 포인터)의 목적지를 검증하여 ROP/JOP 공격을 차단합니다. 리눅스 커널은 수천 개의 함수 포인터(file_operations, vm_operations_struct 등)를 사용하므로, 함수 포인터 오염을 통한 제어 흐름 탈취는 커널 익스플로잇의 핵심 기법입니다. CFI는 이러한 공격을 런타임에 탐지하여 차단합니다.

Forward-edge vs Backward-edge CFI: Forward-edge CFI는 간접 호출(call *%rax)과 간접 점프의 목적지를 검증합니다 (JOP/COP 방어). Backward-edge CFI는 함수 리턴 주소를 보호합니다 (ROP 방어). 완전한 제어 흐름 보호를 위해 둘 다 필요합니다.

CFI 기술 비교

기술커널 버전아키텍처유형설명
Clang CFI (kCFI)6.1+x86_64, arm64Forward-edge (SW)간접 호출 시 함수 시그니처 해시 검증. CONFIG_CFI_CLANG
FineIBT6.2+x86_64 (Intel)Forward-edge (HW+SW)Intel CET IBT + kCFI 결합. 하드웨어 기반 CFI. CONFIG_X86_KERNEL_IBT
Shadow Call Stack5.8+ (arm64)arm64Backward-edge별도 스택에 리턴 주소 보관. CONFIG_SHADOW_CALL_STACK
Intel CET Shadow Stack6.6+x86_64 (Intel)Backward-edge (HW)하드웨어 Shadow Stack으로 리턴 주소 보호. CONFIG_X86_USER_SHADOW_STACK

kCFI (Kernel Control Flow Integrity) 동작 원리

kCFI는 Clang 컴파일러가 모든 간접 호출 대상 함수에 타입 해시를 프리픽스로 삽입하고, 호출 직전에 해시를 검증하는 소프트웨어 기반 CFI입니다.

/* kCFI 컴파일러 계측 예시 (개념적) */

/* 원본 코드 */
struct file_operations fops = {
    .read = my_read,
    .write = my_write,
};
filp->f_op->read(filp, buf, count, pos);

/* kCFI 계측 후 (어셈블리 수준에서 발생하는 동작) */
/*
 * 1. 컴파일러가 각 함수 앞에 4바이트 타입 해시 삽입:
 *    my_read:
 *      .long 0xDEAD1234   ← 함수 시그니처 해시 (ssize_t(*)(struct file*, char*, size_t, loff_t*))
 *      push  %rbp          ← 실제 함수 시작
 *      ...
 *
 * 2. 간접 호출 직전에 해시 검증 코드 삽입:
 *      mov   f_op(%rdi), %rax     ← 함수 포인터 로드
 *      movl  -4(%rax), %ecx       ← 대상 함수 앞의 해시 로드
 *      cmpl  $0xDEAD1234, %ecx    ← 기대 해시와 비교
 *      jne   __cfi_failure         ← 불일치 시 → BUG()/panic
 *      call  *%rax                 ← 일치 시 정상 호출
 */
kCFI vs 이전 Clang CFI:

커널 5.13에서 도입된 초기 Clang CFI는 간접 호출을 점프 테이블로 치환하는 방식이었으나, 크로스 모듈 호출 처리가 복잡하고 LTO(Link-Time Optimization)가 필수였습니다. 커널 6.1의 kCFI는 해시 기반 검증으로 전환하여 LTO 없이도 동작하고, 모듈과의 호환성이 크게 개선되었습니다.

FineIBT (Forward-Edge CFI with Intel IBT)

FineIBT는 Intel CET(Control-flow Enforcement Technology)의 IBT(Indirect Branch Tracking)와 kCFI를 결합한 하드웨어 가속 CFI입니다. IBT만 단독 사용 시 모든 ENDBR64 명령어가 유효한 분기 대상이 되어 보호가 약하지만, kCFI의 타입 해시 검증을 결합하면 강력한 forward-edge 보호가 가능합니다.

/* FineIBT 동작 메커니즘 (x86_64 어셈블리 수준) */
/*
 * [함수 프롤로그] (IBT + kCFI 결합)
 *   my_read:
 *     endbr64                    ← IBT: 유효한 간접 분기 대상 표시
 *     subl  $0xDEAD1234, %r10d   ← kCFI: 전달된 해시에서 기대값을 뺌
 *     jz    .Lok                  ← 0이면(일치) 정상 진행
 *     ud2                         ← 불일치 시 #UD 예외 → BUG()
 *   .Lok:
 *     push  %rbp
 *     ...
 *
 * [간접 호출 사이트]
 *     movl  $0xDEAD1234, %r10d   ← 기대 해시를 r10에 로드
 *     call  *%rax                 ← 간접 호출 (IBT가 endbr64 존재 확인)
 */

/* FineIBT 활성화 조건 */
# CONFIG_X86_KERNEL_IBT=y          ← IBT 활성화
# CONFIG_CFI_CLANG=y               ← kCFI 활성화
# CPU가 CET-IBT 지원 (Intel 12세대+, AMD Zen 4+)
# 커널이 Clang으로 빌드됨

FineIBT는 커널 부팅 시 CPU의 CET-IBT 지원 여부를 자동 감지합니다. 지원하는 CPU에서는 FineIBT(HW+SW), 미지원 CPU에서는 순수 kCFI(SW only)로 자동 폴백합니다. cfi= 커널 파라미터로 동작을 제어할 수 있습니다: cfi=kcfi (소프트웨어만), cfi=fineibt (하드웨어+소프트웨어), cfi=off (비활성화).

Shadow Call Stack (arm64)

Shadow Call Stack(SCS)은 리턴 주소를 일반 스택과 분리된 별도의 그림자 스택에 저장하여, 스택 버퍼 오버플로로 인한 리턴 주소 변조를 방지하는 backward-edge CFI 기법입니다.

/* Shadow Call Stack 동작 (arm64) */
/*
 * arm64에서 x18 레지스터를 Shadow Call Stack 포인터로 예약
 * (x18은 플랫폼 레지스터, 일반 코드에서 사용 금지)
 *
 * [함수 프롤로그]
 *   str  x30, [x18], #8     ← 리턴 주소(LR)를 SCS에 push
 *   stp  x29, x30, [sp, #-16]!  ← 일반 스택에도 저장 (프레임 포인터)
 *
 * [함수 에필로그]
 *   ldr  x30, [x18, #-8]!   ← SCS에서 리턴 주소 복원
 *   ldp  x29, x30, [sp], #16
 *   ret                      ← SCS의 주소로 리턴
 *
 * 공격자가 일반 스택의 리턴 주소를 변조해도,
 * 실제 리턴은 SCS의 원본 주소를 사용하므로 ROP 공격 실패
 */

/* SCS 메모리 할당 (per-task) */
/* arch/arm64/kernel/scs.c */
#define SCS_SIZE       (1 << 10)  /* 1KB (128개 리턴 주소) */
#define SCS_GFP        (GFP_KERNEL | __GFP_ZERO)

/* 태스크 생성 시 SCS 할당, 종료 시 해제 */
/* vmalloc 영역에 할당하여 guard page로 오버플로 감지 */

CFI 위반 처리와 디버깅

# CFI 위반 시 커널 로그 예시
# CFI failure at some_function+0x42/0x100 (target: 0xffffffff81234567)
# kernel BUG at arch/x86/kernel/cfi.c:NN!

# CFI 모드 확인
dmesg | grep -i "cfi\|fineibt\|IBT"
# x86/cfi: Switching to FineIBT CFI
# 또는: x86/cfi: Using kCFI

# 모듈에서 CFI 지원 확인
modinfo some_module | grep cfi
# CFI가 활성화된 커널에서 비-CFI 모듈 로드 시
# 간접 호출이 검증 실패할 수 있음

# CONFIG_CFI_PERMISSIVE=y 사용 시 위반을 경고만 출력 (디버깅용)
# 프로덕션에서는 반드시 permissive 비활성화
CFI와 외부 모듈 호환성:

kCFI가 활성화된 커널에서 서드파티/out-of-tree 모듈을 사용하려면 해당 모듈도 동일한 Clang 버전과 CFI 옵션으로 빌드해야 합니다. GCC로 빌드된 모듈은 kCFI 해시가 없으므로 간접 호출 시 CFI 위반이 발생합니다. DKMS 모듈이나 NVIDIA 드라이버 등은 CFI 환경에서 호환성 문제가 있을 수 있습니다.

FORTIFY_SOURCE

/* CONFIG_FORTIFY_SOURCE=y */
/* 컴파일 타임 + 런타임에 버퍼 오버플로 감지 */
/* memcpy, strcpy, sprintf 등의 안전한 래퍼 제공 */

/* 예: memcpy 크기가 대상 버퍼를 초과하면 */
/*     컴파일 타임: 경고/에러 */
/*     런타임: fortify_panic() 호출 → BUG() */

하드닝 CONFIG 옵션 요약

CONFIG 옵션기능오버헤드
CONFIG_RANDOMIZE_BASEKASLR무시 가능
CONFIG_MITIGATION_PAGE_TABLE_ISOLATIONKPTI (Meltdown)1~5%
CONFIG_MITIGATION_RETPOLINESpectre v2 완화~2%
CONFIG_STACKPROTECTOR_STRONG스택 canary<1%
CONFIG_CFI_CLANG간접 호출 CFI~1%
CONFIG_X86_KERNEL_IBTFineIBT (CET)<1%
CONFIG_SHADOW_CALL_STACK리턴 주소 보호 (arm64)<1%
CONFIG_FORTIFY_SOURCE버퍼 오버플로 감지무시 가능
CONFIG_HARDENED_USERCOPY사용자-커널 복사 검증<1%
CONFIG_INIT_ON_ALLOC_DEFAULT_ON할당 시 0 초기화1~3%
CONFIG_INIT_ON_FREE_DEFAULT_ON해제 시 0 초기화1~5%
CONFIG_SLAB_FREELIST_RANDOMSlab freelist 랜덤화무시 가능
CONFIG_SLAB_FREELIST_HARDENEDSlab freelist 포인터 보호무시 가능
CONFIG_STRICT_KERNEL_RWX커널 코드 W^X 강제무시 가능
CONFIG_STRICT_MODULE_RWX모듈 코드 W^X 강제무시 가능

10. Audit 서브시스템

Linux Audit 서브시스템은 커널 수준에서 보안 관련 이벤트를 기록하는 프레임워크입니다. 시스템 콜, 파일 접근, 네트워크 연결, 사용자 인증 등 거의 모든 커널 활동을 감사할 수 있으며, PCI-DSS, HIPAA, SOX, CAPP(Controlled Access Protection Profile) 등 보안 컴플라이언스 인증의 필수 요구사항을 충족합니다.

Audit 서브시스템은 CONFIG_AUDIT (기본 프레임워크)와 CONFIG_AUDITSYSCALL (시스템 콜 감사)로 활성화됩니다. 대부분의 배포판 커널에서 기본 활성화되어 있습니다. 부팅 시 audit=1 커널 파라미터로 조기 부팅 단계부터 감사를 시작할 수 있습니다.

아키텍처

Audit 서브시스템은 커널 공간의 감사 후크(hook)와 사용자 공간의 auditd 데몬으로 구성됩니다. 커널이 이벤트를 수집하고, NETLINK_AUDIT 소켓을 통해 사용자 공간으로 전달합니다.

커널 공간 사용자 공간 시스템 콜 진입 audit_syscall_entry() audit_context 이벤트 데이터 수집 시스템 콜 종료 audit_syscall_exit() audit_buffer 메시지 직렬화 필터 엔진 규칙 매칭 (5개 리스트) kauditd 커널 스레드 audit_skb_queue netlink 전송 큐 NETLINK_AUDIT AF_NETLINK 소켓 auditd 감사 데몬 audisp 플러그인 이벤트 디스패처 /var/log/audit/ ausearch aureport

audit_context 생명주기

모든 감사 가능한 태스크는 task_struct->audit_context를 통해 audit_context 구조체를 갖습니다. 이 구조체는 시스템 콜 진입/종료 시 이벤트 데이터를 수집하고, 규칙 매칭 결과에 따라 로그 생성 여부를 결정합니다.

/* kernel/audit.h — audit_context 핵심 필드 */
struct audit_context {
    int                    in_syscall;     /* 시스템 콜 실행 중 여부 */
    enum audit_state       state;          /* AUDIT_STATE_{DISABLED,BUILD,RECORD} */
    enum audit_state       current_state;  /* 현재 감사 상태 */
    unsigned int           serial;         /* 이벤트 시리얼 번호 */
    int                    major;          /* 시스템 콜 번호 (NR) */
    int                    uring_op;       /* io_uring 연산 코드 */
    unsigned long          argv[4];        /* 시스템 콜 인자 (a0~a3) */
    long                   return_code;    /* 시스템 콜 반환값 */
    u64                    prio;           /* 우선순위 (에러 시 증가) */
    int                    return_valid;   /* 반환값 유효 여부 */

    /* 이름/경로 정보 */
    struct audit_names     *names;         /* 파일 이름 연결 리스트 */
    int                    name_count;     /* 이름 항목 수 */
    struct path            pwd;            /* 현재 작업 디렉토리 */

    /* IPC/소켓 감사 정보 */
    struct audit_aux_data  *aux;           /* 보조 감사 데이터 체인 */
    struct audit_aux_data  *aux_pids;      /* PID 추적 보조 데이터 */
    struct sockaddr_storage *sockaddr;     /* 소켓 주소 */
    size_t                 sockaddr_len;

    /* 필터링 */
    pid_t                  target_pid;     /* 대상 프로세스 PID */
    kuid_t                 target_auid;    /* 대상 audit UID */
    kuid_t                 target_uid;     /* 대상 실제 UID */

    /* execve 인자 (EXECVE 레코드용) */
    struct audit_tree_refs *trees;         /* 감시 트리 참조 */
    struct audit_proctitle  proctitle;      /* PROCTITLE 레코드 데이터 */
    /* ... */
};
/* kernel/auditsc.c — 시스템 콜 감사 흐름 */

/* 1. 시스템 콜 진입 시 — entry 후크 */
void __audit_syscall_entry(int major, unsigned long a1,
                           unsigned long a2, unsigned long a3,
                           unsigned long a4)
{
    struct audit_context *context = audit_context();
    if (!context)
        return;

    context->serial = 0;          /* 새 이벤트마다 리셋 */
    context->in_syscall = 1;       /* 시스템 콜 내부 마크 */
    context->current_state = state;
    context->major = major;        /* syscall NR 저장 */
    context->argv[0] = a1;         /* 인자 저장 */
    context->argv[1] = a2;
    context->argv[2] = a3;
    context->argv[3] = a4;
}

/* 2. 시스템 콜 종료 시 — exit 후크 */
void __audit_syscall_exit(int success, long return_code)
{
    struct audit_context *context = audit_context();

    context->return_valid = AUDITSC_SUCCESS;
    context->return_code  = return_code;

    /* exit 필터 리스트에서 규칙 매칭 */
    if (context->current_state == AUDIT_STATE_BUILD)
        state = audit_filter_syscall(context);

    /* RECORD 상태면 감사 레코드 생성 및 전송 */
    if (context->current_state >= AUDIT_STATE_RECORD)
        audit_log_exit();        /* → audit_log_start() + audit_log_format() */

    context->in_syscall = 0;       /* 시스템 콜 완료 */
    audit_free_names(context);     /* 이름 목록 해제 */
    audit_free_aux(context);       /* 보조 데이터 해제 */
}
audit_state 상태 머신:

AUDIT_STATE_DISABLED(감사 안 함) → AUDIT_STATE_BUILD(데이터 수집 중, 아직 로그 기록 미확정) → AUDIT_STATE_RECORD(규칙 매칭 성공, 로그 기록 확정). entry 필터에서 BUILD로 전환하고, exit 필터에서 최종 RECORD 여부를 결정합니다. 이 2단계 평가 방식으로 시스템 콜 반환값 기반 필터링이 가능합니다.

커널 감사 후크

Audit 서브시스템은 커널 전반에 후크를 삽입하여 보안 관련 이벤트를 포착합니다. 주요 후크 지점:

후크 함수호출 위치수집 정보
__audit_syscall_entry()시스템 콜 진입점syscall NR, 인자 4개
__audit_syscall_exit()시스템 콜 반환점반환값, 성공/실패, 규칙 매칭 → 레코드 생성
__audit_inode()VFS 경로 해석inode, 디바이스, 파일 모드, UID/GID
__audit_inode_child()디렉토리 내 파일 생성/삭제부모 inode, 자식 이름
audit_log_task_info()레코드 기록 시PID, UID, GID, comm, exe, sessionid
__audit_mq_open()POSIX 메시지 큐 열기큐 이름, 모드, 속성
__audit_socket_*소켓 연산소켓 주소, bind/connect/accept 정보
__audit_fd_pair()pipe/socketpair파일 디스크립터 쌍
__audit_ptrace()ptrace 호출대상 PID, 작업 코드
__audit_log_kern_module()모듈 로드모듈 이름
__audit_fanotify()fanotify 응답응답 유형, 대상 파일
__audit_tk_injoffset()시간 조정timekeeping 오프셋 변경
/* fs/namei.c — VFS 경로 해석 중 감사 후크 호출 예 */
static struct dentry *__lookup_hash(
    const struct qstr *name,
    struct dentry *base, unsigned int flags)
{
    struct dentry *dentry = lookup_dcache(name, base, flags);
    if (dentry)
        return dentry;

    dentry = __lookup_slow(name, base, flags);

    /* 감사 후크: 경로 해석 결과를 audit_context에 기록 */
    if (!IS_ERR(dentry) && unlikely(audit_context()))
        __audit_inode(name, dentry, flags);

    return dentry;
}

감사 메시지 형식

감사 레코드는 type=TYPE msg=audit(EPOCH:SERIAL): FIELDS 형식을 따릅니다. 동일 이벤트의 여러 레코드는 같은 EPOCH:SERIAL 쌍으로 연결됩니다.

# 하나의 이벤트는 여러 레코드 타입으로 구성됨 (같은 serial 공유)

# SYSCALL — 시스템 콜 기본 정보
type=SYSCALL msg=audit(1706000000.123:789): arch=c000003e syscall=257
  success=yes exit=3 a0=ffffff9c a1=7ffd1234 a2=0 a3=0
  items=1 ppid=4521 pid=4523 auid=1000 uid=0 gid=0 euid=0
  suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0
  ses=3 comm="cat" exe="/usr/bin/cat"
  subj=unconfined_u:unconfined_r:unconfined_t:s0
  key="sensitive_files"

# CWD — 현재 작업 디렉토리
type=CWD msg=audit(1706000000.123:789): cwd="/home/user"

# PATH — 접근한 파일 경로 (item 번호로 순서 지정)
type=PATH msg=audit(1706000000.123:789): item=0
  name="/etc/shadow" inode=131074 dev=08:01 mode=0100640
  ouid=0 ogid=42 rdev=00:00 nametype=NORMAL
  cap_fp=0 cap_fi=0 cap_fe=0 cap_fver=0

# PROCTITLE — 프로세스 제목 (hex 인코딩)
type=PROCTITLE msg=audit(1706000000.123:789):
  proctitle=636174002F6574632F736861646F77

주요 레코드 타입

타입번호설명주요 필드
SYSCALL1300시스템 콜 이벤트arch, syscall, success, exit, auid, uid, exe, key
PATH1302파일 경로 정보name, inode, dev, mode, ouid, ogid, nametype
CWD1307현재 작업 디렉토리cwd
EXECVE1309exec 인자argc, a0, a1, a2...
PROCTITLE1327프로세스 커맨드라인proctitle (hex 인코딩)
SOCKADDR1306소켓 주소saddr (hex 인코딩 sockaddr)
USER_AUTH1100사용자 인증pid, uid, auid, msg (PAM 정보)
USER_LOGIN1112사용자 로그인pid, uid, auid, ses, msg
USER_CMD1123sudo 명령 실행pid, uid, auid, cmd
AVC1400SELinux 접근 제어pid, comm, scontext, tcontext, tclass, perm
NETFILTER_PKT1325netfilter 패킷 감사mark, saddr, daddr, sport, dport, proto
KERN_MODULE1326커널 모듈 로드name
ANOM_ABEND1701비정상 프로세스 종료auid, uid, gid, ses, pid, comm, sig

auid (Audit UID)는 사용자가 최초 로그인할 때 설정되며, susudo로 UID가 변경되어도 원래 로그인 사용자를 추적할 수 있습니다. loginuid가 한 번 설정되면 변경 불가하도록 CONFIG_AUDIT_LOGINUID_IMMUTABLE을 설정할 수 있습니다. auid가 4294967295(-1)이면 로그인 과정을 거치지 않은 데몬 프로세스입니다.

kauditd 커널 스레드

kauditd는 커널 감사 메시지를 사용자 공간 auditd로 전달하는 커널 스레드입니다. 3개의 skb 큐를 관리하며, auditd 연결 상태에 따라 메시지 처리 전략을 결정합니다.

/* kernel/audit.c — kauditd 큐 관리 */
static struct sk_buff_head audit_queue;       /* 기본 전송 큐 */
static struct sk_buff_head audit_retry_queue; /* 전송 실패 재시도 큐 */
static struct sk_buff_head audit_hold_queue;  /* auditd 부재 시 보관 큐 */

static int kauditd_thread(void *dummy)
{
    while (!kthread_should_stop()) {
        /* 1. retry 큐: 이전 전송 실패 메시지 재전송 시도 */
        kauditd_send_queue(sk, &audit_retry_queue,
                           UNICAST_RETRIES, kauditd_rehold_skb);

        /* 2. hold 큐: auditd 복귀 시 보관 메시지 전송 */
        kauditd_send_queue(sk, &audit_hold_queue,
                           UNICAST_RETRIES, kauditd_hold_skb);

        /* 3. main 큐: 새 감사 메시지 전송 */
        kauditd_send_queue(sk, &audit_queue,
                           1, kauditd_rehold_skb);

        /* 메시지 없으면 대기 (wake_up으로 깨움) */
        wait_event_freezable(kauditd_wait,
                             skb_queue_len(&audit_queue));
    }
    return 0;
}

필터 엔진

감사 규칙은 5개의 필터 리스트에 배치되며, 이벤트 발생 시 해당 리스트의 규칙을 순차 매칭합니다. 첫 매칭 규칙의 action(always/never)이 적용됩니다.

필터 리스트평가 시점사용 가능 필드
taskfork/clone 시 (자식에 적용)uid, gid, pid, loginuid, sessionid
exit시스템 콜 종료 시 (가장 많이 사용)모든 필드 (syscall NR, 반환값, 경로, uid 등)
user사용자 공간 메시지 도착 시uid, pid, loginuid, msgtype
exclude레코드 전송 직전msgtype (특정 메시지 타입 제외)
filesystem파일시스템 감시 규칙fstype
/* kernel/auditfilter.c — 필터 규칙 구조체 */
struct audit_krule {
    u32                    pflags;      /* 규칙 플래그 */
    u32                    flags;       /* AUDIT_FILTER_{TASK,EXIT,...} */
    u32                    listnr;      /* 필터 리스트 번호 */
    u32                    action;      /* AUDIT_ALWAYS 또는 AUDIT_NEVER */
    u32                    mask[AUDIT_BITMASK_SIZE]; /* syscall 비트마스크 */
    u32                    buflen;
    u32                    field_count; /* 조건 필드 수 */
    struct audit_field     *fields;     /* 필드 조건 배열 */
    struct audit_watch     *watch;      /* 파일 감시 (-w 규칙) */
    struct audit_tree      *tree;       /* 디렉토리 트리 감시 */
    struct audit_fsnotify_mark *exe;   /* 실행 파일 감시 */
    char                   *filterkey;  /* -k 키 문자열 */
    struct list_head       list;        /* 리스트 연결 */
    struct list_head       rlist;       /* 규칙 인덱스 */
};

struct audit_field {
    u32   type;    /* AUDIT_PID, AUDIT_UID, AUDIT_ARCH, ... */
    u32   val;     /* 비교 값 */
    u32   op;      /* AUDIT_EQUAL, AUDIT_NOT_EQUAL, ... */
    union {
        kuid_t uid;
        kgid_t gid;
        struct {
            char   *lsm_str;
            void   *lsm_rule;  /* LSM 규칙 (SELinux 컨텍스트 등) */
        };
    };
};

감사 규칙 유형

제어 규칙 (Control Rules)

감사 서브시스템 자체의 동작을 제어합니다.

# 감사 활성화/비활성화
auditctl -e 0     # 비활성화
auditctl -e 1     # 활성화
auditctl -e 2     # 잠금 (재부팅 전까지 규칙 변경 불가)

# 백로그 제한 (기본 64, 프로덕션에서 증가 필요)
auditctl -b 8192  # 백로그 버퍼 크기

# 실패 모드 (백로그 초과 시 동작)
auditctl -f 0     # silent — 초과분 버림
auditctl -f 1     # printk — 경고 메시지 출력 (기본값)
auditctl -f 2     # panic — 커널 패닉 (고보안 환경)

# 초당 최대 메시지 수 제한
auditctl -r 100   # 초당 100개 (0 = 무제한)

# 모든 규칙 삭제
auditctl -D

# 현재 상태 확인
auditctl -s
# enabled 1
# failure 1
# pid 1234     (auditd PID)
# backlog_limit 8192
# lost 0       (유실된 메시지 수)
# backlog 0    (현재 큐 대기 수)
-e 2 (잠금 모드):

프로덕션 환경에서는 규칙 설정 완료 후 -e 2로 잠금하여 공격자가 감사 규칙을 비활성화하지 못하도록 방지합니다. 잠금은 재부팅으로만 해제됩니다. 규칙 파일의 마지막 줄에 -e 2를 배치하세요.

파일시스템 감시 규칙 (Watch Rules)

# -w (감시 대상) -p (권한 필터) -k (검색 키)
# 권한: r=읽기, w=쓰기, x=실행, a=속성변경

# 인증 관련 파일 감시
auditctl -w /etc/passwd -p wa -k auth_files
auditctl -w /etc/shadow -p wa -k auth_files
auditctl -w /etc/group -p wa -k auth_files
auditctl -w /etc/gshadow -p wa -k auth_files
auditctl -w /etc/sudoers -p wa -k auth_files
auditctl -w /etc/sudoers.d/ -p wa -k auth_files

# PAM 설정 감시
auditctl -w /etc/pam.d/ -p wa -k pam_config

# SSH 설정 감시
auditctl -w /etc/ssh/sshd_config -p wa -k sshd_config
auditctl -w /root/.ssh/ -p wa -k root_ssh

# 시간 변경 감시 (규정 준수 필수)
auditctl -w /etc/localtime -p wa -k time_change

# 커널 모듈 관련
auditctl -w /sbin/insmod -p x -k module_load
auditctl -w /sbin/modprobe -p x -k module_load
auditctl -w /etc/modprobe.d/ -p wa -k module_config

# cron 설정 감시
auditctl -w /etc/cron.d/ -p wa -k cron_config
auditctl -w /etc/crontab -p wa -k cron_config
auditctl -w /var/spool/cron/ -p wa -k cron_config
감시 규칙 내부 구현:

-w 규칙은 내부적으로 audit_watch 구조체로 변환되어 fsnotify 프레임워크와 연동됩니다. 감시 대상 inode에 대한 FS_MODIFY, FS_ATTRIB 등의 이벤트를 수신하여 감사 레코드를 생성합니다. 디렉토리 감시(-w /etc/sudoers.d/)는 audit_tree를 사용하여 하위 파일까지 재귀적으로 추적합니다.

시스템 콜 규칙 (Syscall Rules)

# 형식: -a action,filter -F field=value -S syscall -k key
# action: always (기록) / never (무시)
# filter: task, exit, user, exclude

# 프로세스 실행 추적 (모든 execve)
auditctl -a always,exit -F arch=b64 -S execve -k exec_log

# 접근 거부 감사 (EACCES, EPERM)
auditctl -a always,exit -F arch=b64 -S open -S openat -S openat2 \
  -F exit=-EACCES -k access_denied
auditctl -a always,exit -F arch=b64 -S open -S openat -S openat2 \
  -F exit=-EPERM -k access_denied

# 파일 삭제 추적
auditctl -a always,exit -F arch=b64 -S unlink -S unlinkat -S rename \
  -S renameat -k file_delete

# 시간 변경 시스템 콜 감사
auditctl -a always,exit -F arch=b64 -S adjtimex -S settimeofday \
  -S clock_settime -k time_change

# 네트워크 연결 감사 (connect만)
auditctl -a always,exit -F arch=b64 -S connect -F a2!=110 -k network_connect

# 특정 UID 범위 감사 (서비스 계정 제외)
auditctl -a always,exit -F arch=b64 -S execve -F auid>=1000 \
  -F auid!=4294967295 -k user_exec

# 커널 모듈 로드/언로드
auditctl -a always,exit -F arch=b64 -S init_module -S finit_module \
  -S delete_module -k kernel_modules

# mount/umount 감사
auditctl -a always,exit -F arch=b64 -S mount -S umount2 -k mount_ops

# ptrace 감사 (디버거 연결 탐지)
auditctl -a always,exit -F arch=b64 -S ptrace -k ptrace_attach

# 네트워크 설정 변경 (sethostname, setdomainname)
auditctl -a always,exit -F arch=b64 -S sethostname -S setdomainname -k hostname

# 32비트 시스템 콜도 함께 감사 (호환성 계층)
auditctl -a always,exit -F arch=b32 -S execve -k exec_log_32bit

제외 규칙 (Exclude Rules)

# 노이즈가 많은 메시지 타입 제외
auditctl -a always,exclude -F msgtype=CWD          # CWD 레코드 제외
auditctl -a always,exclude -F msgtype=EOE          # 이벤트 종료 마커 제외
auditctl -a always,exclude -F msgtype=CRYPTO_KEY_USER # SSH 키 이벤트 제외

# 특정 프로그램의 감사 제외 (성능 최적화)
auditctl -a never,exit -F arch=b64 -S all -F exe=/usr/sbin/chronyd
auditctl -a never,exit -F arch=b64 -S all -F exe=/usr/bin/vmtoolsd

감사 도구

auditctl — 규칙 관리

# 현재 규칙 목록
auditctl -l

# 현재 상태
auditctl -s

# 규칙 삭제 (개별)
auditctl -d always,exit -F arch=b64 -S execve -k exec_log

# 모든 규칙 삭제
auditctl -D

# 규칙 파일에서 로드
auditctl -R /etc/audit/rules.d/custom.rules

ausearch — 로그 검색

# 키로 검색
ausearch -k passwd_changes --start today

# 메시지 타입으로 검색
ausearch -m SYSCALL -sc execve --start recent
ausearch -m USER_AUTH -sv no   # 인증 실패만

# auid (원래 로그인 사용자)로 검색
ausearch -ua 1000 --start today

# 특정 PID로 검색
ausearch -p 4523

# 시간 범위 검색
ausearch --start 01/15/2024 12:00:00 --end 01/15/2024 18:00:00

# 실행 파일로 검색
ausearch -x /usr/bin/passwd

# 파일 이름으로 검색
ausearch -f /etc/shadow

# 해석된 형식 출력 (-i: UID→이름, syscall→이름 변환)
ausearch -k exec_log -i --start today

# 이벤트 단위 구분자와 함께 출력
ausearch -k exec_log --format text

# CSV 형식 출력 (외부 도구 연동)
ausearch -k exec_log --format csv

# 특정 이벤트 ID로 검색
ausearch -a 789  # serial 789인 모든 레코드

# 여러 조건 AND 결합
ausearch -m SYSCALL -sc openat -k sensitive_files -sv no --start today

aureport — 보고서 생성

# 종합 요약 보고서
aureport --summary

# 인증 이벤트 보고서
aureport --auth --start this-week

# 파일 접근 보고서
aureport --file --summary

# 시스템 콜 통계
aureport --syscall --summary

# 이상 이벤트
aureport --anomaly

# 실패한 이벤트만
aureport --failed

# 실행 보고서
aureport -x --summary

# 키별 이벤트 수
aureport -k --summary

# 로그인 보고서
aureport --login --summary

# 사용자별 이벤트 수
aureport --user --summary

# 터미널별 이벤트
aureport --tty

autrace — 프로세스 추적

# autrace: strace와 유사하지만 audit 프레임워크 사용
# 모든 감사 규칙을 일시 삭제 후 대상 프로세스만 추적

autrace /bin/ls /tmp

# 추적 결과 분석
ausearch --start recent -p 12345 --raw | aureport --file --summary
ausearch --start recent -p 12345 --raw | aureport --syscall

auditd 데몬 설정

# /etc/audit/auditd.conf — 핵심 설정 항목

# 로그 파일 위치
log_file = /var/log/audit/audit.log

# 로그 형식 (RAW: 원시 형식, ENRICHED: UID→이름 등 해석 포함)
log_format = ENRICHED

# 로그 그룹 (adm 그룹에 읽기 권한 부여)
log_group = adm

# 로그 파일당 최대 크기 (MB)
max_log_file = 50

# 최대 로그 파일 수 (순환)
num_logs = 10

# 최대 크기 도달 시 동작
max_log_file_action = ROTATE     # ROTATE, SYSLOG, SUSPEND, IGNORE, KEEP_LOGS

# 디스크 공간 부족 시 동작
space_left = 75                  # 잔여 공간 경고 임계값 (MB)
space_left_action = SYSLOG       # SYSLOG, EMAIL, EXEC, SUSPEND, SINGLE, HALT
admin_space_left = 50            # 관리자 경고 임계값 (MB)
admin_space_left_action = SUSPEND
disk_full_action = SUSPEND       # 디스크 꽉 찬 경우
disk_error_action = SUSPEND      # 디스크 오류 시

# 네트워크 전달 (원격 감사 서버)
# name_format = HOSTNAME
# name = myserver.example.com

# 디스패처 (audisp 플러그인)
dispatcher = /sbin/audispd

# 플러시 방식
flush = INCREMENTAL_ASYNC        # 성능과 안정성 균형
freq = 50                        # 50개 레코드마다 플러시

# TCP 리스너 (원격 수집 서버로 사용 시)
# tcp_listen_port = 60
# tcp_max_per_addr = 1

영구 규칙 설정

auditctl로 설정한 규칙은 재부팅 시 사라집니다. 영구 규칙은 /etc/audit/rules.d/ 디렉토리에 .rules 파일로 저장합니다.

# /etc/audit/rules.d/audit.rules — 영구 규칙 파일 구조

# === 1단계: 기존 규칙 초기화 ===
-D
-b 8192
-f 1
--backlog_wait_time 60000

# === 2단계: 파일 감시 규칙 ===
-w /etc/passwd -p wa -k auth_files
-w /etc/shadow -p wa -k auth_files
-w /etc/group -p wa -k auth_files
-w /etc/sudoers -p wa -k auth_files
-w /etc/sudoers.d/ -p wa -k auth_files
-w /etc/ssh/sshd_config -p wa -k sshd_config

# === 3단계: 시스템 콜 규칙 ===
-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=4294967295 -k user_exec
-a always,exit -F arch=b32 -S execve -F auid>=1000 -F auid!=4294967295 -k user_exec
-a always,exit -F arch=b64 -S init_module -S finit_module -S delete_module -k kernel_modules
-a always,exit -F arch=b64 -S mount -S umount2 -k mount_ops
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -S clock_settime -k time_change

# === 4단계: 고성능 제외 규칙 ===
-a never,exit -F arch=b64 -S all -F exe=/usr/sbin/chronyd
-a always,exclude -F msgtype=CWD

# === 마지막: 규칙 잠금 (프로덕션 필수) ===
-e 2

# 규칙 적용: augenrules --load 또는 service auditd restart

augenrules/etc/audit/rules.d/의 모든 .rules 파일을 알파벳 순으로 병합하여 /etc/audit/audit.rules를 생성합니다. 규칙 파일에 번호 접두사를 사용하면 순서를 제어할 수 있습니다: 10-base.rules, 20-watch.rules, 30-syscall.rules, 99-finalize.rules.

시스템 콜 감사 예제

# 특정 사용자(UID 1000)의 모든 파일 삭제 감사
auditctl -a always,exit -F arch=b64 -S unlinkat -F uid=1000 -k user_deletes

# 감사 로그 확인
ausearch -k user_deletes
# ----
# type=SYSCALL msg=audit(1706000000.123:456): arch=c000003e
#   syscall=263 success=yes exit=0 a0=ffffff9c a1=55a8b... 
#   uid=1000 gid=1000 comm="rm" exe="/usr/bin/rm"
# type=PATH msg=audit(1706000000.123:456): item=1
#   name="secret.txt" inode=12345 nametype=DELETE

로그 분석 기법

# 1. 의심스러운 실행 파일 탐지
ausearch -m EXECVE --start today -i | \
  aureport -x --summary | sort -rn -k1 | head -20

# 2. 실패한 파일 접근 상위 사용자
ausearch -sv no -m SYSCALL --start this-week | \
  aureport --user --summary | sort -rn -k1

# 3. 시간대별 이벤트 분포 (DDoS/brute-force 탐지)
aureport --start today --summary

# 4. 특정 파일에 접근한 모든 프로세스 추적
ausearch -f /etc/shadow -i --start this-month

# 5. 권한 상승 이벤트 추적 (setuid/setgid 호출)
ausearch -m SYSCALL -sc setuid --start today -i
ausearch -m SYSCALL -sc setgid --start today -i

# 6. 특정 사용자의 전체 세션 활동 재구성
ausearch --session 42 -i | aureport --file
ausearch --session 42 -i | aureport --syscall
ausearch --session 42 -i | aureport --host

# 7. PROCTITLE 디코딩 (hex → 텍스트)
ausearch -k exec_log --start today --raw | \
  awk '/proctitle=/{gsub(/proctitle=/,""); cmd=$NF; gsub(/../," 0x&",cmd); print cmd}' | \
  xargs printf '%b\n'

audisp 플러그인

audisp(Audit Dispatcher)은 감사 이벤트를 외부 시스템으로 전달하는 플러그인 프레임워크입니다. /etc/audit/plugins.d/에 플러그인 설정 파일을 배치합니다.

# /etc/audit/plugins.d/syslog.conf — syslog 전달 플러그인
active = yes
direction = out
path = /sbin/audisp-syslog
type = always
args = LOG_INFO
format = string

# /etc/audit/plugins.d/af_unix.conf — 유닉스 소켓 전달
active = yes
direction = out
path = builtin_af_unix
type = always
args = 0640 /var/run/audispd_events
format = string
플러그인용도설정 파일
audisp-syslogsyslog/rsyslog로 감사 이벤트 전달syslog.conf
audisp-remote원격 감사 서버로 TCP 전달au-remote.conf
sedispatchSELinux setroubleshoot 연동sedispatch.conf
audisp-af_unix유닉스 소켓으로 이벤트 전달af_unix.conf
audisp-ids침입 탐지 시스템 연동ids.conf

성능 최적화

감사의 성능 영향:

Audit 서브시스템은 시스템 콜 경로에 후크를 삽입하므로 성능 오버헤드가 발생합니다. 규칙 수가 많을수록, 매칭 빈도가 높을수록 오버헤드가 증가합니다. 일반적으로 1~5% 수준이지만, -S all 규칙이나 과도한 파일 감시 시 더 커질 수 있습니다.

최적화 기법설명
규칙 순서 최적화never(제외) 규칙을 always 규칙보다 앞에 배치. 고빈도 제외 대상을 먼저 매칭하여 불필요한 평가 방지
노이즈 프로세스 제외-a never,exit -F exe=/path/to/noisy로 감사 불필요한 데몬 제외
auid 필터 활용-F auid>=1000 -F auid!=4294967295로 데몬 프로세스 자동 제외
백로그 크기 조정-b 8192 이상으로 설정. 백로그 초과 시 이벤트 유실 발생
backlog_wait_time백로그 가득 찬 경우 태스크 대기 시간(ms). 0이면 대기 없이 유실
로그 플러시 방식flush = INCREMENTAL_ASYNC로 비동기 플러시 (성능↑, 약간의 유실 위험)
ENRICHED 비활성화log_format = RAW로 auditd CPU 부하 절감 (UID→이름 해석 생략)
exclude 규칙-a always,exclude -F msgtype=CWD로 불필요 레코드 타입 제외
/* kernel/auditsc.c — 규칙 매칭 오버헤드 최소화를 위한 최적화 */

/* syscall 비트마스크로 해당 syscall에 규칙이 있는지 O(1) 확인 */
static inline int audit_n_rules;  /* 전체 규칙 수 — 0이면 감사 완전 스킵 */

/* 감사 비활성화 시 빠른 경로 */
static inline void audit_syscall_entry(int major, ...)
{
    /* audit_n_rules == 0 이면 즉시 반환 (오버헤드 거의 0) */
    if (unlikely(audit_n_rules))
        __audit_syscall_entry(major, ...);
}

보안 컴플라이언스 규칙 세트

주요 보안 표준에서 요구하는 감사 규칙 예시:

표준요구사항대응 규칙
PCI-DSS 10.2.1관리자 활동 감사-a always,exit -F arch=b64 -S execve -F uid=0 -k admin_cmd
PCI-DSS 10.2.2root 활동 감사-w /etc/sudoers -p wa -k sudo_changes
PCI-DSS 10.2.4접근 실패 기록-a always,exit -S openat -F exit=-EACCES -k access_fail
PCI-DSS 10.2.6감사 로그 초기화/삭제-w /var/log/audit/ -p wa -k audit_log_tamper
PCI-DSS 10.2.7객체 생성/삭제-a always,exit -S unlink -S rmdir -k object_delete
CIS 4.1.4시간 변경-a always,exit -S adjtimex -S clock_settime -k time-change
CIS 4.1.6MAC 정책 변경-w /etc/selinux/ -p wa -k MAC-policy
CIS 4.1.8로그인/로그아웃-w /var/log/lastlog -p wa -k logins
CIS 4.1.9세션 정보-w /var/run/utmp -p wa -k session
사전 구성된 규칙 세트:

audit 패키지에는 CIS, PCI-DSS, STIG 등에 맞춘 사전 규칙 세트가 포함되어 있습니다. /usr/share/audit/sample-rules/ 디렉토리에서 30-pci-dss-v31.rules, 30-stig.rules, 30-nispom.rules 등을 참조하세요. augenrules로 필요한 규칙 파일을 /etc/audit/rules.d/에 복사하여 활성화합니다.

LSM 연동

Audit 서브시스템은 SELinux, AppArmor 등 LSM과 긴밀하게 연동됩니다. LSM이 접근을 거부하면 자동으로 감사 레코드가 생성됩니다.

# SELinux AVC 거부 → 자동 감사 레코드 생성
type=AVC msg=audit(1706000000.456:321): avc:  denied  { read } for
  pid=3456 comm="httpd" name="index.html" dev="sda1" ino=67890
  scontext=system_u:system_r:httpd_t:s0
  tcontext=system_u:object_r:user_home_t:s0
  tclass=file permissive=0

# AppArmor DENIED → 자동 감사 레코드 생성
type=AVC msg=audit(1706000000.789:654):
  apparmor="DENIED" operation="open"
  profile="/usr/sbin/nginx" name="/etc/shadow"
  pid=5678 comm="nginx" requested_mask="r" denied_mask="r"
  fsuid=33 ouid=0

# SELinux 컨텍스트 기반 감사 규칙
# 특정 SELinux 타입이 접근하는 이벤트만 감사
auditctl -a always,exit -F arch=b64 -S openat \
  -F subj_type=httpd_t -k httpd_access
/* security/selinux/avc.c — SELinux AVC에서 감사 호출 */
static void avc_audit_post_callback(
    struct audit_buffer *ab,
    void *a)
{
    struct common_audit_data *ad = a;
    audit_log_format(ab, " ");
    audit_log_untrustedstring(ab, ad->selinux_audit_data->scontext);
    audit_log_format(ab, " tcontext=");
    audit_log_untrustedstring(ab, ad->selinux_audit_data->tcontext);
    audit_log_format(ab, " tclass=%s",
                     secclass_map[ad->selinux_audit_data->tclass - 1].name);
}

/* 감사 결정: AVC denied 또는 auditallow 규칙 매칭 시 로그 */
static inline int avc_audit_required(
    u32 requested, struct av_decision *avd,
    int result, u32 auditdeny, u32 *deniedp)
{
    u32 denied = requested & ~avd->allowed;

    /* 거부된 경우: auditdeny 마스크와 교차 확인 */
    if (denied && (denied & auditdeny))
        return 1;   /* 감사 필요 */

    /* 허용된 경우: auditallow 마스크 확인 */
    if (!denied && (requested & avd->auditallow))
        return 1;   /* auditallow로 허용 이벤트도 감사 */

    return 0;       /* 감사 불필요 */
}

11. IMA/EVM 및 Lockdown

IMA(Integrity Measurement Architecture)와 EVM(Extended Verification Module)은 파일 무결성을 보장합니다. Lockdown LSM은 커널의 자체 변경을 방지합니다.

IMA/EVM과 Lockdown의 상세 구현은 Secure Boot 심화 — IMA/EVMSecure Boot 심화 — Lockdown LSM을 참조하세요.

기술역할핵심 CONFIG
IMA파일 해시 측정 및 검증. TPM PCR에 기록CONFIG_IMA
IMA Appraisalxattr 서명 검증. 변조 파일 실행 차단CONFIG_IMA_APPRAISE
EVM보안 xattr(SELinux 라벨, IMA 해시) 무결성 보호CONFIG_EVM
Lockdown커널 자체 수정 방지 (kexec, /dev/mem, eBPF 쓰기 등)CONFIG_SECURITY_LOCKDOWN_LSM
# IMA 상태 확인
cat /sys/kernel/security/ima/runtime_measurements_count
cat /sys/kernel/security/ima/ascii_runtime_measurements | head

# Lockdown 상태
cat /sys/kernel/security/lockdown
# [none] integrity confidentiality

12. 보안 관련 커널 설정 종합

카테고리CONFIG 옵션설명
LSMCONFIG_SECURITYLSM 프레임워크 활성화
CONFIG_SECURITY_SELINUXSELinux
CONFIG_SECURITY_APPARMORAppArmor
CONFIG_SECURITY_SMACKSMACK
CONFIG_SECURITY_LANDLOCKLandlock
CONFIG_SECURITY_YAMAYama (ptrace 제한)
seccompCONFIG_SECCOMPseccomp 기본
CONFIG_SECCOMP_FILTERseccomp-bpf 필터
CapabilitiesCONFIG_SECURITY_CAPABILITIESPOSIX capabilities (항상 on)
CONFIG_DEFAULT_SECURITY기본 major LSM 선택
CONFIG_LSMLSM 활성화 순서 문자열
하드닝CONFIG_RANDOMIZE_BASEKASLR
CONFIG_STACKPROTECTOR_STRONG스택 보호 (강함)
CONFIG_FORTIFY_SOURCE버퍼 오버플로 감지
CONFIG_HARDENED_USERCOPYuser-kernel copy 검증
CONFIG_STRICT_KERNEL_RWX커널 코드 W^X
무결성CONFIG_IMAIntegrity Measurement
CONFIG_EVMExtended Verification
CONFIG_SECURITY_LOCKDOWN_LSMLockdown LSM
감사CONFIG_AUDIT감사 프레임워크
CONFIG_AUDITSYSCALL시스템 콜 감사
메모리CONFIG_INIT_ON_ALLOC_DEFAULT_ON할당 시 0 초기화
CONFIG_SLAB_FREELIST_RANDOMSlab 랜덤화
CONFIG_SLAB_FREELIST_HARDENEDSlab 포인터 보호
보안과 성능의 트레이드오프:

보안 하드닝 옵션은 성능 오버헤드를 수반합니다. CONFIG_INIT_ON_FREE_DEFAULT_ON은 1~5%, KPTI는 1~5% (시스템 콜 집중 워크로드), Retpoline은 ~2% 정도입니다. 프로덕션 환경에서는 보안 요구사항과 성능 영향을 벤치마크하여 선택하세요.

역사적 커널 보안 취약점 사례

리눅스 커널의 보안 취약점은 하드웨어 수준의 설계 결함부터 소프트웨어 논리 오류까지 다양한 형태로 발견되어 왔습니다. 주요 사례를 분석하고, 각 취약점이 커널 보안 아키텍처에 미친 영향과 완화 기법의 변천을 살펴봅니다.

Spectre 변종들과 완화 기법 변천사

2018년 1월 공개된 Spectre 취약점은 현대 프로세서의 투기적 실행(speculative execution) 메커니즘을 악용하여 커널 메모리를 읽어낼 수 있는 하드웨어 수준의 결함입니다. 커널은 소프트웨어 완화 기법을 통해 이 문제에 대응해 왔습니다.

Spectre v1 (CVE-2017-5753) — 배열 경계 검사 우회 (Bounds Check Bypass):

투기적 실행 중 배열 경계 검사가 우회되어, 공격자가 의도적으로 범위 밖 인덱스를 사용해 캐시 사이드채널을 통해 커널 메모리를 읽을 수 있습니다. 조건 분기가 아직 해결되지 않은 상태에서 프로세서가 경계 검사를 통과한 것으로 추측하고 실행을 진행하는 점을 악용합니다.

/* 취약한 코드 패턴 — Spectre v1에 노출 */
if (index < array_size) {
    value = array[index];           /* 투기적 실행 시 bounds check 우회 */
    leak  = probe_array[value * 4096]; /* 캐시 사이드채널 */
}

/* 완화된 코드 패턴 — array_index_nospec() 적용 */
#include <linux/nospec.h>

if (index < array_size) {
    index = array_index_nospec(index, array_size); /* 투기적 실행 시 인덱스를 0으로 클램프 */
    value = array[index];
}

array_index_nospec()는 조건 분기와 무관하게 인덱스를 안전 범위로 제한하는 데이터 의존성 기반 마스킹을 수행합니다. 투기적 실행 중에도 인덱스가 유효 범위를 벗어나지 않도록 보장합니다.

Spectre v2 (CVE-2017-5715) — 분기 예측 주입 (Branch Target Injection):

공격자가 간접 분기(indirect branch)의 예측 대상을 조작하여, 커널이 투기적으로 공격자가 선택한 가젯(gadget) 코드를 실행하게 만듭니다. 이를 통해 임의의 커널 메모리를 캐시 사이드채널로 유출할 수 있습니다.

/*
 * Retpoline: 간접 분기를 투기적 실행 불가능한 구조로 변환
 * GCC/Clang -mindirect-branch=thunk 옵션으로 자동 적용
 */

/* 일반 간접 호출 (취약) */
call *%rax                /* 분기 예측기가 대상을 조작 가능 */

/* Retpoline 변환 후 (완화) */
__x86_indirect_thunk_rax:
    call    retpoline_call_target
capture_ret:
    pause                    /* 투기적 실행이 여기서 무한 루프 */
    lfence
    jmp     capture_ret
retpoline_call_target:
    mov     %rax, (%rsp)     /* 실제 대상 주소를 리턴 스택에 배치 */
    ret                      /* ret로 간접 점프 → 분기 예측기 우회 */
하드웨어 완화 기법 (IBRS/IBPB/STIBP):

최신 프로세서는 마이크로코드 업데이트를 통해 하드웨어 수준의 완화를 제공합니다:

IBRS (Indirect Branch Restricted Speculation): 권한 수준이 변경될 때 간접 분기 예측을 제한합니다.
IBPB (Indirect Branch Prediction Barrier): 컨텍스트 전환 시 분기 예측 버퍼를 무효화합니다.
STIBP (Single Thread Indirect Branch Predictors): SMT(하이퍼스레딩) 환경에서 스레드 간 분기 예측 정보 공유를 차단합니다.
eIBRS (Enhanced IBRS)가 지원되는 경우, Retpoline 없이 하드웨어만으로 Spectre v2를 완화할 수 있습니다. /sys/devices/system/cpu/vulnerabilities/spectre_v2에서 현재 완화 상태를 확인할 수 있습니다.

/* 커널 내부 Spectre 완화 상태 확인 */
$ cat /sys/devices/system/cpu/vulnerabilities/spectre_v1
Mitigation: usercopy/swapgs barriers and __user pointer sanitization

$ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2
Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; STIBP: conditional; RSB filling; BHI: BHI_DIS_S

권한 상승 취약점 패턴 분석

커널 권한 상승(privilege escalation) 취약점은 일반 사용자가 root 권한을 획득할 수 있게 하는 가장 위험한 유형의 버그입니다. 파일시스템, 프로세스 관리, 메모리 관리 서브시스템의 경계에서 권한 검사가 누락되는 패턴이 반복적으로 발견됩니다.

CVE-2021-4154 — cgroup v1 release_agent 임의 명령 실행:

cgroup v1의 release_agent 파일에 임의의 경로를 기록하면, cgroup 내 마지막 프로세스가 종료될 때 해당 경로의 바이너리가 호스트의 root 권한으로 실행됩니다. 컨테이너 내부에서 cgroup 파일시스템에 접근 가능한 경우, 호스트 탈출과 root 권한 획득이 동시에 가능한 심각한 취약점입니다.

/* CVE-2021-4154 공격 흐름 (개념적 설명) */

/* 1. 컨테이너 내부에서 cgroup v1 마운트 */
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp

/* 2. 자식 cgroup 생성 및 notify_on_release 활성화 */
mkdir /tmp/cgrp/child
echo 1 > /tmp/cgrp/child/notify_on_release

/* 3. release_agent에 호스트 경로의 악성 스크립트 지정 */
echo /path/to/malicious_script > /tmp/cgrp/release_agent

/* 4. child cgroup에서 프로세스 시작 후 즉시 종료 */
/* → release_agent가 호스트 root로 실행됨 */
sh -c "echo \$\$ > /tmp/cgrp/child/cgroup.procs"
CVE-2022-0847 (Dirty Pipe) — pipe 버퍼 플래그 오류로 임의 파일 쓰기:

Linux 5.8에서 도입된 버그로, pipe 버퍼의 PIPE_BUF_FLAG_CAN_MERGE 플래그가 새 pipe 버퍼 할당 시 초기화되지 않아 발생합니다. splice()로 파일 페이지 캐시를 pipe에 매핑한 뒤, 해당 pipe에 write()하면 페이지 캐시를 직접 덮어쓸 수 있습니다. 이를 통해 읽기 전용 파일(예: /etc/passwd, SUID 바이너리)을 임의로 수정하여 root 권한을 획득합니다.

/* Dirty Pipe (CVE-2022-0847) 핵심 메커니즘 */

/* copy_page_to_iter_pipe()에서 플래그 초기화 누락 */
static size_t copy_page_to_iter_pipe(struct page *page, ...) {
    struct pipe_buffer *buf = &pipe->bufs[i_head & p_mask];
    buf->ops   = &page_cache_pipe_buf_ops;
    buf->page  = page;
    buf->offset = offset;
    buf->len   = bytes;
    /* BUG: buf->flags 초기화 누락!
     * 이전 사용에서 PIPE_BUF_FLAG_CAN_MERGE가 남아있으면
     * 후속 write()가 페이지 캐시를 직접 덮어씀 */
}

/* 수정: flags를 명시적으로 초기화 */
buf->flags = 0;  /* 또는 적절한 초기값 설정 */
CVE-2023-0386 — OverlayFS + FUSE를 통한 setuid 바이너리 생성:

OverlayFS의 하위 레이어(lower layer)에 FUSE 파일시스템을 사용할 때, FUSE에서 setuid/setgid 비트가 설정된 파일을 제공하면 OverlayFS가 상위 레이어(upper layer)로 복사(copy-up)하는 과정에서 권한 검사가 누락됩니다. 일반 사용자가 user namespace 내에서 FUSE 마운트를 통해 root 소유의 setuid 바이너리를 생성하고, 이를 호스트에서 실행하여 권한을 상승시킬 수 있습니다.

권한 상승 취약점의 공통 패턴:

위 세 가지 취약점에서 반복되는 패턴은 파일시스템 계층 간 권한 검사 누락입니다:

- cgroup v1: cgroup 파일시스템 조작 → 호스트 명령 실행 (네임스페이스 경계 미검증)
- Dirty Pipe: pipe → 페이지 캐시 (버퍼 플래그 전파 시 초기화 누락)
- OverlayFS: FUSE → OverlayFS copy-up (레이어 간 setuid 비트 전파 시 권한 미검증)
커널 서브시스템 간 데이터가 이동할 때, 각 경계에서 권한과 무결성을 재검증해야 합니다.

Container Escape 취약점

컨테이너는 커널의 namespace, cgroup, seccomp 등으로 격리를 구현하지만, 커널 자체가 공유되므로 커널 취약점을 통한 탈출(escape)이 가능합니다. 컨테이너 보안의 근본적 한계와 주요 탈출 사례를 분석합니다.

CVE-2020-15257 — containerd shim API 접근으로 호스트 탈출:

containerd의 shim API가 abstract Unix domain socket으로 노출되어 있었으며, 호스트 네트워크 네임스페이스(--net=host)를 사용하는 컨테이너에서 이 소켓에 접근할 수 있었습니다. 공격자는 shim API를 통해 새로운 컨테이너를 호스트의 PID/mount 네임스페이스에서 실행시켜 호스트에 완전한 접근 권한을 획득합니다.

/* abstract Unix socket은 네트워크 네임스페이스에 바인딩됨 */
/* --net=host 컨테이너는 호스트의 abstract socket에 접근 가능 */

/* containerd shim이 abstract socket으로 API 노출 (취약) */
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0';  /* abstract namespace — 파일시스템에 없음 */
strncpy(&addr.sun_path[1], "/containerd-shim/...", ...);

/* 수정: filesystem-based socket으로 전환 → mount namespace로 격리 */
addr.sun_path[0] = '/';   /* 일반 경로 — mount namespace에 의해 격리됨 */
strncpy(addr.sun_path, "/run/containerd/shim/...", ...);
CVE-2022-0185 — fsconfig 힙 오버플로우를 통한 네임스페이스 탈출:

fsconfig() 시스템 콜에서 사용자 입력의 길이 검증이 부족하여 정수 언더플로우가 발생하고, 이로 인해 커널 힙에서 버퍼 오버플로우가 일어납니다. user namespace 내의 CAP_SYS_ADMIN 권한만으로도 트리거 가능하며, 힙 오버플로우를 통해 커널 자료구조를 덮어쓴 뒤 임의 코드 실행으로 초기 네임스페이스(init namespace)로 탈출할 수 있습니다.

/* CVE-2022-0185: legacy_parse_param()에서의 정수 언더플로우 */

static int legacy_parse_param(struct fs_context *fc,
                              struct fs_parameter *param) {
    ...
    size_t len = 0;

    /* 여러 파라미터를 연결하며 len 누적 */
    len += strlen(param->key);
    len += strlen(param->string);

    /* BUG: len이 PAGE_SIZE를 초과할 때 size 계산에서 언더플로우 발생 */
    size_t size = PAGE_SIZE - len;  /* 언더플로우 → 매우 큰 값 */

    /* 오버플로우된 size로 memcpy → 힙 오버플로우 */
    memcpy(buf + len, param->string, size);
    ...
}
Container Escape 방어 교훈:

컨테이너 탈출 취약점에 대한 핵심 방어 전략:

1. User Namespace 제한: 불필요한 경우 user.max_user_namespaces=0으로 비활성화하여 CVE-2022-0185와 같은 공격의 전제 조건을 제거합니다.
2. Seccomp 프로파일 강화: fsconfig, fsopen 등 컨테이너에서 불필요한 시스템 콜을 차단합니다. Docker의 기본 seccomp 프로파일은 약 44개의 시스콜을 차단하지만, 워크로드에 맞는 최소 프로파일을 적용하면 attack surface를 더욱 축소할 수 있습니다.
3. 네트워크 격리 유지: --net=host 사용을 최소화하고, abstract Unix socket을 통한 호스트 서비스 노출을 점검합니다.
4. 커널 업데이트: 컨테이너 런타임과 커널을 최신 상태로 유지하는 것이 가장 기본적이면서 효과적인 방어입니다.

커널 정보 누출 (Information Leak) 패턴

커널 정보 누출은 커널 메모리의 내용이 사용자 공간으로 복사되어 KASLR 우회, 힙 스프레이 등 후속 공격에 활용되는 취약점 유형입니다. 초기화되지 않은 메모리가 사용자에게 전달되는 패턴이 가장 흔합니다.

초기화되지 않은 스택/힙 데이터 누출:

커널이 사용자 공간으로 데이터를 복사할 때, 구조체의 패딩 바이트나 사용되지 않는 필드가 초기화되지 않으면 이전 커널 스택/힙의 잔류 데이터가 그대로 사용자에게 전달됩니다. 이 데이터에는 커널 포인터(KASLR 우회), 다른 프로세스의 민감한 데이터, 커널 자료구조 주소 등이 포함될 수 있습니다.

/* 취약한 패턴: copy_to_user() 전 구조체 초기화 누락 */
struct user_info {
    __u32 type;      /* 4 bytes */
    __u64 value;     /* 8 bytes */
    /* 컴파일러가 type과 value 사이에 4바이트 패딩 삽입
     * 이 패딩은 초기화되지 않으면 커널 스택 데이터를 포함 */
};

long vulnerable_ioctl(struct file *f, unsigned int cmd, unsigned long arg) {
    struct user_info info;
    /* BUG: memset 누락! info의 패딩 바이트에 스택 잔류 데이터 존재 */
    info.type  = 1;
    info.value = get_some_value();
    /* 패딩 포함하여 16바이트 전체를 사용자 공간으로 복사 */
    if (copy_to_user((void __user *)arg, &info, sizeof(info)))
        return -EFAULT;
    return 0;
}

/* 수정된 패턴: memset()으로 전체 구조체 초기화 */
long safe_ioctl(struct file *f, unsigned int cmd, unsigned long arg) {
    struct user_info info;
    memset(&info, 0, sizeof(info));  /* 패딩 포함 전체 초기화 */
    info.type  = 1;
    info.value = get_some_value();
    if (copy_to_user((void __user *)arg, &info, sizeof(info)))
        return -EFAULT;
    return 0;
}
컴파일러 수준의 자동 초기화 방어:

수동 memset()은 누락되기 쉽기 때문에, 커널은 컴파일러 수준에서 자동으로 스택 변수를 초기화하는 옵션을 제공합니다:

CONFIG_INIT_STACK_ALL_ZERO: GCC 12+ / Clang 16+에서 -ftrivial-auto-var-init=zero 옵션을 사용하여 모든 스택 변수를 0으로 자동 초기화합니다. 성능 오버헤드는 1% 미만으로 보고되며, 정보 누출 취약점 클래스 전체를 체계적으로 제거합니다.
STRUCTLEAK GCC 플러그인: GCC 플러그인으로 사용자 공간에 복사되는 구조체를 감지하여 자동으로 0 초기화합니다. CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL이 가장 강력한 모드입니다.

/* CONFIG_INIT_STACK_ALL_ZERO 효과 */

/* 컴파일러가 함수 진입 시 모든 로컬 변수를 자동 초기화 */
long example_ioctl(struct file *f, unsigned int cmd, unsigned long arg) {
    struct user_info info;
    /* -ftrivial-auto-var-init=zero에 의해
     * info 전체(패딩 포함)가 자동으로 0 초기화됨
     * 별도의 memset() 없이도 정보 누출 방지 */
    info.type  = 1;
    info.value = get_some_value();
    if (copy_to_user((void __user *)arg, &info, sizeof(info)))
        return -EFAULT;
    return 0;
}

/* 커널 빌드 설정 */
/* .config에서 다음 옵션 활성화 */
CONFIG_INIT_STACK_ALL_ZERO=y          /* 스택 전체 0 초기화 */
CONFIG_GCC_PLUGIN_STRUCTLEAK=y        /* STRUCTLEAK 플러그인 (GCC 전용) */
CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL=y /* 모든 참조 전달 구조체 초기화 */
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y     /* 힙 할당 시 0 초기화 */

Dirty COW (CVE-2016-5195) — Copy-on-Write 경쟁 조건

2016년 10월 공개된 Dirty COW는 리눅스 커널 역사상 가장 널리 악용된 권한 상승 취약점 중 하나입니다. get_user_pages()의 Copy-on-Write 처리 과정에서 발생하는 경쟁 조건(race condition)을 악용하여, 읽기 전용 파일을 임의로 수정할 수 있습니다. 2007년(커널 2.6.22)부터 존재한 버그로, 약 9년간 커널에 잠복해 있었습니다.

CVE-2016-5195 (Dirty COW) — 핵심 메커니즘:

madvise(MADV_DONTNEED)/proc/self/mem에 대한 write()를 동시에 반복 호출하면, CoW 페이지 폴트 처리 경로에서 경쟁 조건이 발생합니다. follow_page_mask()가 CoW 복사본 대신 원본 읽기 전용 페이지에 대한 쓰기 참조를 반환하게 되어, 읽기 전용 파일(/etc/passwd, SUID 바이너리 등)을 직접 수정할 수 있습니다.

/* Dirty COW (CVE-2016-5195) 경쟁 조건 분석 */

/*
 * 정상 CoW 흐름:
 * 1. write fault → do_wp_page() → 새 페이지 할당 및 복사
 * 2. PTE를 새 페이지로 업데이트 (RW)
 * 3. 원본 페이지는 변경되지 않음
 *
 * 경쟁 조건 흐름:
 * Thread A: write(/proc/self/mem) → get_user_pages(FOLL_WRITE)
 *   → faultin_page() → CoW 복사 시작
 * Thread B: madvise(MADV_DONTNEED) → 방금 만든 CoW 복사본을 무효화
 *   → PTE를 다시 원본 읽기 전용 페이지로 되돌림
 * Thread A: follow_page_mask() 재시도 → FOLL_WRITE 제거 후
 *   → 원본 읽기 전용 페이지에 쓰기 참조 반환!
 */

/* mm/gup.c — 취약한 코드 (수정 전) */
static long __get_user_pages(..., unsigned int gup_flags, ...) {
retry:
    ...
    page = follow_page_mask(vma, start, foll_flags, &page_mask);
    if (!page) {
        ret = faultin_page(tsk, vma, start, foll_flags, &nonblocking);
        /* CoW 복사가 일어났지만, 다른 스레드가
         * madvise(MADV_DONTNEED)로 복사본을 폐기할 수 있음 */
        if (!ret)
            goto retry;
        /* retry 시 FOLL_WRITE가 빠져 원본 페이지에 접근 */
    }
}

/* 수정: FOLL_COW 플래그 도입 (commit 19be0eaffa3a) */
/* CoW 복사가 완료된 경우에만 쓰기 접근을 허용하고,
 * retry 시 CoW 상태를 재검증 */
Dirty COW의 교훈:

영향 범위: Linux 2.6.22 ~ 4.8.3 (2007~2016, 약 9년 잠복)
CVSS 점수: 7.8 (High) — 로컬 권한 상승
실제 악용: Android 루팅, 서버 침해에 광범위하게 사용됨
근본 원인: 멀티스레드 환경에서 메모리 관리 상태 변경과 접근 권한 검사 사이의 TOCTOU(Time-of-Check Time-of-Use) 문제
방어: vm.unprivileged_userfaultfd=0 설정, 커널 업데이트, grsecurity/PaX 패치

하드웨어 투기적 실행 취약점 변형들

Spectre/Meltdown 이후 프로세서의 투기적 실행 관련 취약점이 지속적으로 발견되었습니다. 커널은 각 변형에 대해 소프트웨어 완화를 구현하고, /sys/devices/system/cpu/vulnerabilities/를 통해 시스템의 취약 여부와 완화 상태를 노출합니다.

L1TF / Foreshadow (CVE-2018-3615, CVE-2018-3620, CVE-2018-3646):

L1 Terminal Fault는 Intel 프로세서에서 PTE(Page Table Entry)의 Present 비트가 0인 경우에도 L1 데이터 캐시에서 투기적으로 데이터를 읽을 수 있는 취약점입니다. SGX 엔클레이브, OS 커널, 가상 머신(VMX) 환경에 각각 영향을 미칩니다.

/* L1TF 완화 상태 확인 */
$ cat /sys/devices/system/cpu/vulnerabilities/l1tf
Mitigation: PTE Inversion; VMX: conditional cache flushes, SMT vulnerable

/*
 * L1TF 커널 완화 기법:
 *
 * 1. PTE Inversion: non-present PTE에서 물리 주소 비트를 반전시켜
 *    투기적 실행 시 L1 캐시 히트를 방지
 *    (arch/x86/include/asm/pgtable.h — protnone_mask())
 *
 * 2. VMX L1D Flush: VM 진입 전 L1 데이터 캐시를 플러시
 *    kvm_intel.vmentry_l1d_flush=always|cond|never
 *
 * 3. SMT 비활성화: HyperThread 형제 코어를 통한 L1D 공유 차단
 *    nosmt 또는 echo off > /sys/devices/system/cpu/smt/control
 */

/* 커널 부트 파라미터 */
l1tf=full        /* PTE inversion + VMX flush + SMT 비활성화 */
l1tf=full,force  /* 강제 적용 (사용자 변경 불가) */
l1tf=flush       /* PTE inversion + VMX flush (SMT 유지) */
l1tf=flush,nosmt /* flush + SMT 비활성화 */
l1tf=off         /* 완화 비활성화 (위험) */
MDS — Microarchitectural Data Sampling (CVE-2018-12126/12127/12130, CVE-2019-11091):

MDS는 Intel 프로세서의 내부 마이크로아키텍처 버퍼(Store Buffer, Fill Buffer, Load Port)에서 투기적 실행을 통해 데이터를 샘플링할 수 있는 취약점 계열입니다. RIDL(Rogue In-Flight Data Load), Fallout, ZombieLoad라는 이름으로도 알려져 있습니다.

/* MDS 취약점 변형들 */

/*
 * MSBDS (CVE-2018-12126) — Microarchitectural Store Buffer Data Sampling
 *   Store Buffer에 남은 이전 데이터를 투기적으로 읽기 (Fallout)
 *
 * MFBDS (CVE-2018-12130) — Microarchitectural Fill Buffer Data Sampling
 *   Fill Buffer의 데이터를 투기적으로 읽기 (ZombieLoad)
 *
 * MLPDS (CVE-2018-12127) — Microarchitectural Load Port Data Sampling
 *   Load Port에 남은 데이터를 투기적으로 읽기 (RIDL)
 *
 * MDSUM (CVE-2019-11091) — Microarchitectural Data Sampling Uncacheable Memory
 *   Uncacheable 메모리 접근 시 마이크로아키텍처 버퍼 데이터 누출
 */

/* MDS 완화 확인 */
$ cat /sys/devices/system/cpu/vulnerabilities/mds
Mitigation: Clear CPU buffers; SMT vulnerable

/*
 * 커널 완화: VERW 명령어를 사용한 CPU 버퍼 클리어
 * - 컨텍스트 전환 시 VERW 실행 → 내부 버퍼 덮어쓰기
 * - 커널→사용자 모드 전환 시 실행
 * - VM exit 시 실행
 * - idle 진입 시 실행
 *
 * 커널 부트 파라미터:
 * mds=full       — VERW 완화 + SMT 경고
 * mds=full,nosmt — VERW 완화 + SMT 비활성화
 * mds=off        — 완화 비활성화
 */
GDS / Downfall (CVE-2022-40982):

Gather Data Sampling은 Intel의 AVX2/AVX-512 GATHER 명령어 실행 시 같은 물리 코어의 SIMD 레지스터 파일에서 이전 데이터를 투기적으로 수집할 수 있는 취약점입니다. AES 키, TLS 세션 키 등 벡터 레지스터에 로드된 민감한 데이터가 유출될 수 있습니다.

/* GDS/Downfall 완화 */
$ cat /sys/devices/system/cpu/vulnerabilities/gather_data_sampling
Mitigation: Microcode

/*
 * GDS 완화:
 * 1. 마이크로코드 업데이트: GATHER 명령어의 투기적 데이터 전달 차단
 * 2. 커널 부트 파라미터:
 *    gather_data_sampling=force  — 강제 완화
 *    gather_data_sampling=off    — 완화 비활성화
 *
 * 영향받는 프로세서: Skylake, Ice Lake, Rocket Lake 등
 * (Alder Lake, Sapphire Rapids 이후는 하드웨어 수정됨)
 *
 * 성능 영향: GATHER 명령어 사용 워크로드에서 ~50% 저하 가능
 * (OpenSSL AES-GCM, 데이터베이스 벡터화 쿼리 등)
 */

/* 시스템의 전체 하드웨어 취약점 상태 일괄 확인 */
$ for f in /sys/devices/system/cpu/vulnerabilities/*; do
    echo "$(basename $f): $(cat $f)"
done
/* 출력 예시:
 * gather_data_sampling: Mitigation: Microcode
 * itlb_multihit: Not affected
 * l1tf: Mitigation: PTE Inversion; VMX: ...
 * mds: Mitigation: Clear CPU buffers; SMT vulnerable
 * meltdown: Mitigation: PTI
 * mmio_stale_data: Mitigation: Clear CPU buffers; SMT vulnerable
 * retbleed: Mitigation: Enhanced IBRS
 * spec_store_bypass: Mitigation: Speculative Store Bypass disabled ...
 * spectre_v1: Mitigation: usercopy/swapgs barriers ...
 * spectre_v2: Mitigation: Enhanced / Automatic IBRS; ...
 * srbds: Mitigation: Microcode
 * tsx_async_abort: Mitigation: TSX disabled
 */

Use-After-Free (UAF) 취약점 패턴

Use-After-Free는 리눅스 커널에서 가장 빈번하게 발견되는 메모리 안전성 취약점 유형입니다. 해제된 메모리 객체에 대한 참조가 남아있어, 해당 메모리가 재할당된 후 공격자가 제어하는 데이터로 덮어쓸 수 있습니다. 참조 카운트 관리 오류, 비동기 콜백의 생명주기 불일치, RCU 보호 누락 등이 근본 원인입니다.

nf_tables UAF (CVE-2024-1086) — Netfilter 권한 상승:

nf_tables의 verdict 처리에서 nft_verdict_init()NF_DROP에 긍정적 errno 값(예: NF_ACCEPT)을 허용하여, nf_hook_slow()에서 패킷이 Drop 대신 Accept로 처리됩니다. 이 과정에서 nft_do_chain()이 이미 해제된 skb에 대해 verdict를 수행하여 Use-After-Free가 발생합니다. CVSS 7.8, 로컬 권한 상승이 가능합니다.

/* CVE-2024-1086: nf_tables verdict 처리 결함 */

/* net/netfilter/nf_tables_api.c — 취약한 코드 */
static int nft_verdict_init(const struct nft_ctx *ctx, ...) {
    ...
    data->verdict.code = ntohl(nla_get_be32(tb[NFTA_VERDICT_CODE]));
    /* BUG: NF_DROP(0)에 양수 errno를 허용 → NF_DROP + 양수 = NF_ACCEPT(1)
     * nf_hook_slow()에서 이 verdict를 NF_ACCEPT로 해석 */
    switch (data->verdict.code) {
    case NF_ACCEPT:
    case NF_DROP:  /* NF_DROP에 양수 errno 허용됨 */
    case NF_QUEUE:
        break;
    ...
    }
}

/* 수정: NF_DROP의 errno를 음수 값만 허용하도록 검증 추가 */
case NF_DROP:
    if (data->verdict.code & NF_VERDICT_MASK)
        data->verdict.code &= ~NF_VERDICT_MASK;  /* errno 비트 제거 */
    break;
CVE-2014-3153 (Towelroot) — futex 서브시스템 UAF:

futex_requeue()에서 PI(Priority Inheritance) futex의 waiter를 non-PI futex로 requeue할 때, rt_mutex 소유권 전이 과정에서 Use-After-Free가 발생합니다. Android 루팅 도구 "Towelroot"로 널리 악용되었으며, 2014년 대부분의 Android 기기에 영향을 미쳤습니다.

/* CVE-2014-3153: futex requeue 결함 */

/*
 * 정상 동작: futex_requeue()로 waiter를 다른 futex로 이동
 * 취약점:
 * 1. PI futex(FUTEX_LOCK_PI) waiter를 non-PI futex(FUTEX_WAIT)로 requeue
 * 2. rt_mutex의 top_waiter가 변경되지만, 이전 waiter의 참조가 남음
 * 3. 이전 waiter의 task_struct가 해제된 후에도 rt_mutex가 참조
 * → Use-After-Free → 임의 코드 실행
 *
 * 수정: requeue 시 PI와 non-PI futex 간 전환을 명시적으로 금지
 *       FUTEX_CMP_REQUEUE_PI만 PI futex 간 requeue 허용
 */

/* kernel/futex.c — 수정 */
static int futex_requeue(...) {
    /* 추가된 검증: requeue_pi가 아닌 경우 PI futex 거부 */
    if (!requeue_pi && match_futex(&key1, &key2)) {
        ret = -EINVAL;
        goto out_put_keys;
    }
    ...
}
UAF 취약점의 공통 패턴과 방어:

커널에서 UAF가 발생하는 주요 패턴과 각각의 방어 기법:

1. 참조 카운트 오류: kref, refcount_t의 put/get 불일치 → CONFIG_REFCOUNT_FULL로 underflow 감지
2. 비동기 콜백 생명주기: 타이머, workqueue, RCU 콜백이 소유 객체보다 오래 생존 → 해제 전 cancel_work_sync(), del_timer_sync() 필수
3. SLAB 재할당 공격: 해제된 slab 객체를 같은 크기의 공격자 제어 객체로 대체 → CONFIG_SLAB_FREELIST_HARDENED, CONFIG_RANDOM_KMALLOC_CACHES(6.6+)
4. RCU 보호 누락: RCU로 보호해야 할 포인터를 직접 해제 → kfree_rcu() 사용, rcu_dereference() 접근자 필수
탐지: KASAN(CONFIG_KASAN)이 UAF를 런타임에 감지. KFENCE(CONFIG_KFENCE)는 프로덕션 환경에서도 낮은 오버헤드로 UAF를 샘플링 탐지합니다.

Race Condition 취약점 패턴

커널의 동시성(concurrency) 처리에서 발생하는 경쟁 조건은 재현이 어렵고 탐지가 늦어지는 특성 때문에 장기간 잠복하는 경우가 많습니다. Dirty COW, 다수의 파일시스템/네트워크 스택 취약점이 이 패턴에 해당합니다.

CVE-2022-2588 — cls_route UAF (경쟁 조건):

네트워크 트래픽 분류기 route4에서 필터 삭제와 동시 접근 사이의 경쟁 조건으로 Use-After-Free가 발생합니다. 네임스페이스 내 CAP_NET_ADMIN 권한만으로 트리거 가능하며, 로컬 권한 상승이 가능합니다.

CVE-2023-3269 (StackRot) — Maple Tree RCU 경쟁 조건:

Linux 6.1에서 도입된 Maple Tree 자료구조의 VMA(Virtual Memory Area) 관리에서 RCU 보호와 mmap_lock 상호작용에 경쟁 조건이 존재합니다. Maple Tree 노드의 교체 과정에서 RCU 콜백이 아직 읽기 중인 노드를 해제하여 Use-After-Free가 발생합니다. 스택 기반의 공격(stack pivot)으로 권한 상승이 가능합니다.

/* Race Condition 취약점 탐지 도구 */

/* 1. KCSAN (Kernel Concurrency Sanitizer) — 동적 데이터 레이스 탐지 */
CONFIG_KCSAN=y
CONFIG_KCSAN_STRICT=y            /* 엄격 모드 */
CONFIG_KCSAN_REPORT_ONCE_IN_MS=0 /* 모든 레이스 보고 */

/* KCSAN이 탐지하는 패턴:
 * - 락 없는 공유 변수 동시 접근 (data race)
 * - READ_ONCE/WRITE_ONCE 누락
 * - 보호되지 않는 구조체 필드 접근
 */

/* 2. Lockdep — 잠금 의존성 및 데드락 사전 탐지 */
CONFIG_LOCKDEP=y
CONFIG_PROVE_LOCKING=y
CONFIG_LOCK_STAT=y

/* 3. syzbot/syzkaller — 퍼저 기반 경쟁 조건 탐지
 * Google이 운영하는 커널 퍼저로, 시스템 콜을 무작위 조합하여
 * 멀티스레드 시나리오에서 경쟁 조건을 자동 탐지
 * https://syzkaller.appspot.com — 발견된 버그 대시보드 */
커널 취약점 추적 리소스:

/sys/devices/system/cpu/vulnerabilities/ — 하드웨어 취약점 상태 및 완화 확인
https://www.cvedetails.com/vendor/33/Linux.html — Linux 커널 CVE 데이터베이스
https://syzkaller.appspot.com — syzbot 커널 퍼저 발견 버그 대시보드
https://git.kernel.org/pub/scm/linux/security/vulns.git/ — 커널 보안 취약점 공식 추적