커널 하드닝 (Kernel Hardening)

리눅스 커널의 방어 심층(defense-in-depth) 전략을 종합합니다. KASLR, KPTI, kCFI/FineIBT, SMEP/SMAP, Stack Protector, FORTIFY_SOURCE, W^X 정책, SLAB 하드닝, GCC 보안 플러그인, Lockdown LSM, Spectre/Meltdown 하드웨어 취약점(Vulnerability) 완화, 그리고 프로덕션 환경별 하드닝 체크리스트까지 다룹니다.

전제 조건: 커널 보안 개요메모리 관리(Memory Management) 문서를 먼저 읽으면 이해가 수월합니다. 하드닝 기법은 메모리 레이아웃, 페이지 테이블(Page Table), 컴파일러 계측 개념과 밀접하게 연결됩니다.
일상 비유: 커널 하드닝은 건물의 다층 보안 시스템과 비슷합니다. 외벽(KASLR), 출입문 잠금(KPTI), CCTV(CFI), 금고(W^X), 경보 장치(Stack Protector)가 각각 독립적으로 동작하지만, 모두 함께 가동될 때 침입자가 목표에 도달하기 극도로 어려워집니다.

핵심 요약

  • 하드웨어 보호 — SMEP, SMAP, CET, NX/XD, MTE 등으로 사용자 공간 코드 실행을 차단합니다.
  • 주소 은닉 — KASLR, FKASLR, KPTI로 커널 주소를 랜덤화하고 페이지 테이블을 분리합니다.
  • 제어 흐름 보호 — kCFI, FineIBT, Shadow Call Stack으로 JOP/ROP 공격을 차단합니다.
  • 메모리 무결성 — Stack Protector, FORTIFY_SOURCE, W^X로 오버플로와 코드 주입을 탐지합니다.
  • 힙 하드닝 — SLAB freelist 랜덤화/하드닝으로 힙 스프레이와 UAF 익스플로잇을 방지합니다.
  • 접근 제어 — Lockdown LSM, Seccomp으로 커널 자체 수정과 공격 표면을 축소합니다.
방어 계층대표 기술차단 대상성능 영향
하드웨어 보호SMEP, SMAP, CET, NX/XD, MTEret2usr, 사용자 공간(User Space) 코드 실행무시 가능
주소 은닉KASLR, FKASLR, KPTI주소 예측 ROP, Meltdown1~5%
제어 흐름 보호kCFI, FineIBT, Shadow Call Stack, GCSJOP, COOP, ROP~1%
메모리 무결성(Integrity)Stack Protector, FORTIFY_SOURCE, W^X스택/힙 오버플로, 코드 주입<1%
힙 하드닝SLAB freelist 랜덤화/하드닝, RANDOM_KMALLOC힙 스프레이, UAF 익스플로잇<2%
접근 제어(Access Control)Lockdown LSM, Seccomp, LSM커널 자체 수정, 공격 표면 축소없음~최소

단계별 이해

  1. 문제 인식: 커널에 버그가 있으면 공격자가 루트 권한을 탈취할 수 있습니다. 하드닝은 버그가 있어도 익스플로잇을 실패시키는 전략입니다.
  2. 주소 은닉 (KASLR): 커널 코드 위치를 랜덤화하여, 공격자가 "어디를 공격할지" 알 수 없게 만듭니다.
  3. 페이지(Page) 격리 (KPTI): 사용자 공간에서 커널 메모리를 볼 수 없게 페이지 테이블을 분리합니다.
  4. 하드웨어 차단 (SMEP/SMAP): CPU가 커널 모드에서 사용자 공간 코드 실행/데이터 접근을 하드웨어적으로 차단합니다.
  5. 제어 흐름 검증 (CFI): 함수 포인터를 변조해도 컴파일러가 삽입한 해시(Hash) 검증으로 잘못된 함수를 호출할 수 없게 합니다.
  6. 메모리 보호 (Stack Protector, FORTIFY_SOURCE): 스택/힙 오버플로를 런타임에 탐지하여 즉시 차단합니다.
  7. 종심 방어: 각 계층이 독립적으로 동작하므로, 한 계층이 우회되어도 다음 계층이 공격을 차단합니다.

하드닝 계층 개요

리눅스 커널은 다양한 컴파일 시점 및 런타임 보안 옵션을 제공합니다. 이러한 하드닝 옵션은 취약점이 존재하더라도 공격의 성공 확률을 크게 낮추는 방어 심층(defense-in-depth) 전략의 핵심입니다.

커널 하드닝 옵션 계층 하드웨어 보호 (CPU 기능) SMEP 커널→유저 코드 실행 차단 SMAP 커널→유저 데이터 접근 차단 CET Shadow Stack / IBT NX/XD 데이터 페이지 실행 방지 MTE (ARM64) 메모리 태깅 확장 컴파일러 방어 (빌드 시점) FORTIFY_SOURCE 문자열 함수 오버플로우 검사 CFI (Clang) 간접 호출 대상 검증 STACKPROTECTOR 스택 카나리 (오버플로우 감지) INIT_STACK_ALL 스택 자동 초기화 Shadow CS 리턴 주소 보호 메모리 보호 (런타임) HARDENED_USERCOPY 커널↔사용자 복사 검증 SLAB 하드닝 프리리스트 포이즌/랜덤화 KASLR 주소 공간 무작위화 KPTI 커널 페이지 테이블 격리 RODATA 읽기 전용 데이터 보호 접근 제어 (공격 표면 축소) Seccomp-BPF 시스템 콜 필터링 LSM (SELinux 등) 강제적 접근 통제 Namespaces 격리 경계 설정 Capabilities 최소 권한 분할 Lockdown 커널 자체 수정 차단 위에서 아래로: 하드웨어 → 컴파일러 → 메모리 → 접근 제어 (방어 심층) 공격자는 모든 계층을 우회해야 익스플로잇 성공 → 한 계층이라도 차단되면 공격 실패

KASLR (Kernel Address Space Layout Randomization)

커널 코드, 모듈, 물리 메모리(Physical Memory) 매핑(Mapping)의 베이스 주소를 부팅 시 랜덤으로 배치하여, 메모리 레이아웃을 예측할 수 없게 합니다. 공격자가 커널 심볼(Kernel Symbol)의 정확한 주소를 알아야 하는 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 진입 시 스택 오프셋(Offset)을 랜덤 조정

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은 함수 단위로 재배치(Relocation)하여 이 한계를 극복합니다.

/* 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 포맷 사용 강제
타이밍 사이드채널캐시(Cache)/TLB 타이밍으로 주소 추론KPTI, 사이트 격리
Spectre 변종투기적 실행(Speculative Execution)으로 커널 메모리 읽기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 커널 파라미터:

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

KPTI (Kernel Page Table Isolation)

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

KPTI는 사용자 모드와 커널 모드에서 별도의 페이지 테이블을 사용합니다. 사용자 공간 페이지 테이블에는 커널 주소 공간(Address Space)이 최소한만 매핑(트램폴린 코드, 인터럽트(Interrupt) 핸들러(Handler) 엔트리)되어, Meltdown 류 사이드 채널로 커널 메모리를 읽을 수 없게 합니다.

/* arch/x86/mm/pti.c — KPTI 페이지 테이블 분리 */
/* 커널 진입 시 CR3 레지스터를 전환하여 전체 커널 매핑을 활성화 */

/* 사용자 모드 PGD: 최소 커널 매핑만 포함 */
static void pti_clone_entry_text(void)
{
    /* entry_SYSCALL_64, 인터럽트 핸들러 등
     * 트램폴린 코드만 사용자 PGD에 복사 */
    pti_clone_pgtable((unsigned long)__entry_text_start,
                       (unsigned long)__entry_text_end,
                       PTI_CLONE_PMD);
}

/* CR3 전환: 비트 12(PCID)로 커널/사용자 PGD 구분 */
/* SWITCH_TO_KERNEL_CR3: CR3 비트 12 클리어 → 커널 PGD */
/* SWITCH_TO_USER_CR3:   CR3 비트 12 설정 → 사용자 PGD */
#define PTI_USER_PGTABLE_BIT   PAGE_SHIFT
#define PTI_USER_PGTABLE_MASK  (1 << PTI_USER_PGTABLE_BIT)
# KPTI 상태 확인
dmesg | grep "page tables isolation"
# Kernel/User page tables isolation: enabled

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

KPTI 성능 영향: 시스템 콜(System Call) 집중 워크로드에서 1~5% 오버헤드(Overhead)가 발생합니다. PCID(Process Context Identifier)를 지원하는 CPU에서는 TLB 플러시(Flush)를 줄여 오버헤드가 최소화됩니다. 최신 Intel/AMD CPU(Meltdown에 영향받지 않는 세대)에서는 KPTI가 자동으로 비활성화됩니다.

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 내부에서 자동 처리 */

SMEP은 Intel Ivy Bridge(2012), AMD Zen(2017)부터 지원됩니다. SMAP은 Intel Broadwell(2014), AMD Zen(2017)부터 지원됩니다. 두 기능 모두 CR4 레지스터(Register)의 비트로 제어되며, 커널이 부팅 시 CPU 기능을 감지하여 자동 활성화합니다.

# SMEP/SMAP 지원 확인
grep -o 'smep\|smap' /proc/cpuinfo | sort -u
# smep
# smap

# CR4 레지스터 비트 확인 (디버깅 환경)
# CR4.SMEP = bit 20, CR4.SMAP = bit 21

SMEP/SMAP 내부 구현과 우회 방어

SMEP(Supervisor Mode Execution Prevention)과 SMAP(Supervisor Mode Access Prevention)은 CR4 레지스터의 특정 비트를 통해 제어됩니다. 커널 모드(Ring 0)에서 사용자 공간 메모리에 대한 실행(SMEP)과 접근(SMAP)을 하드웨어 수준에서 차단하여, ret2usr 공격을 원천 봉쇄합니다.

CR4 레지스터 보안 비트 필드와 SMEP/SMAP 동작 CR4 레지스터 (64-bit) Bit 20: SMEP Bit 21: SMAP Bit 23: CET Bit 11: UMIP Bit 18: OSXSAVE Bit 5: PAE Bit 13: VMXE Bit 7: PGE ... SMEP 동작 (Bit 20 = 1) Ring 0 (커널 모드) 코드 실행 시도 PTE.U/S = 1 (유저)? NO (커널 페이지) 실행 허용 YES (유저 페이지) #PF (Page Fault) ret2usr 공격 차단! SMEP 우회 시도: CR4.SMEP 클리어 → pinned CR4 bits로 방어 (v4.0+) → native_write_cr4()에서 핀 검증 cr4_pinned_mask |= X86_CR4_SMEP | X86_CR4_SMAP SMAP 동작 (Bit 21 = 1) Ring 0 (커널 모드) 데이터 접근 시도 RFLAGS.AC = 1? YES (stac) 접근 허용 NO (기본 상태) #PF (Page Fault) 의도치 않은 유저 접근 차단! stac/clac: copy_from_user() 내부에서 SMAP을 일시 해제/재활성화 → AC flag 윈도우를 최소화 stac → copy → clac (최소 권한 구간)

CR4 핀닝과 우회 방어

공격자가 커널 코드 실행 권한을 얻은 후 mov cr4, rax로 SMEP/SMAP 비트를 클리어하는 우회 기법이 있었습니다. 커널 4.0+에서는 CR4 핀닝(pinning)으로 이를 방어합니다.

/* arch/x86/kernel/cpu/common.c — CR4 핀닝 */

static unsigned long cr4_pinned_bits __read_mostly;

void __init setup_cr_pinning(void)
{
    unsigned long mask;

    mask = (X86_CR4_SMEP | X86_CR4_SMAP | X86_CR4_UMIP);
    cr4_pinned_bits = this_cpu_read(cpu_tlbstate.cr4) & mask;
}

/* native_write_cr4()에서 핀된 비트가 클리어되면 복원 */
void native_write_cr4(unsigned long val)
{
    unsigned long bits_missing = cr4_pinned_bits & ~val;

    if (unlikely(bits_missing)) {
        /* 경고 출력 후 핀된 비트 강제 복원 */
        pr_warn_once("pinned CR4 bits changed: 0x%lx!?\n",
                     bits_missing);
        val |= bits_missing;
    }
    __write_cr4(val);
}

ARM64 PAN (Privileged Access Never)

ARM64에서 SMAP에 해당하는 기능은 PAN(Privileged Access Never)입니다. ARMv8.1부터 하드웨어로 지원되며, 이전 프로세서에서는 소프트웨어 PAN(CONFIG_ARM64_SW_TTBR0_PAN)으로 에뮬레이션합니다.

기능x86_64ARM64RISC-V
커널→유저 실행 차단SMEP (CR4.20)PXN (Page eXecute Never)Smepmp (PMP)
커널→유저 접근 차단SMAP (CR4.21)PAN (PSTATE.PAN)Smepmp (PMP)
일시 해제 명령stac / clacuaccess_enable / disableN/A
지원 시작Ivy Bridge / BroadwellARMv8.0(PXN) / v8.1(PAN)Sifive U74+
/* arch/arm64/include/asm/uaccess.h — ARM64 PAN 제어 */

static inline void __uaccess_enable_hw_pan(void)
{
    /* PSTATE.PAN = 0 → 유저 메모리 접근 허용 */
    asm(".arch_extension pan\nmsr panauthkeys, %0"
        :: "r"(0));
}

static inline void __uaccess_disable_hw_pan(void)
{
    /* PSTATE.PAN = 1 → 유저 메모리 접근 차단 (기본 상태) */
    asm(".arch_extension pan\nmsr panauthkeys, %0"
        :: "r"(1));
}

/* 소프트웨어 PAN (ARMv8.0 프로세서용) */
/* TTBR0_EL1을 빈 페이지 테이블로 교체하여 유저 매핑 제거 */
/* copy_from_user() 시에만 원래 TTBR0 복원 */

UMIP (User-Mode Instruction Prevention)

UMIP(CR4 비트 11)은 사용자 모드에서 SGDT, SIDT, SLDT, SMSW, STR 명령을 실행하면 #GP 예외를 발생시킵니다. 이 명령들은 커널 주소 정보를 유출할 수 있어 KASLR 우회에 악용됩니다.

/* UMIP: 사용자 모드에서 차단되는 명령 */
/*
 * SGDT — GDT 베이스 주소 → 커널 주소 유출
 * SIDT — IDT 베이스 주소 → 커널 주소 유출
 * SLDT — LDT 셀렉터
 * SMSW — Machine Status Word (CR0 일부)
 * STR  — Task Register
 *
 * CONFIG_X86_UMIP=y (4.15+)
 * 커널은 #GP 에뮬레이션으로 0을 반환하여 레거시 앱 호환성 유지
 */
SMEP/SMAP/UMIP 확인:
grep -oE 'smep|smap|umip' /proc/cpuinfo | sort -u
# 출력: smep, smap, umip

세 기능 모두 /proc/cpuinfo의 flags에서 확인 가능합니다. 비활성화하려면 커널 커맨드라인에 nosmep, nosmap을 추가하지만, 프로덕션에서는 절대 사용하지 마세요.

ARM64 MTE (Memory Tagging Extension)

MTE(Memory Tagging Extension)는 ARMv8.5-A에서 도입된 하드웨어 기반 메모리 안전성 기능입니다. 모든 메모리 포인터와 메모리 영역에 4비트 태그를 부여하고, 접근 시 태그 불일치를 하드웨어가 탐지합니다. Use-After-Free, 버퍼 오버플로(Buffer Overflow), 더블 프리 등의 메모리 버그를 하드웨어 수준에서 탐지합니다.

ARM64 MTE: 포인터 태그와 메모리 태그 검증 태깅된 포인터 (64-bit) Tag [59:56] 가상 주소 [55:0] 4-bit (0~15) TBI (Top Byte Ignore)로 태그 영역 사용 할당된 메모리 영역 Tag=5 16B Granule ×N Tag=A 인접 객체 Tag=3 해제된 영역 Tag=9 다른 할당 정상 접근 ptr Tag=5 → mem Tag=5 ✓ 버퍼 오버플로 탐지 ptr Tag=5 → mem Tag=A ✗ Use-After-Free 탐지 stale ptr Tag=5 → mem Tag=3 ✗ MTE 모드 Sync: 즉시 예외 → 정확한 위치 디버깅 Async: 지연 보고 → 낮은 오버헤드 (~3%) Asymm: 읽기=비동기 쓰기=동기 (최적 균형) CONFIG_ARM64_MTE=y MTE 주요 명령어 IRG (Insert Random Tag) — 포인터에 랜덤 태그 삽입 STG (Store Allocation Tag) — 메모리 그래뉼에 태그 설정 (16바이트 단위) LDG (Load Allocation Tag) — 메모리 태그 로드 ADDG/SUBG — 태그 유지하며 포인터 연산 태그 불일치 시: Sync 모드에서 SIGSEGV(SEGV_MTESERR), Async 모드에서 SIGSEGV(SEGV_MTEAERR)

커널 MTE 지원

/* arch/arm64/include/asm/mte-kasan.h — 커널 MTE 통합 */

/* KASAN과 MTE 통합: CONFIG_KASAN_HW_TAGS */
/* MTE를 하드웨어 KASAN 백엔드로 사용 */

static inline u8 mte_get_ptr_tag(void *ptr)
{
    /* 포인터의 비트 [59:56]에서 4-bit 태그 추출 */
    return ((u64)ptr >> 56) & 0xF;
}

static inline void *mte_set_mem_tag_range(
    void *addr, size_t size, u8 tag)
{
    /* STG 명령으로 [addr, addr+size) 영역에 태그 설정 */
    /* 16바이트 단위(granule)로 태그 저장 */
    do {
        asm("stg %0, [%0]" : "+r"(addr));
        addr += 16;  /* MTE granule size */
        size -= 16;
    } while (size);
    return addr;
}

/* SLUB 할당자 MTE 통합 */
/* kmalloc() 반환 시: IRG로 랜덤 태그 생성 → STG로 영역 태깅 */
/* kfree() 시: 새 태그로 재태깅 → stale 포인터 접근 시 태그 불일치 */
MTE 모드CONFIG 옵션오버헤드탐지 정밀도사용 환경
Sync (동기)kasan.mode=sync~15%정확한 위치개발/디버깅
Async (비동기)kasan.mode=async~3%대략적 위치프로덕션
Asymm (비대칭)kasan.mode=asymm~5%쓰기=정확, 읽기=대략프로덕션 (권장)
ℹ️

MTE 지원 하드웨어: Google Pixel 8 (Tensor G3), Samsung Galaxy S24+ (Exynos 2400), Qualcomm Snapdragon 8 Gen 2+, 그리고 Arm Neoverse N2/V2 서버 CPU에서 MTE를 지원합니다. Android 14+에서 앱별 MTE 활성화가 가능하며, 커널에서는 CONFIG_KASAN_HW_TAGS=y로 하드웨어 KASAN을 활성화합니다.

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 동작 메커니즘:
  • 함수 프롤로그에 endbr64 (IBT 유효 분기 대상 표시) + subl $hash, %r10d (해시 검증) 삽입
  • 간접 호출 사이트에서 movl $hash, %r10d로 기대 해시를 전달 후 call *%rax
  • 해시 불일치 시 ud2 → #UD 예외 → BUG()
  • 활성화 조건: CONFIG_X86_KERNEL_IBT=y + CONFIG_CFI_CLANG=y + CET-IBT 지원 CPU (Intel 12세대+, AMD Zen 4+)
ℹ️

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로 오버플로 감지 */

AArch64 GCS — Guarded Control Stack (커널 6.13+)

커널 6.13에서 AArch64 GCS(Guarded Control Stack) 유저스페이스 지원이 추가되었습니다. GCS는 Armv9.4-A의 하드웨어 기능으로, Shadow Call Stack의 소프트웨어 구현을 하드웨어로 대체합니다.

구분Shadow Call Stack (기존)GCS (v6.13+)
구현컴파일러 삽입 코드 (x18 레지스터)CPU 하드웨어 (별도 GCS 메모리)
보호 대상커널 코드만유저스페이스 + 커널 모두
변조 방지소프트웨어 기반 (우회 가능)하드웨어 강제 (일반 store 명령으로 GCS 메모리 쓰기 불가)
오버헤드<1%~0% (하드웨어 지원)
GCS 활성화: prctl(PR_SET_SHADOW_STACK_STATUS)로 스레드별 GCS를 활성화합니다. GCS는 x86의 Intel CET Shadow Stack과 유사한 개념이며, 함수 호출 시 리턴 주소가 별도의 하드웨어 보호 스택에 자동 저장됩니다.

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 환경에서 호환성 문제가 있을 수 있습니다.

스택 프로텍터 (Stack Protector)

스택 프로텍터는 스택 버퍼 오버플로를 런타임에 탐지하는 컴파일러 기반 방어 기술입니다. 함수 진입 시 리턴 주소와 지역 변수 사이에 카나리(canary) 값을 삽입하고, 함수 반환 직전에 카나리가 변조되었는지 검증합니다. 변조가 감지되면 __stack_chk_fail()이 호출되어 커널 패닉(또는 프로세스 종료)을 발생시킵니다.

"카나리(canary)" 이름의 유래: 19~20세기 탄광에서는 일산화탄소(CO)나 메탄 같은 유독가스를 감지하기 위해 카나리아 새를 갱도에 데려갔습니다. 사람보다 호흡이 빠른 새가 먼저 쓰러지면 광부들이 즉시 대피하는 조기 경보 센서 역할이었습니다. 보안에서도 동일한 비유가 적용됩니다 — 스택에 삽입된 카나리 값이 "새" 역할을 하여, 버퍼 오버플로가 리턴 주소에 도달하기 전에 카나리가 먼저 변조되어 위험을 알립니다. 이 용어는 1998년 Crispin Cowan 등이 발표한 StackGuard 논문에서 처음 사용되었습니다.

CONFIG 옵션 비교

옵션GCC 플래그보호 대상오버헤드
CONFIG_STACKPROTECTOR-fstack-protector8바이트 이상 char 배열이 있는 함수만최소 (~0.1%)
CONFIG_STACKPROTECTOR_STRONG-fstack-protector-strong지역 배열, 주소 참조 변수 포함 함수낮음 (~0.5%)
CONFIG_STACKPROTECTOR_ALL-fstack-protector-all모든 함수높음 (~5%)
권장 설정: 대부분의 배포판과 Android 커널은 CONFIG_STACKPROTECTOR_STRONG을 기본으로 사용합니다. -fstack-protector-strong-fstack-protector보다 보호 범위가 넓으면서도 -fstack-protector-all보다 성능 영향이 적어 최적의 균형점입니다.
정상 스택 프레임 리턴 주소 (RIP) 저장된 RBP 카나리 (canary) 지역 변수 (char buf[64] 등) 높은 낮은 오버플로 발생 시 리턴 주소 (변조됨!) 저장된 RBP (변조됨!) 카나리 (변조 감지!) 오버플로된 데이터 ↑ 버퍼 경계 초과 오버플로 방향 함수 반환 직전: 카나리 == __stack_chk_guard ? 일치 불일치 정상 반환 __stack_chk_fail()

커널 구현 세부

/* 커널 스택 카나리 초기화 — init/main.c, arch 코드 */

/* per-CPU 또는 per-task 카나리 값 */
/* x86_64: GS 세그먼트의 고정 오프셋에 저장 */
#ifdef CONFIG_STACKPROTECTOR
unsigned long __stack_chk_guard __read_mostly;
EXPORT_SYMBOL(__stack_chk_guard);
#endif

/* 부팅 초기 카나리 설정 */
static void __init boot_init_stack_canary(void)
{
    unsigned long canary;

    /* 하드웨어 RNG 또는 get_random_bytes()로 생성 */
    get_random_bytes(&canary, sizeof(canary));

    /* 최하위 바이트를 0으로 설정 (문자열 종료 방지) */
    canary &= CANARY_MASK;

    /* current→stack_canary에 저장 */
    current->stack_canary = canary;
}

/* 컴파일러가 함수 에필로그에 삽입하는 검증 코드 (개념) */
void __noreturn __stack_chk_fail(void)
{
    panic("Kernel stack is corrupted in: %pS\n",
          __builtin_return_address(0));
}
per-task 카나리: 최신 커널(v4.20+)은 태스크별 카나리를 사용합니다. 컨텍스트 스위치 시 switch_to()에서 카나리가 교체되므로, 한 태스크(Task)의 카나리 유출이 다른 태스크에 영향을 미치지 않습니다. x86_64에서는 %gs:0x28 오프셋에 저장됩니다.

컴파일러 생성 프롤로그/에필로그 코드

__stack_chk_fail()은 개발자가 직접 호출하는 함수가 아닙니다. GCC(또는 Clang)가 컴파일 시점에 보호 대상 함수의 진입부(프롤로그)와 반환부(에필로그)에 카나리 검증 코드를 자동 삽입합니다. 다음은 x86_64에서 -fstack-protector-strong 적용 시 GCC가 생성하는 실제 어셈블리입니다.

; ── 함수 프롤로그 (GCC 자동 삽입) ──────────────────────
    push   %rbp
    mov    %rsp, %rbp
    sub    $0x50, %rsp           ; 지역 변수 공간 할당
    mov    %gs:0x28, %rax        ; per-task 카나리를 GS 세그먼트에서 로드
    mov    %rax, -0x8(%rbp)     ; 스택 프레임(RBP 바로 아래)에 카나리 저장
    xor    %eax, %eax           ; 레지스터에서 카나리 흔적 즉시 제거

    ; ... 함수 본문 (지역 변수, 버퍼 연산 등) ...

; ── 함수 에필로그 (GCC 자동 삽입) ──────────────────────
    mov    -0x8(%rbp), %rax     ; 스택에서 저장된 카나리 읽기
    xor    %gs:0x28, %rax        ; 원본 카나리와 XOR 비교
    je     .Lok                   ; 결과가 0이면(일치) 정상 반환
    call   __stack_chk_fail      ; 불일치 → 카나리 변조 감지 → panic()
.Lok:
    leave
    ret

핵심은 xor %gs:0x28, %rax 명령입니다. XOR 연산 결과가 0이 아니면(카나리가 변조됨) je 분기를 타지 않고 바로 call __stack_chk_fail로 진행합니다. 이 함수는 __noreturn 속성을 가지므로 호출 시 복귀하지 않으며, 커널에서는 panic()으로 시스템을 즉시 정지시킵니다.

직접 확인하기: 커널 빌드 후 objdump -d vmlinux | grep -A30 "<함수명>:"으로 특정 함수의 프롤로그/에필로그에 삽입된 카나리 검증 코드를 확인할 수 있습니다. 또는 개별 오브젝트 파일을 gcc -S -fstack-protector-strong으로 컴파일하여 어셈블리 출력을 검토할 수도 있습니다.

-fstack-protector-strong 함수 선택 기준

GCC는 모든 함수에 카나리를 삽입하지 않습니다. -fstack-protector-strong은 다음 조건 중 하나라도 만족하는 함수만 보호 대상으로 선택합니다.

조건예시 코드설명적용 플래그
지역 배열 (모든 타입)int arr[4];배열 크기·타입 무관, char 외 포함-strong, -all
지역 변수 주소 참조foo(&local);포인터를 통한 간접 변조 가능성-strong, -all
alloca() 호출alloca(n);런타임 동적 스택 할당-strong, -all
가변 길이 배열 (VLA)char buf[n];런타임 크기 결정-strong, -all
8바이트 이상 char 배열char buf[8];기본 -fstack-protector의 유일한 조건-basic, -strong, -all
위 조건 없는 함수int add(int a, int b)-fstack-protector-all만 보호-all 전용

-fstack-protector-strong은 GCC 4.9에서 Han Shen(Google)이 도입했습니다. 취약 함수의 약 85%를 ~0.5% 오버헤드로 보호하여, 현재 모든 주요 리눅스 배포판과 Android 커널의 기본 설정입니다.

소스 함수 -fstack-protector-strong 함수 선택 기준 검사 보호? Yes 프롤로그: 카나리 저장 에필로그: 카나리 검증 함수 반환 시 canary == %gs:0x28 ? 일치 정상 반환 불일치 __stack_chk_fail() → panic() No 카나리 미삽입

실제 커널 소스 참조

앞서 보여준 코드는 개념 수준의 단순화입니다. 실제 리눅스 커널에서 스택 프로텍터 관련 코드가 위치하는 주요 파일은 다음과 같습니다.

파일 경로역할비고
kernel/panic.c__stack_chk_fail() 정의__noreturn, panic() 호출
arch/x86/include/asm/stackprotector.hx86 카나리 초기화·교체%gs:0x28 (fixed_percpu_data) 기반
arch/x86/kernel/process.c컨텍스트 스위치 시 카나리 교체__switch_to() 내부에서 호출
include/linux/stackprotector.h아키텍처 공통 인터페이스boot_init_stack_canary() 선언
arch/arm64/include/asm/stackprotector.hARM64 카나리 초기화current->stack_canary 직접 접근
실제 구현 차이: 실제 kernel/panic.c__stack_chk_fail()__builtin_return_address(0)으로 호출 지점 주소를 포함한 패닉 메시지를 출력합니다. 또한 per-task 카나리 교체는 __switch_to()가 호출하는 아키텍처별 매크로에서 처리되며, 컨텍스트 스위치마다 현재 태스크(Task)의 카나리 값이 %gs:0x28 위치에 기록됩니다.

아키텍처별 카나리 구현 차이

아키텍처카나리 저장 위치접근 방식비고
x86_64fixed_percpu_data + 0x28%gs:0x28 (per-CPU 세그먼트)GCC 하드코딩 오프셋, 변경 불가
ARM64task_struct->stack_canarysp_el0current 기반 로드-mstack-protector-guard=sysreg
RISC-Vtask_struct->stack_canarytp (thread pointer) + 오프셋-mstack-protector-guard=tls

x86_64는 GCC가 %gs:0x28 오프셋을 하드코딩하므로, 커널은 반드시 해당 오프셋에 카나리를 배치해야 합니다(fixed_percpu_data 구조체의 stack_canary 필드). 반면 ARM64와 RISC-V는 컴파일러 옵션으로 카나리 위치를 지정할 수 있어 구현에 유연성이 있습니다.

사용자 공간 vs 커널 동작 차이

항목사용자 공간커널
__stack_chk_fail 동작__fortify_fail()abort() → SIGABRTpanic() → 시스템 정지
영향 범위해당 프로세스만 종료전체 시스템 정지
카나리 소스glibc 초기화 시 /dev/urandomget_random_bytes() / 하드웨어 RNG
카나리 위치TLS (%fs:0x28)per-CPU (%gs:0x28)
커널이 panic()하는 이유: 스택 카나리가 변조되었다는 것은 스택 버퍼 오버플로가 발생하여 실행 상태가 이미 손상되었음을 의미합니다. 커널 모드(ring 0)에서 손상된 상태로 실행을 계속하면 임의 코드 실행, 권한 상승, 데이터 손상 등 치명적 결과를 초래할 수 있으므로, 즉시 시스템을 정지시키는 것이 가장 안전한 대응입니다.

스택 프로텍터의 한계

심층 방어 (Defense in Depth): 이러한 한계 때문에 스택 프로텍터는 단독이 아닌 다층 방어의 한 계층으로 사용합니다. VMAP_STACK(가드 페이지로 즉시 감지), CFI(제어 흐름 무결성), KASLR(주소 무작위화), Shadow Call Stack(ARM64 반환 주소 별도 보호)과 조합하면 각 기법의 한계를 상호 보완할 수 있습니다.

VMAP_STACK 커널 스택 가드 페이지

스택 프로텍터는 함수 복귀 시점에 카나리 훼손을 사후 감지합니다. 반면 VMAP_STACK은 커널 스택을 vmalloc 영역에 할당하여 양쪽 끝에 매핑되지 않은 가드 페이지(Guard Page)를 배치합니다. 스택 오버플로우(Stack Overflow) 발생 즉시 하드웨어 페이지 폴트(Page Fault)가 트리거되어, 인접 메모리가 훼손되기 전에 커널이 안전하게 중단됩니다.

감지 흐름 비교

VMAP_STACK 활성 (즉시 감지) 스택 오버플로우 발생 Guard page 접근 → #PF 발생 Double Fault → IST 별도 스택 전환 안전한 kernel panic 출력 인접 메모리 훼손 없음 크래시 덤프로 원인 추적 가능 VMAP_STACK 비활성 (감지 불가) 스택 오버플로우 발생 인접 메모리 조용히 덮어쓰기 데이터 손상 전파 (감지되지 않음) 예측 불가 크래시 또는 익스플로잇 스택 충돌 공격(Stack Clash) 가능 원인 추적 극히 어려움

CONFIG 옵션

CONFIG 옵션도입 버전기능기본값
CONFIG_VMAP_STACK4.9 (x86_64)vmalloc 기반 커널 스택 + 양쪽 Guard pagex86_64 기본 활성
CONFIG_THREAD_SIZE_ORDER스택 크기 (기본 order-2 = 16KB)아키텍처별 기본값

STACK_END_MAGIC 폴백

VMAP_STACK이 비활성인 환경(일부 임베디드, 구형 아키텍처)에서는 스택 최하단에 매직 넘버(Magic Number)를 배치하여 오버플로우를 간접 감지합니다.

/* include/linux/magic.h */
#define STACK_END_MAGIC  0x57AC6E9D

/* include/linux/sched/task_stack.h */
static inline unsigned long *end_of_stack(const struct task_struct *task)
{
    return task->stack;  /* 스택 최하단 (낮은 주소) */
}

/* kernel/fork.c — 새 태스크 생성 시 매직 설정 */
void set_task_stack_end_magic(struct task_struct *tsk)
{
    unsigned long *stackend = end_of_stack(tsk);
    *stackend = STACK_END_MAGIC;
}

/* kernel/sched/core.c — schedule_debug()에서 매 스케줄 시 검사 */
if (unlikely(*end_of_stack(prev) != STACK_END_MAGIC))
    panic("corrupted stack end detected inside scheduler\n");
STACK_END_MAGIC의 한계: 이 매직 검사는 schedule()이 호출될 때만 수행됩니다. 오버플로우 발생 시점과 감지 시점 사이에 이미 인접 메모리가 훼손될 수 있으며, 스케줄링 없이 크래시가 발생하면 아예 감지되지 않습니다. CONFIG_VMAP_STACK 활성화가 가능한 환경이라면 반드시 VMAP_STACK을 우선 사용하십시오.
관련 문서: vmalloc 기반 스택 할당 구현은 vmalloc — 커널 스택과 vmalloc, 가드 페이지가 포함된 커널 스택 메모리 레이아웃은 Context Switching — 커널 스택 가드 페이지와 오버플로 탐지를 참조하십시오.

스택 훼손 방어 체계 종합

앞서 개별적으로 설명한 Stack Protector, VMAP_STACK, Shadow Call Stack, STACKLEAK, INIT_STACK_ALL_ZERO는 각각 스택의 서로 다른 지점을 보호합니다. 이 메커니즘들이 결합되면 방어 심층(Defense-in-Depth) 체계를 형성하여, 어느 하나가 우회되더라도 다음 계층이 공격을 차단합니다.

커널 스택 프레임 — 방어 메커니즘 매핑 Guard Page (unmapped) 리턴 주소 (RIP) 저장된 RBP 스택 카나리 (Canary) 지역 변수 영역 (포인터, 배열, 구조체) 미사용 스택 공간 Guard Page (unmapped) VMAP_STACK 스택 경계 HW 트랩 (즉시) Shadow Call Stack 리턴 주소 별도 보관 (arm64) Stack Protector 카나리 검증 (함수 반환 시) INIT_STACK_ALL_ZERO 지역 변수 0 초기화 (컴파일 시) STACKLEAK 함수 반환 시 0xBAAAAAAD 포이즌 RANDOMIZE_KSTACK_OFFSET syscall마다 오프셋 랜덤화 감지 타이밍별 분류 즉시 (HW 트랩) — VMAP_STACK Guard Page → 오버플로 즉시 #PF 함수 반환 시 — Stack Protector, Shadow Call Stack → 오버플로 후 복귀 전 감지 컴파일 시 — INIT_STACK_ALL_ZERO, FORTIFY_SOURCE → 취약점 원인 사전 차단 사후 클리어 — STACKLEAK → 정보 유출 방지 (탐지가 아닌 예방) 오버플로 전파 단계별 상세는: Context Switching — 스택 오버플로 전파와 훼손 영역 참조 높은 낮은

스택 훼손 관련 CVE 사례

커널 스택 훼손은 실제 환경에서 반복적으로 발생하며, 여러 주요 CVE의 근본 원인입니다. 다음 사례들은 각기 다른 스택 훼손 유형과 이를 방지하는 방어 메커니즘을 보여줍니다.

CVE유형구성요소영향방어 메커니즘
CVE-2016-10153스택 깊이 고갈ecryptfs8KB 스택 오버플로 → THREAD_SIZE 16KB 확대 계기VMAP_STACK
CVE-2017-1000364Stack Clash사용자/커널 경계guard page 건너뛰기 → 권한 상승확장된 guard gap (1MB)
CVE-2019-10126드라이버 스택 오버플로Marvell WiFi (mwifiex)원격 코드 실행Stack Protector
CVE-2022-0435원격 스택 오버플로TIPC 모듈원격 코드 실행 (인증 불요)FORTIFY_SOURCE

사례 분석: CVE-2022-0435 (TIPC 원격 스택 오버플로)

TIPC(Transparent Inter-Process Communication) 프로토콜 모듈에서 도메인(Domain) 레코드 수를 검증하지 않아, 원격 공격자가 조작된 패킷으로 커널 스택을 오버플로시킬 수 있었습니다. 인증 없이 네트워크를 통해 트리거 가능하여 CVSS 7.8의 높은 위험도를 받았습니다.

/* 취약 코드 (net/tipc/mon.c) — 간략화 */
static void tipc_mon_rcv(struct net *net,
                          struct tipc_mon_domain *arrv_dom, ...)
{
    struct tipc_mon_domain dom_bef;  /* 스택에 할당된 버퍼 */
    struct tipc_mon_domain *dom;

    /* ❌ arrv_dom->member_cnt에 대한 상한 검사 없음!
     * member_cnt가 MAX_MON_DOMAIN(64)을 초과하면
     * memcpy가 스택 프레임 경계를 넘어 쓰기 수행 */
    memcpy(&dom_bef, dom, dom_size(dom));
    /*                    ↑ dom_size()는 member_cnt에 비례
     * → 스택 오버플로 → 리턴 주소 변조 → 임의 코드 실행 */
}

/* 수정 코드 (패치) */
static void tipc_mon_rcv(...)
{
    /* ✓ member_cnt 상한 검증 추가 */
    if (arrv_dom->member_cnt > MAX_MON_DOMAIN) {
        pr_warn_ratelimited("Too large domain record\n");
        return;
    }
    /* ... */
}
교훈: 외부 입력(네트워크 패킷)에서 온 크기 값을 검증 없이 memcpy()의 길이로 사용하면 스택 오버플로가 발생합니다. FORTIFY_SOURCE가 활성화되어 있었다면 memcpy()의 대상 크기 초과를 런타임에 감지하여 패닉으로 전환했을 것입니다. 스택 오버플로 전파 과정의 상세 설명은 Context Switching — 스택 오버플로 전파와 훼손 영역을 참조하십시오.

사례 분석: CVE-2016-10153 (ecryptfs 스택 깊이 고갈)

ecryptfs는 암호화 파일시스템으로, 하위 파일시스템(ext4, btrfs 등) 위에 계층적으로 동작합니다. ecryptfs → VFS → 하위 FS의 호출 체인이 깊어지면서, 특히 페이지 캐시 경로에서 8KB 스택을 초과하는 사례가 보고되었습니다.

# 당시 스택 트레이스 (간략화) — 깊은 호출 체인으로 스택 고갈:
ecryptfs_writepage
  → ecryptfs_encrypt_page
    → ecryptfs_write_lower_page_segment
      → vfs_write
        → ext4_file_write_iter
          → ext4_da_write_begin
            → __block_write_begin
              → ext4_get_block
                → ext4_map_blocks
                  → ext4_ext_map_blocks     ← 8KB 스택 거의 소진
                    → ext4_find_extent
# → 14단계 이상의 중첩 호출, 각 프레임 200~500바이트
# → 총 ~7.5KB 사용으로 8KB 스택 경계 도달
결과: 이 CVE는 x86_64의 THREAD_SIZE를 8KB에서 16KB로 확대하는 직접적인 계기가 되었습니다(v3.15). 동시에 CONFIG_VMAP_STACK(v4.9)이 도입되어, 깊이 고갈 시 가드 페이지가 즉시 트랩을 발생시키도록 개선되었습니다.

스택 방어 메커니즘 비교

아래 표는 모든 스택 관련 방어 메커니즘을 감지 시점, 보호 대상, 성능 영향, 아키텍처 지원 관점에서 비교합니다.

메커니즘CONFIG 옵션감지 시점보호 대상오버헤드아키텍처
Stack ProtectorSTACKPROTECTOR_STRONG함수 반환 시스택 버퍼 오버플로~0.5%모든 아키텍처
VMAP_STACKVMAP_STACK즉시 (HW 트랩)스택 깊이 고갈무시 가능x86_64, arm64
Shadow Call StackSHADOW_CALL_STACK함수 반환 시리턴 주소 변조~1%arm64
STACKLEAKGCC_PLUGIN_STACKLEAK함수 반환 시정보 유출(Info Leak)~1%GCC only
INIT_STACK_ALL_ZEROINIT_STACK_ALL_ZERO컴파일 시미초기화 변수 사용1~3%GCC 12+, Clang
STACK_END_MAGICSCHED_STACK_END_CHECKschedule() 시스택 오버플로 (폴백)무시 가능모든 아키텍처
KSTACK_OFFSETRANDOMIZE_KSTACK_OFFSET런타임 (예방)스택 레이아웃 예측~1%x86_64, arm64
KASAN StackKASAN_STACK즉시 (SW 계측)스택 변수 OOB높음 (디버그)모든 아키텍처

환경별 권장 조합

환경필수 활성화권장 추가비고
프로덕션 서버STACKPROTECTOR_STRONG, VMAP_STACK, RANDOMIZE_KSTACK_OFFSETINIT_STACK_ALL_ZERO성능과 보안의 균형
고보안 환경위 전체 + STACKLEAK + INIT_STACK_ALL_ZEROShadow Call Stack (arm64)최대 방어 심층, ~5% 오버헤드 감수
임베디드/IoTSTACKPROTECTOR_STRONG, SCHED_STACK_END_CHECKVMAP_STACK (지원 시)VMAP_STACK 미지원 아키텍처 고려
개발/디버그위 전체 + KASAN_STACK + DEBUG_STACK_USAGEftrace stack tracer성능 무관, 최대 탐지 우선
관련 문서: 스택 오버플로 전파 단계와 디버깅 기법은 Context Switching — 스택 오버플로 전파와 훼손 영역, 스택 훼손 원인의 체계적 분류는 Context Switching — 커널 스택 훼손의 원인 분류를 참조하십시오.

FORTIFY_SOURCE

FORTIFY_SOURCEmemcpy(), strcpy(), memset() 등 메모리/문자열 조작 함수의 버퍼(Buffer) 크기를 컴파일 타임과 런타임에 검증하는 방어 기술입니다. 컴파일러가 대상 버퍼의 크기를 __builtin_object_size()로 추론할 수 있으면 컴파일 에러를 발생시키고, 런타임에만 크기를 알 수 있으면 래퍼 함수가 오버플로를 탐지합니다.

동작 메커니즘

memcpy(dst, src, len) 호출 컴파일 타임 런타임 __builtin_object_size(dst) 버퍼 크기 정적 추론 len > sizeof(dst) ? 정적 크기 비교 컴파일 에러! YES 안전 통과 NO __fortify_memcpy_chk() 래퍼 함수 호출 len > obj_size 런타임 체크 동적 크기 비교 fortify_panic() YES 정상 수행 NO CONFIG_FORTIFY_SOURCE — 컴파일 타임 + 런타임 이중 검증

커널 구현

/* include/linux/fortify-string.h — FORTIFY_SOURCE 래퍼 */

/* 컴파일러 내장 함수로 버퍼 크기 추론 */
#define __underlying_memcpy  __builtin_memcpy
#define __fortify_size(p)    __builtin_object_size(p, 0)

/* memcpy 래퍼: 컴파일 타임 + 런타임 이중 검증 */
__FORTIFY_INLINE void *memcpy(void *dest,
                              const void *src,
                              size_t count)
{
    size_t dest_size = __fortify_size(dest);
    size_t src_size  = __fortify_size(src);

    /* 컴파일 타임 검증: 크기가 상수이면 빌드 에러 */
    if (__builtin_constant_p(count)) {
        if (dest_size != (size_t)-1 && count > dest_size)
            __write_overflow();  /* 빌드 에러 트리거 */
        if (src_size != (size_t)-1 && count > src_size)
            __read_overflow2();
    }

    /* 런타임 검증: 동적 크기일 때 */
    if (dest_size != (size_t)-1 && count > dest_size)
        fortify_panic(__func__);

    return __underlying_memcpy(dest, src, count);
}

/* 보호 대상 함수 목록: */
/* memcpy, memmove, memset, strcpy, strncpy, strcat, strncat,
 * strlen, strnlen, memcmp, kmemdup, ... */

탐지 사례

/* FORTIFY_SOURCE 탐지 예시 (개념 예시) */

struct my_data {
    char name[16];
    int  flags;
};

static void copy_name(struct my_data *data, const char *input)
{
    /* ❌ 컴파일 에러: sizeof(data→name) = 16인데 32바이트 복사 */
    memcpy(data->name, input, 32);

    /* ✓ 안전: 크기 범위 내 */
    memcpy(data->name, input, min(strlen(input) + 1,
                                    sizeof(data->name)));
}
FORTIFY_SOURCE 레벨: 커널은 CONFIG_FORTIFY_SOURCE 하나의 옵션만 제공하지만, 내부적으로 __builtin_object_size(p, 0)(전체 객체 크기)와 __builtin_object_size(p, 1)(서브-객체 크기)를 모두 활용합니다. v6.x 커널에서는 서브-객체 단위(구조체(Struct) 멤버)까지 검증이 확장되어, 구조체 내부 필드 간 오버플로도 감지합니다.

안전한 문자열/메모리 API

FORTIFY_SOURCE가 컴파일타임/런타임에 버퍼 오버플로를 탐지한다면, 안전한 API를 사용하는 것은 오버플로 자체를 원천적으로 방지하는 전략입니다. 리눅스 커널은 위험한 C 표준 함수를 대체하는 안전한 API를 제공하며, KSPP(Kernel Self Protection Project)를 중심으로 위험 함수 제거가 진행되고 있습니다.

문자열 함수 대체표

위험 API안전한 대체 API이유
strcpy()strscpy()대상 버퍼 크기 제한, NUL 종단 보장
strncpy()strscpy()strncpy는 NUL 미종단 가능, 나머지 zero-fill 낭비
strncpy() + zero-fill 필요strscpy_pad()NUL 종단 + 나머지 zero-fill
strlcpy()strscpy()strlcpy는 소스 전체 strlen 수행 (DoS 위험)
sprintf()snprintf() / scnprintf()버퍼 오버플로 방지
sprintf() (동적)kasprintf()필요한 크기를 자동 할당
sysfs에서 sprintf()sysfs_emit()PAGE_SIZE 초과 방지
simple_strtoul()kstrtoul()에러 검출, 오버플로 검사
simple_strtol()kstrtol()에러 검출, 오버플로 검사

문자열 API 사용 예제

/* strscpy — strcpy/strncpy 대체 */
char buf[16];
ssize_t ret = strscpy(buf, user_input, sizeof(buf));
if (ret < 0)
    pr_warn("input truncated (original: %zu bytes)\n",
            strlen(user_input));

/* kasprintf — 동적 크기 안전 포맷 */
char *path = kasprintf(GFP_KERNEL, "/dev/%s/%d", name, id);
if (!path)
    return -ENOMEM;
/* ... 사용 ... */
kfree(path);

/* kstrtoul — simple_strtoul 대체 */
unsigned long val;
int ret = kstrtoul(buf, 10, &val);
if (ret)
    return ret;  /* -EINVAL 또는 -ERANGE */

/* scnprintf — snprintf의 실제 기록 바이트 수 반환 */
char msg[64];
int len = 0;
len += scnprintf(msg + len, sizeof(msg) - len, "cpu=%d ", cpu);
len += scnprintf(msg + len, sizeof(msg) - len, "irq=%u", irq_count);
  1. strscpy()반환값이 음수면 절단이 발생한 것이며, 항상 NUL 종단을 보장합니다. strncpy()와 달리 불필요한 zero-fill을 수행하지 않아 성능도 우수합니다.
  2. kasprintf()내부적으로 vsnprintf()로 필요한 크기를 계산한 후 kmalloc() + vsnprintf()를 수행합니다. 호출자는 반드시 kfree()로 해제해야 합니다.
  3. kstrtoul()오버플로 시 -ERANGE를 반환하고, 유효하지 않은 입력 시 -EINVAL을 반환합니다. simple_strtoul()과 달리 에러 경로가 명확합니다.
  4. scnprintf()snprintf()와 달리 실제 기록된 바이트 수(NUL 제외)를 반환하여 연쇄 호출에 안전합니다. 버퍼가 가득 차면 0을 반환하므로 오버플로 없이 누적 작성이 가능합니다.

메모리 보안 API

위험 패턴안전한 API이유
kfree(ptr) (민감 데이터)kfree_sensitive(ptr)해제 전 메모리를 zero-fill
memset(p,0,n); kfree(p)kfree_sensitive(ptr)컴파일러가 memset 최적화 제거 방지
kvfree(ptr) (민감 데이터)kvfree_sensitive(ptr)kvmalloc 할당에 대한 보안 해제
memset(buf, 0, n) (보안용)memzero_explicit(buf, n)컴파일러 dead store 최적화 방지
/* kfree_sensitive — 민감 데이터 안전 해제 */
struct crypto_key *key = kmalloc(sizeof(*key), GFP_KERNEL);
/* ... 암호키 사용 ... */
kfree_sensitive(key);  /* memzero_explicit() + kfree() */

/* memzero_explicit — 스택 버퍼의 민감 데이터 소거 */
char password[128];
/* ... 패스워드 처리 ... */
memzero_explicit(password, sizeof(password));
/* 컴파일러가 "어차피 함수 끝이니 불필요" 하고 제거하지 않음 */

/* check_object_size — 복사 크기 런타임 검증 */
void my_copy(void *dst, const void *src, size_t n)
{
    check_object_size(dst, n, true);   /* true = 쓰기 대상 */
    check_object_size(src, n, false);  /* false = 읽기 소스 */
    memcpy(dst, src, n);
}
  1. kfree_sensitive()내부적으로 memzero_explicit(ptr, ksize(ptr))kfree()를 호출합니다. ksize()는 실제 할당된 slab 크기를 반환하므로 요청 크기보다 큰 영역까지 완전히 소거됩니다.
  2. memzero_explicit()barrier() 호출로 컴파일러의 dead store elimination을 방지합니다. 일반 memset(p, 0, n)은 이후 p를 사용하지 않으면 컴파일러가 제거할 수 있습니다.
  3. check_object_size()CONFIG_HARDENED_USERCOPY가 활성화되면 copy_to_user()/copy_from_user()에서 자동으로 호출됩니다. slab 객체 경계를 넘는 복사를 탐지하여 커널 메모리 유출을 방지합니다.
KSPP 안전 API 마이그레이션: KSPP(Kernel Self Protection Project)에서는 위험 API 제거를 적극 추진하고 있으며, 최신 커널(6.x)에서는 strcpy(), strncpy(), strlcpy(), simple_strtoul() 등이 점진적으로 제거되고 있습니다. 새로 작성하는 커널 코드에서는 반드시 안전한 대체 API를 사용해야 하며, checkpatch.pl도 위험 함수 사용 시 경고를 출력합니다.

Lockdown LSM

Lockdown LSM은 커널 무결성을 보호하기 위해 커널 자체의 수정을 제한하는 보안 모듈입니다. Secure Boot 환경에서 부팅 체인의 신뢰를 런타임까지 연장하는 핵심 메커니즘으로, integrityconfidentiality 두 단계의 보안 수준을 제공합니다.

ℹ️

Lockdown LSM과 Secure Boot의 관계에 대한 자세한 내용은 Secure Boot & 부팅 보안 — Lockdown 문서를 참조하세요.

Lockdown 모드

Lockdown LSM 보안 수준 lockdown=none 제한 없음 lockdown=integrity 커널 변조 차단 lockdown=confidentiality + 커널 정보 유출 차단 integrity 모드 제한 - 서명되지 않은 모듈 로드 차단 - /dev/mem, /dev/kmem 쓰기 차단 - /dev/port 접근 차단 - kexec_load() 차단 (미서명) - ACPI 테이블 오버라이드 차단 - MSR 쓰기 차단 - ioperm/iopl 차단 - debugfs 쓰기 차단 - EFI 런타임 서비스 쓰기 차단 - PCI BAR 접근 차단 confidentiality 추가 제한 - /dev/mem, /dev/kmem 읽기도 차단 - /proc/kcore 접근 차단 - kprobes 차단 - eBPF 커널 메모리 읽기 차단 - perf 이벤트 접근 차단 - tracefs 접근 차단 - /proc/kallsyms 주소 차단 - bpf_read_kernel() 차단 - RDMSR 직접 접근 차단 - 하드웨어 디버깅(JTAG) 차단 ← 낮음 ─── 보안 수준 ─── 높음 →

커널 구현

/* security/lockdown/lockdown.c */

enum lockdown_reason {
    LOCKDOWN_NONE,
    LOCKDOWN_MODULE_SIGNATURE,
    LOCKDOWN_DEV_MEM,
    LOCKDOWN_KEXEC,
    LOCKDOWN_HIBERNATION,
    LOCKDOWN_PCI_ACCESS,
    LOCKDOWN_IOPORT,
    LOCKDOWN_MSR,
    LOCKDOWN_ACPI_TABLES,
    LOCKDOWN_PCMCIA_CIS,
    LOCKDOWN_TIOCSSERIAL,
    LOCKDOWN_MODULE_PARAMETERS,
    LOCKDOWN_MMIOTRACE,
    LOCKDOWN_DEBUGFS,
    LOCKDOWN_XMON_WR,
    LOCKDOWN_BPF_WRITE_USER,
    LOCKDOWN_DBG_WRITE_KERNEL,     /* integrity 끝 */
    LOCKDOWN_INTEGRITY_MAX,
    LOCKDOWN_KCORE,
    LOCKDOWN_KPROBES,
    LOCKDOWN_BPF_READ_KERNEL,
    LOCKDOWN_PERF,
    LOCKDOWN_TRACEFS,
    LOCKDOWN_XMON_RW,
    LOCKDOWN_XFRM_SECRET,
    LOCKDOWN_CONFIDENTIALITY_MAX,
};

/* LSM 훅: 요청된 작업이 현재 lockdown 수준에서 허용되는지 검사 */
static int lockdown_is_locked_down(enum lockdown_reason what)
{
    if (what <= LOCKDOWN_NONE || what >= LOCKDOWN_CONFIDENTIALITY_MAX)
        return -EPERM;

    if (kernel_locked_down >= what)
        return -EPERM;  /* 차단 */

    return 0;           /* 허용 */
}

제한 항목 상세 테이블

제한 항목integrityconfidentiality영향받는 인터페이스
미서명 모듈 로드차단차단init_module(), finit_module()
/dev/mem 쓰기차단차단/dev/mem, /dev/kmem
/dev/mem 읽기허용차단/dev/mem
kexec_load (미서명)차단차단kexec_load()
MSR 쓰기차단차단/dev/cpu/*/msr
ACPI 테이블 오버라이드차단차단acpi_table_upgrade()
/proc/kcore허용차단/proc/kcore
kprobes허용차단kprobe_register()
eBPF 커널 읽기허용차단bpf(BPF_PROG_LOAD)
perf 이벤트허용차단perf_event_open()
tracefs허용차단/sys/kernel/tracing/
Secure Boot 연동: UEFI Secure Boot가 활성화된 시스템에서 부팅하면, 커널이 자동으로 lockdown=integrity를 적용합니다. 이는 security/lockdown/lockdown.clock_kernel_down()에서 EFI 보안 부팅 상태를 확인하여 설정됩니다. lockdown=confidentiality로 강화하려면 커널 커맨드라인에 명시해야 합니다.

Spectre/Meltdown 방어

CPU 투기적 실행(speculative execution) 취약점은 마이크로아키텍처 수준에서 정보를 유출합니다. Spectre(변형 1, 2), Meltdown(변형 3), 그리고 후속 변형(MDS, TAA, MMIO Stale Data, BHI, Retbleed, GDS, Inception, SRSO 등)에 대해 커널은 소프트웨어와 마이크로코드 기반 완화책을 제공합니다.

ℹ️

Spectre/Meltdown 취약점의 공격 관점 분석은 커널 보안 취약점 사례 — Spectre 문서를 참조하세요.

주요 취약점 및 완화책

취약점CVE공격 벡터커널 완화책성능 영향
Meltdown (V3)CVE-2017-5754커널 메모리 읽기KPTI1~5%
Spectre V1CVE-2017-5753경계 검사 우회배열 인덱스 마스킹, LFENCE~1%
Spectre V2CVE-2017-5715분기 예측(Branch Prediction)기 주입Retpoline, IBRS/IBPB, eIBRS2~8%
MDSCVE-2018-12130마이크로아키텍처 버퍼VERW, SMT 비활성화~3%
TAACVE-2019-11135TSX 비동기 중단TSX 비활성화, VERWTSX 워크로드
MMIO Stale DataCVE-2022-21123MMIO 레지스터 잔여 데이터VERW, 마이크로코드최소
RetbleedCVE-2022-29900/01RET 명령 투기적 실행IBRS, untrained RET5~15%
GDSCVE-2023-34083Gather Data Sampling마이크로코드, VERW최소
BHICVE-2022-0001분기 히스토리 주입BHI_DIS_S, 소프트웨어 시퀀스~2%
SRSOCVE-2023-20569Speculative RAS OverflowSafe RET, IBPBAMD 특화

방어 메커니즘 흐름

CPU 투기적 실행 분기 예측, 캐시 사이드 채널 Spectre V1 경계 검사 우회 Spectre V2 분기 예측기 주입 Meltdown 권한 검사 우회 읽기 V1 완화 array_index_nospec() LFENCE 배리어 V2 완화 Retpoline (소프트웨어) IBRS/IBPB/eIBRS (하드웨어) Meltdown 완화 KPTI (페이지 테이블 분리) 최신 CPU는 하드웨어 수정 MDS/TAA/MMIO 완화 VERW (MD_CLEAR) — 마이크로아키텍처 버퍼 플러시 + SMT 비활성화 옵션 /sys/devices/system/cpu/vulnerabilities/* 에서 상태 확인

Retpoline 구현

Retpoline은 간접 분기(indirect branch)를 RET 명령으로 대체하여 분기 예측기(BTB)를 우회하는 소프트웨어 완화책입니다. 투기적 실행을 무한 루프(speculation trap)에 가두어 사이드 채널 유출을 방지합니다.

/* arch/x86/lib/retpoline.S — Retpoline 트램폴린 (개념 예시) */

/* 간접 호출: call *%rax → retpoline 대체 */
/* __x86_indirect_thunk_rax: */
/*   call    .Lspec_trap       ← RSB(Return Stack Buffer)에 .Lspec_trap 주소 push */
/* .Lcapture:                   */
/*   pause                     ← 투기적 실행이 여기서 무한 루프 */
/*   lfence                    */
/*   jmp     .Lcapture         */
/* .Lspec_trap:                 */
/*   mov     %rax, (%rsp)      ← 실제 대상 주소로 RSB 엔트리 교체 */
/*   ret                       ← 실제 대상으로 반환 (투기적 실행은 .Lcapture에 갇힘) */

/* IBRS/IBPB: 마이크로코드 기반 대안 */
/* IBRS (Indirect Branch Restricted Speculation): */
/*   — MSR 0x48에 IBRS 비트 설정 → 간접 분기 투기를 제한 */
/* IBPB (Indirect Branch Prediction Barrier): */
/*   — MSR 0x49에 IBPB 비트 설정 → 분기 예측 버퍼 플러시 */
/* eIBRS (Enhanced IBRS): 커널/사용자 전환 시 자동 적용 */

Spectre V1 방어: array_index_nospec()

/* include/linux/nospec.h — Spectre V1 경계 클램핑 */

/* 투기적 실행 시에도 배열 인덱스가 범위를 벗어나지 않도록 마스킹 */
#define array_index_nospec(index, size)    \
({                                              \
    typeof(index) _i = (index);             \
    typeof(size)  _s = (size);              \
    unsigned long _mask = array_index_mask_nospec(_i, _s); \
    ((typeof(_i)) (_i & _mask));            \
})

/* 사용 예: syscall 테이블 접근 */
if (nr < NR_syscalls) {
    nr = array_index_nospec(nr, NR_syscalls);
    return sys_call_table[nr];
}

취약점 상태 확인

# 현재 시스템의 CPU 취약점 완화 상태 확인
for f in /sys/devices/system/cpu/vulnerabilities/*; do
    echo "$(basename $f): $(cat $f)"
done

# 출력 예시:
# meltdown: Not affected (또는 Mitigation: PTI)
# spectre_v1: Mitigation: usercopy/swapgs barriers and __user pointer sanitization
# spectre_v2: Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; RSB filling; ...
# mds: Not affected
# tsx_async_abort: Not affected
# mmio_stale_data: Not affected
# retbleed: Not affected
# spec_rstack_overflow: Not affected

# 완화책 비활성화 (벤치마크 용도, 프로덕션 금지!)
# 커널 커맨드라인: mitigations=off
완화책과 성능: mitigations=auto(기본값)는 CPU에 적합한 완화책을 자동 선택합니다. mitigations=auto,nosmt는 SMT(하이퍼스레딩)까지 비활성화하여 MDS/TAA 방어를 강화합니다. 벤치마크 결과 없이 mitigations=off를 프로덕션에 적용하지 마세요.

W^X 정책 (Write XOR Execute)

W^X 정책은 메모리 페이지가 쓰기 가능(W)과 실행 가능(X)을 동시에 가질 수 없도록 강제하는 보안 원칙입니다. 공격자가 커널 메모리에 쉘코드를 주입한 후 실행하는 것을 원천적으로 차단합니다.

CONFIG 옵션보호 대상설명
CONFIG_STRICT_KERNEL_RWX커널 코드 영역커널 텍스트 섹션을 읽기전용+실행, 데이터 섹션을 읽기쓰기+비실행으로 분리
CONFIG_STRICT_MODULE_RWX커널 모듈(Kernel Module)모듈의 코드/데이터 영역에 동일한 W^X 강제
CONFIG_DEBUG_WX디버깅부팅 시 W+X 매핑 존재 여부를 스캔하여 경고 (디버그 빌드)
/* arch/x86/mm/init.c — 커널 페이지 권한 분리 */

/* mark_rodata_ro(): init 이후 텍스트/rodata 영역을 읽기 전용으로 설정 */
void mark_rodata_ro(void)
{
    /* .text  → PAGE_KERNEL_ROX  (읽기+실행, 쓰기 불가) */
    /* .rodata → PAGE_KERNEL_RO  (읽기 전용, 실행 불가) */
    /* .data  → PAGE_KERNEL      (읽기+쓰기, 실행 불가) */
    set_memory_ro((unsigned long)__start_rodata,
                  (unsigned long)__end_rodata);
    set_memory_nx((unsigned long)__start_rodata,
                  (unsigned long)__end_rodata);
}

/* free_initmem(): init 섹션 메모리 해제 후 권한 회수 */
/* 부팅 후 사용하지 않는 __init 코드/데이터를 해제하여 공격 표면 축소 */
# W^X 위반 검사 (CONFIG_DEBUG_WX=y 필요)
dmesg | grep "W+X"
# x86/mm: Checked W+X mappings: passed, no W+X pages found.

# 커널 섹션별 권한 확인
cat /sys/kernel/debug/kernel_page_tables | head -30
# (debugfs 접근 가능 시)

HARDENED_USERCOPY

HARDENED_USERCOPYcopy_from_user()/copy_to_user()에서 커널 메모리 객체의 경계를 검증하여, 잘못된 크기의 복사를 탐지하는 런타임 방어입니다. SLAB 객체 크기, 스택 범위, 텍스트/rodata 영역 검증을 수행합니다.

HARDENED_USERCOPY 검증 파이프라인 copy_from_user(kptr, uptr, len) 1. access_ok(uptr, len) 사용자 주소 범위 유효성 확인 2. __check_object_size(kptr, len, is_write) HARDENED_USERCOPY 핵심 검증 스택 범위 검증 현재 태스크 스택 내? IRQ/softirq 스택 포함 SLAB 객체 경계 검증 __check_heap_object() kptr ~ kptr+len이 하나의 SLUB 객체 안? 커널 영역 검증 .text/.rodata 영역 쓰기? → 즉시 차단 통과 → 복사 실패 → BUG() 실패 → BUG() CONFIG_HARDENED_USERCOPY — 스택 + SLAB + 영역 3중 검증으로 커널 메모리 유출/변조 방지
/* mm/usercopy.c — HARDENED_USERCOPY 핵심 로직 */

void __check_object_size(const void *ptr, unsigned long n,
                         bool to_user)
{
    /* 1단계: 0 크기 → 무시 */
    if (!n)
        return;

    /* 2단계: 커널 텍스트/rodata 영역 검증 */
    if (overlaps(ptr, n, _stext, _etext))
        usercopy_abort("kernel text", NULL, to_user, 0, n);

    /* 3단계: 스택 범위 검증 */
    if (check_stack_object(ptr, n) == BAD_STACK)
        usercopy_abort("process stack", NULL, to_user, 0, n);

    /* 4단계: SLAB 객체 경계 검증 */
    if (check_heap_object(ptr, n, to_user))
        return;  /* 검증 통과 */

    /* 5단계: vmalloc 영역 검증 */
    if (is_vmalloc_addr(ptr) && check_vmalloc_object(ptr, n))
        return;
}

/* SLAB 화이트리스트: usercopy 허용 오프셋/크기 */
/* kmem_cache_create_usercopy(name, size, align,
 *     flags, useroffset, usersize, ctor) */
/* useroffset~usersize 범위만 usercopy 허용 */

Usercopy 화이트리스트

커널 5.x부터 SLAB 캐시에 usercopy 화이트리스트 기능이 추가되었습니다. 구조체 내에서 사용자 공간으로 복사해도 안전한 영역만 명시적으로 지정합니다.

/* 화이트리스트 예시: pipe_buffer */
/* fs/pipe.c */
pipe_bufs = kmem_cache_create_usercopy(
    "pipe_buffer",
    sizeof(struct pipe_buffer),
    0,                            /* align */
    SLAB_ACCOUNT,
    offsetof(struct pipe_buffer, data_offset),
    sizeof((struct pipe_buffer){}.data_offset) +
    sizeof((struct pipe_buffer){}.data_len),
    NULL);
/* data_offset~data_len 범위만 copy_to_user 허용 */
/* 범위 밖(예: ops 포인터) 접근 시 BUG() */
HARDENED_USERCOPY 실전 탐지 사례:

CVE-2017-7895(nfsd)에서 발견된 버퍼 오버리드: NFSv2/v3 핸들러에서 copy_to_user()의 크기가 SLAB 객체 경계를 초과하여 커널 힙 메모리가 유출되었습니다. HARDENED_USERCOPY가 활성화된 커널에서는 이 복사가 BUG()로 즉시 차단됩니다.

SLAB/힙 하드닝

커널 힙(SLAB/SLUB) 할당자를 대상으로 한 공격을 완화하는 하드닝 옵션들입니다. Use-After-Free(UAF), 힙 스프레이, 프리리스트 포인터 오염 등의 공격을 어렵게 만듭니다.

/* 권장 SLAB 하드닝 설정 (.config) */

CONFIG_SLAB_FREELIST_RANDOM=y    /* 프리리스트 순서 무작위화 */
CONFIG_SLAB_FREELIST_HARDENED=y  /* 프리리스트 포인터 인코딩 */
CONFIG_RANDOM_KMALLOC_CACHES=y   /* 다중 랜덤 캐시 (6.6+) */

/* === 메모리 초기화 === */
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y /* 힙 할당 시 0 초기화 */
CONFIG_INIT_ON_FREE_DEFAULT_ON=y  /* 힙 해제 시 0 초기화 */
CONFIG 옵션방어 대상동작성능 영향
CONFIG_SLAB_FREELIST_RANDOM힙 스프레이SLUB freelist 순서를 랜덤화하여 할당 패턴 예측 불가무시 가능
CONFIG_SLAB_FREELIST_HARDENEDfreelist 포인터 오염freelist 포인터를 XOR 인코딩(주소 + random + pointer)무시 가능
CONFIG_RANDOM_KMALLOC_CACHEScross-cache 공격동일 크기 kmalloc에 대해 16개 랜덤 캐시 중 선택1~2%
CONFIG_INIT_ON_ALLOC_DEFAULT_ON정보 유출kmalloc/kzalloc 반환 시 0으로 초기화1~3%
CONFIG_INIT_ON_FREE_DEFAULT_ONUAF 데이터 유출kfree 시 0으로 초기화1~5%
/* mm/slub.c — SLAB_FREELIST_HARDENED 핵심 메커니즘 */

/* freelist 포인터 인코딩: ptr XOR random XOR address */
static inline void *freelist_ptr(
    const struct kmem_cache *s,
    void *ptr, unsigned long ptr_addr)
{
    /* 공격자가 freelist 포인터를 변조하면
     * 디코딩 시 비정상 주소 → 즉시 크래시 */
    return (void *)((unsigned long)ptr ^ s->random ^ ptr_addr);
}
KFENCE (Kernel Electric Fence): CONFIG_KFENCE=y는 프로덕션 환경에서 약 1%의 오버헤드로 UAF와 OOB(Out-Of-Bounds) 접근을 샘플링 방식으로 감지합니다. KASAN과 달리 프로덕션 커널에서 상시 활성화할 수 있어, 하드닝 목적뿐 아니라 버그 조기 발견에도 유용합니다.

KFENCE/KASAN/KMSAN 메모리 안전성

커널은 메모리 안전성을 강화하기 위해 다양한 동적 검사 도구를 제공합니다. KASAN은 개발/테스트 환경에서 포괄적 탐지를, KFENCE는 프로덕션 환경에서 낮은 오버헤드로 샘플링 탐지를, KMSAN은 초기화되지 않은 메모리 사용을 탐지합니다.

KFENCE 샘플링 아키텍처 및 도구 비교 KFENCE 전용 메모리 풀 가드 페이지 PROT_NONE 객체 A 32 bytes 가드 페이지 객체 B freed 가드 페이지 동작 원리: 1. 타이머(기본 100ms)마다 다음 kmalloc을 KFENCE 풀에 할당 2. 객체 양쪽에 가드 페이지(PROT_NONE) 배치 3. OOB 접근 → 가드 페이지 #PF → 즉시 탐지 + 상세 리포트 4. 해제 후 canary 패턴 기록 → UAF 쓰기 시 canary 훼손 탐지 5. 해제 후 PROT_NONE 설정 → UAF 읽기/쓰기 시 #PF 탐지 설정: CONFIG_KFENCE=y kfence.sample_interval=100 (ms, 0=비활성화) 풀 크기: 기본 255개 객체 (CONFIG_KFENCE_NUM_OBJECTS) 메모리 안전성 도구 비교 KASAN (Kernel Address Sanitizer) 오버헤드: 2~3배 (generic), ~50% (SW tags), ~3% (HW tags/MTE) 탐지: OOB, UAF, double-free, 100% 탐지율 용도: 개발/CI/퍼징 전용, shadow memory 1/8 비율 KFENCE (Kernel Electric Fence) 오버헤드: ~1% (프로덕션 안전) 탐지: OOB, UAF (샘플링 방식, 확률적) 용도: 프로덕션 상시 활성화, Google 서버 배포 검증 KMSAN (Kernel Memory Sanitizer) 오버헤드: 3~5배 탐지: 초기화되지 않은 메모리 사용 (info leak) 용도: 개발/CI 전용, 커널 6.1+ KCSAN (Kernel Concurrency Sanitizer) 오버헤드: 2~5배 탐지: 데이터 레이스 (concurrent access without synchronization)
# KFENCE 탐지 리포트 예시 (dmesg)
# ==================================================================
# BUG: KFENCE: out-of-bounds read in some_function+0x42/0x100
#
# Out-of-bounds read at 0xffff8880a1234560 (1 byte(s) past end of
# 64-byte region [0xffff8880a1234520, 0xffff8880a1234560))
#
# kfence-#42 [0xffff8880a1234520-0xffff8880a1234560, size=64,
#  cache=kmalloc-64] allocated by task 1234:
#   alloc_skb+0x40/0x80
#   ...
# ==================================================================

# KFENCE 상태 확인
cat /sys/kernel/debug/kfence/stats
# enabled: 1
# sample_interval: 100
# total_bugs: 0
# total_allocs: 12345

페이지 할당자(Page Allocator) 하드닝

SLAB 하드닝 외에도 버디 시스템(페이지 할당자) 수준에서의 하드닝 옵션이 있습니다. 페이지 할당 패턴 예측을 어렵게 하고, 해제된 페이지의 데이터를 제거합니다.

페이지 할당자 하드닝 계층 SHUFFLE_PAGE_ALLOCATOR Page 7 Page 2 Page 5 Page 1 Page 9 버디 시스템 프리 리스트 순서를 랜덤화 → 연속 할당 시 물리 주소 패턴 예측 불가 → heap spray + physmap 공격 완화 페이지 초기화/포이즈닝 해제 시 0 채움 INIT_ON_FREE_DEFAULT_ON 할당 시 0 채움 INIT_ON_ALLOC_DEFAULT_ON PAGE_POISONING: 해제 시 0xAA 패턴 기록 → 재사용 시 패턴 검증 (UAF 탐지) → INIT_ON_FREE가 더 안전 (0 채우기) DEBUG_PAGEALLOC 해제된 페이지를 언맵(PROT_NONE) 처리 → UAF 접근 시 즉시 #PF (가장 강력한 탐지) → 오버헤드 매우 높음 (디버깅 전용) → debug_pagealloc=on 커널 커맨드라인 프로덕션 금지: TLB flush 폭증으로 10배 이상 느려짐 PAGE_TABLE_CHECK (6.0+) 페이지 테이블 엔트리의 이중 매핑 탐지 → 동일 물리 페이지의 커널+유저 동시 매핑 차단 → 커널/유저 페이지 공유 공격 방어 → PTE/PMD/PUD 레벨에서 참조 카운트 검증 CONFIG_PAGE_TABLE_CHECK=y + page_table_check=on
CONFIG 옵션오버헤드탐지 대상환경
CONFIG_SHUFFLE_PAGE_ALLOCATOR~1%물리 주소(Physical Address) 예측 공격프로덕션
CONFIG_INIT_ON_ALLOC_DEFAULT_ON1~3%정보 유출프로덕션
CONFIG_INIT_ON_FREE_DEFAULT_ON1~5%UAF 데이터 유출프로덕션 (고보안)
CONFIG_PAGE_POISONING3~5%UAF (0xAA 패턴 검증)QA/스테이징
CONFIG_DEBUG_PAGEALLOC10배+UAF (#PF 즉시 탐지)개발 전용
CONFIG_PAGE_TABLE_CHECK~1%이중 매핑 공격프로덕션 (6.0+)

GCC 보안 플러그인

GCC 컴파일러 플러그인을 통해 추가적인 보안 강화를 적용할 수 있습니다. 이 옵션들은 "선택(Nice)" 범주에 해당하지만, 최대 보안이 요구되는 환경에서 효과적입니다.

CONFIG 옵션기능성능 영향설명
CONFIG_GCC_PLUGIN_RANDSTRUCT구조체 레이아웃 랜덤화~1%커널 구조체 멤버 순서를 빌드마다 랜덤화하여, 오프셋 기반 공격 차단
CONFIG_GCC_PLUGIN_LATENT_ENTROPY잠재 엔트로피 수집무시 가능컴파일 시 상수 연산에 랜덤 값 혼합, 커널 RNG 엔트로피 풀 보강
CONFIG_GCC_PLUGIN_STACKLEAK함수 반환 시 스택 클리어~1%함수 반환 시 사용한 스택 프레임(Stack Frame)을 포이즌 값으로 덮어써 정보 유출 방지
/* GCC_PLUGIN_RANDSTRUCT 효과 (개념) */

/* 원본 구조체 */
struct cred {
    atomic_t usage;
    kuid_t   uid, euid, suid, fsuid;
    kgid_t   gid, egid, sgid, fsgid;
    /* ... */
};

/* RANDSTRUCT 적용 후 (빌드 A) */
/* struct cred {
 *     kgid_t   fsgid;       ← 순서 랜덤화
 *     kuid_t   suid;
 *     atomic_t usage;
 *     kuid_t   fsuid;
 *     ... 
 * }; */

/* 공격자가 offsetof(struct cred, uid)를 하드코딩하면
 * 다른 빌드에서 오프셋이 달라져 공격 실패 */

/* GCC_PLUGIN_STACKLEAK 효과 */
/* 함수 에필로그에서 사용한 스택 영역을 STACKLEAK_POISON(0xBAAAAAAD)으로 덮음 */
/* → 이전 함수의 스택 데이터(커널 포인터 등)가 다음 함수에 노출되지 않음 */
Clang 호환성: GCC 플러그인은 GCC 전용입니다. Clang/LLVM으로 커널을 빌드하는 경우, RANDSTRUCT은 CONFIG_RANDSTRUCT=y로 Clang 네이티브 지원이 제공됩니다 (커널 6.1+). STACKLEAK은 Clang에서도 CONFIG_GCC_PLUGIN_STACKLEAK을 통해 지원됩니다.

스택/힙 초기화 전략

초기화되지 않은 변수는 정보 유출(info leak)의 주요 원인입니다. 커널은 컴파일러와 런타임 양쪽에서 자동 초기화를 지원하여 이 공격 벡터를 제거합니다.

초기화되지 않은 메모리 → 정보 유출 공격 경로와 방어 공격 시나리오: 초기화되지 않은 변수를 통한 커널 포인터 유출 1. 커널 함수에서 스택 변수 선언 (미초기화) 2. 이전 함수의 스택 프레임에 커널 포인터가 잔류 3. copy_to_user()로 사용자 공간에 유출 → KASLR 우회 스택 자동 초기화 CONFIG_INIT_STACK_ALL_ZERO -ftrivial-auto-init=zero (GCC 12+ / Clang 16+) 모든 지역 변수(auto)를 0으로 자동 초기화 컴파일러가 함수 프롤로그에 초기화 코드 삽입 오버헤드: ~1% (컴파일러 최적화로 불필요한 초기화 제거) CONFIG_GCC_PLUGIN_STACKLEAK 함수 반환 시 스택을 0xBAAAAAAD로 포이즈닝 STACKLEAK + INIT_STACK_ALL_ZERO 조합 권장: → 진입 시 0 초기화 + 반환 시 잔여 데이터 제거 = 이중 방어 힙 자동 초기화 CONFIG_INIT_ON_ALLOC_DEFAULT_ON kmalloc/kzalloc 반환 시 자동 0 채움 CONFIG_INIT_ON_FREE_DEFAULT_ON kfree 시 0 채움 (UAF 데이터 유출 방지) 런타임 제어: init_on_alloc=1 init_on_free=1 벤치마크: init_on_alloc 1~3%, init_on_free 1~5% init_on_alloc은 기존 kzalloc()과 중복 시 최적화 → 이미 kzalloc()인 코드에서는 추가 오버헤드 없음
/* INIT_STACK_ALL_ZERO 효과 — 컴파일러가 삽입하는 코드 */

/* 원본 코드 */
int some_ioctl(struct file *filp, unsigned long arg)
{
    struct my_info info;  /* 미초기화 */
    info.field1 = val1;
    /* info.field2 미설정 → 이전 스택 데이터 잔류! */
    copy_to_user((void __user *)arg, &info, sizeof(info));
}

/* INIT_STACK_ALL_ZERO 적용 후 (개념) */
int some_ioctl(struct file *filp, unsigned long arg)
{
    struct my_info info = {};  /* 컴파일러가 자동 삽입 */
    info.field1 = val1;
    /* info.field2 = 0 (안전) */
    copy_to_user((void __user *)arg, &info, sizeof(info));
}

ZERO_CALL_USED_REGS

함수 반환 직전에 호출 규약(Calling Convention)상 사용된 모든 레지스터를 0으로 클리어합니다. ROP/JOP 가젯이 레지스터에 남아있는 커널 포인터를 활용하는 것을 방지하고, 스택 이외의 레지스터 경로를 통한 정보 유출도 차단합니다.

/* CONFIG_ZERO_CALL_USED_REGS=y */
/* -fzero-call-used-regs=used-gpr (GCC 11+ / Clang 15+) */

/* 컴파일러가 함수 에필로그에 삽입하는 코드 (x86_64) */
/* 함수가 rax, rcx, rdx를 사용했다면: */
/*   xor %ecx, %ecx   ← rcx = 0 */
/*   xor %edx, %edx   ← rdx = 0 */
/*   ret               ← rax는 반환값이므로 유지 */

/* 효과: */
/* 1. ROP 가젯에서 레지스터 값 재사용 방지 */
/* 2. 레지스터에 남은 커널 포인터 유출 차단 */
/* 3. JIT 스프레이 공격에서 가젯 품질 저하 */
/*
 * 오버헤드: ~1% (레지스터 xor만 추가)
 * 보안 효과: 가젯 체인 구성 시 레지스터 초기화 단계 강제
 */
초기화 전략 조합 권장 (2026년 기준):
환경스택레지스터
프로덕션 (표준)INIT_STACK_ALL_ZEROINIT_ON_ALLOC-
프로덕션 (고보안)INIT_STACK_ALL_ZERO + STACKLEAKINIT_ON_ALLOC + INIT_ON_FREEZERO_CALL_USED_REGS
디버깅INIT_STACK_ALL_ZERO + STACKLEAKINIT_ON_ALLOC + KMSANZERO_CALL_USED_REGS

하드닝 CONFIG 옵션 요약

CONFIG 옵션기능오버헤드
CONFIG_RANDOMIZE_BASEKASLR무시 가능
CONFIG_MITIGATION_PAGE_TABLE_ISOLATIONKPTI (Meltdown)1~5%
CONFIG_MITIGATION_RETPOLINESpectre v2 완화~2%
CONFIG_STACKPROTECTOR_STRONG스택 canary<1%
CONFIG_VMAP_STACKGuard page 스택 오버플로 감지무시 가능
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 강제무시 가능

KASLR/KPTI/CFI 통합 방어

KASLR(주소 배치 랜덤화), KPTI(커널 페이지 테이블 격리), CFI(Control-Flow Integrity)는 각각 독립된 공격 벡터를 차단하지만, 세 기술이 동시에 활성화될 때 공격 표면이 급격히 축소됩니다. 하나의 방어층이 우회되어도 나머지 기술이 익스플로잇 체인을 차단하는 종심 방어(defense in depth) 구조를 형성합니다.

공격 차단 범위 비교

방어 기술차단 대상CONFIG 옵션한계
KASLR주소 예측 기반 ROP/JOPCONFIG_RANDOMIZE_BASE정보 유출(info leak) 시 우회 가능
KPTI사용자 공간에서 커널 메모리 읽기 (Meltdown)CONFIG_PAGE_TABLE_ISOLATIONTLB flush 오버헤드, 커널 내부 공격엔 무효
kCFI간접 호출 대상 변조 (JOP/COOP)CONFIG_CFI_CLANG직접 호출/데이터 전용 공격엔 무효
SMEP/SMAP커널이 사용자 코드 실행/데이터 접근CPU 기능 (CR4 비트)커널 공간(Kernel Space) 내 가젯엔 무효

상호 보완성

공격자가 ROP 체인을 구성하려면 (1) 커널 심볼 주소를 알아야 하고(KASLR이 차단), (2) 사용자 공간에서 커널 메모리를 읽으려면(KPTI가 차단), (3) 간접 호출 대상을 변조하려면(CFI가 차단) 모든 방어층을 동시에 우회해야 합니다.

공격자 (Exploit) 주소 예측 ROP 커널 메모리 읽기 간접 호출 변조 KASLR 주소 랜덤화 → 예측 불가 KPTI 페이지 테이블 분리 kCFI (Clang) 제어 흐름 무결성 ✕ 차단 ✕ 차단 ✕ 차단 SMEP/SMAP (하드웨어 보호) 커널 모드에서 사용자 공간 코드 실행/데이터 접근 차단 커널 코드 + 데이터 영역 W^X (CONFIG_STRICT_KERNEL_RWX) + 스택 보호 + FORTIFY_SOURCE 종심 방어 (Defense in Depth)

kCFI 검증 흐름

/* Clang kCFI가 생성하는 간접 호출 검증 코드 (개념 예시) */
/* 컴파일러가 각 함수 앞에 타입 해시를 삽입 */

/* 함수 정의 앞에 .cfi_type_hash 삽입 */
/* __cfi_my_callback:
 *   .word 0xDEAD1234   ← 함수 시그니처의 타입 해시 */
void my_callback(struct sk_buff *skb) { ... }

/* 간접 호출 시 컴파일러가 삽입하는 검증 코드 */
static void dispatch(void (*fn)(struct sk_buff *), struct sk_buff *skb)
{
    /* 컴파일러가 자동 삽입: fn[-1]의 해시 == 예상 해시 검증 */
    /* 불일치 시 → __cfi_check_fail() → BUG() */
    fn(skb);  /* kCFI 검증 후 호출 */
}
통합 활성화 권장: 프로덕션 커널에서는 CONFIG_RANDOMIZE_BASE=y + CONFIG_PAGE_TABLE_ISOLATION=y + CONFIG_CFI_CLANG=y + CONFIG_STRICT_KERNEL_RWX=y를 모두 활성화하세요. 개별 기술의 한계를 상호 보완하여 익스플로잇 체인 구성을 극도로 어렵게 만듭니다.

하드닝 성능 영향

옵션성능 영향보호 대상권장 환경
FORTIFY_SOURCE1% 미만버퍼 오버플로우모든 환경
STACKPROTECTOR_STRONG1~2%스택 오버플로(Stack Overflow)우모든 환경
INIT_STACK_ALL_ZERO1% 미만정보 유출모든 환경
HARDENED_USERCOPY1% 미만커널↔유저 복사 오버런모든 환경
CFI_CLANG1~3%제어 흐름 무결성프로덕션 (Clang 빌드)
KASAN2~3배 느림메모리 오류 전체테스트/개발 환경 전용
KPTI5~30% (시스콜 빈도 의존)Meltdown 완화Intel CPU (필수)
SLAB_FREELIST_HARDENED1% 미만힙 익스플로잇모든 환경
RANDOM_KMALLOC_CACHES1~2%힙 스프레이6.6+ 프로덕션
ℹ️

성능 측정 방법: 하드닝 옵션 적용 전후로 fio(I/O), wrk(HTTP), sysbench(CPU/메모리) 벤치마크를 수행하세요. KPTI의 경우 시스템 콜 빈도가 높은 워크로드(DB, 웹서버)에서 영향이 크고, 계산 중심 워크로드(HPC)에서는 거의 무시 가능합니다.

커널 하드닝 체크리스트

프로덕션 환경에서 커널 보안을 강화하기 위한 체계적인 체크리스트입니다. CONFIG 옵션을 필수(Must), 권장(Should), 선택(Nice)으로 분류하고, 성능 영향을 함께 표기합니다.

커널 하드닝 CONFIG 분류 필수 (Must) RANDOMIZE_BASE PAGE_TABLE_ISOLATION STACKPROTECTOR_STRONG FORTIFY_SOURCE STRICT_KERNEL_RWX STRICT_MODULE_RWX SECURITY (LSM 활성화) SECCOMP_FILTER HARDENED_USERCOPY RETPOLINE 성능 영향: 1~5% 모든 프로덕션 환경 적용 권장 (Should) INIT_ON_ALLOC_DEFAULT_ON SLAB_FREELIST_RANDOM SLAB_FREELIST_HARDENED RANDOMIZE_KSTACK_OFFSET CFI_CLANG SECURITY_LOCKDOWN_LSM IMA + EVM AUDIT + AUDITSYSCALL SECURITY_YAMA SECURITY_LANDLOCK 성능 영향: 1~3% 높은 보안 요구사항 환경 선택 (Nice) INIT_ON_FREE_DEFAULT_ON SHUFFLE_PAGE_ALLOCATOR GCC_PLUGIN_RANDSTRUCT GCC_PLUGIN_LATENT_ENTROPY GCC_PLUGIN_STACKLEAK INIT_STACK_ALL_ZERO ZERO_CALL_USED_REGS RANDOMIZE_MEMORY FG_KASLR (실험적) KFENCE 성능 영향: 3~10% 최대 보안 또는 테스트 환경 ← 낮은 오버헤드 ─── 보안 강도 / 성능 오버헤드 ─── 높은 오버헤드 →

CONFIG 옵션별 상세

분류CONFIG 옵션기능성능 영향비고
필수CONFIG_RANDOMIZE_BASEKASLR: 커널 주소 랜덤화무시 가능x86_64 3.14+, arm64 4.6+
CONFIG_PAGE_TABLE_ISOLATIONKPTI: Meltdown 방어1~5% (syscall 집중)PCID 지원 CPU는 영향 최소
CONFIG_STACKPROTECTOR_STRONG스택 카나리(Stack Canary) 검증~0.5%GCC 4.9+, Clang 지원
CONFIG_FORTIFY_SOURCE버퍼 크기 검증~0.5%memcpy/strcpy 등 래핑
CONFIG_STRICT_KERNEL_RWX커널 코드 W^X무시 가능코드 영역 읽기전용
CONFIG_VMAP_STACKGuard page 스택 오버플로 감지무시 가능x86_64 4.9+ 기본 활성
권장CONFIG_INIT_ON_ALLOC_DEFAULT_ON할당 시 0 초기화1~3%정보 유출 방지
CONFIG_SLAB_FREELIST_RANDOMSlab 할당 순서 랜덤화무시 가능heap spray 완화
CONFIG_CFI_CLANGkCFI 간접 호출 검증~1%Clang 16+ 필요
CONFIG_SECURITY_LOCKDOWN_LSMLockdown 커널 보호없음디버깅 도구 제한됨
CONFIG_RANDOMIZE_KSTACK_OFFSETsyscall 스택 오프셋 랜덤~1%커널 5.13+
선택CONFIG_INIT_ON_FREE_DEFAULT_ON해제 시 0 초기화1~5%UAF 정보 유출 방지
CONFIG_GCC_PLUGIN_RANDSTRUCT구조체 레이아웃 랜덤화~1%GCC 플러그인 필요
CONFIG_GCC_PLUGIN_STACKLEAK함수 반환 시 스택 클리어~1%정보 유출 방지
CONFIG_INIT_STACK_ALL_ZERO스택 변수 0 초기화1~3%GCC 12+ / Clang
CONFIG_KFENCEKernel Electric Fence~1%프로덕션 버그 탐지

환경별 권장 프로파일

환경필수권장선택mitigationslockdown
웹 서버전체전체INIT_ON_FREEautointegrity
컨테이너(Container) 호스트전체전체KFENCE, STACKLEAKautointegrity
금융/의료전체전체전체auto,nosmtconfidentiality
HPC/성능 우선전체선별비적용autonone
IoT/임베디드전체CFI, LOCKDOWN비적용autointegrity
개발/디버깅전체KFENCE만STACKLEAKautonone
하드닝 적용 원칙: (1) 필수 옵션은 예외 없이 활성화, (2) 권장 옵션은 성능 벤치마크 후 적용, (3) 선택 옵션은 보안 요구사항에 따라 판단합니다. 새로운 CONFIG 옵션을 활성화할 때는 반드시 CONFIG_KFENCE=y로 메모리 안전성 모니터링을 병행하세요. 하드닝 설정 변경 후에는 전체 워크로드 벤치마크(fio, wrk, sysbench 등)를 반드시 수행하세요.
배포판별 기본 설정: Ubuntu는 AppArmor + KASLR + KPTI + STACKPROTECTOR_STRONG이 기본, Fedora/RHEL은 SELinux + KASLR + KPTI가 기본, Android는 SELinux(enforcing) + kCFI + ShadowCallStack이 기본 활성화됩니다. 배포판 기본값을 기준으로 추가 하드닝을 적용하세요.

런타임 sysctl 체크리스트

커널 하드닝은 빌드 시점(CONFIG) 옵션뿐 아니라 런타임 sysctl 설정으로도 강화해야 합니다. 다음은 프로덕션 환경에서 반드시 점검해야 할 sysctl 항목입니다.

분류설정확인 명령권장값
커널 포인터kptr_restrictsysctl kernel.kptr_restrict2
dmesg 접근dmesg_restrictsysctl kernel.dmesg_restrict1
perf 접근perf_event_paranoidsysctl kernel.perf_event_paranoid3
BPF 접근unprivileged_bpf_disabledsysctl kernel.unprivileged_bpf_disabled1
userfaultfdunprivileged_userfaultfdsysctl vm.unprivileged_userfaultfd0
User NSmax_user_namespacessysctl user.max_user_namespaces0 (불필요 시)
SysRqsysrqsysctl kernel.sysrq0 또는 176
코어 덤프(Core Dump)core_patternsysctl kernel.core_pattern제한된 경로
ASLRrandomize_va_spacesysctl kernel.randomize_va_space2
ptraceyama.ptrace_scopesysctl kernel.yama.ptrace_scope1 이상
# 커널 하드닝 상태 종합 점검 스크립트

echo "=== 커널 하드닝 상태 점검 ==="

# sysctl 보안 설정 확인
for key in \
    kernel.kptr_restrict \
    kernel.dmesg_restrict \
    kernel.perf_event_paranoid \
    kernel.unprivileged_bpf_disabled \
    kernel.yama.ptrace_scope \
    kernel.randomize_va_space \
    vm.unprivileged_userfaultfd; do
    val=$(sysctl -n $key 2>/dev/null)
    echo "$key = ${val:-NOT_FOUND}"
done

# CONFIG_ 옵션 확인
echo "=== 커널 빌드 보안 옵션 ==="
config=/boot/config-$(uname -r)
if [ -f "$config" ]; then
    for opt in \
        FORTIFY_SOURCE HARDENED_USERCOPY \
        STACKPROTECTOR_STRONG CFI_CLANG \
        INIT_STACK_ALL_ZERO SLAB_FREELIST_HARDENED \
        RANDOMIZE_BASE PAGE_TABLE_ISOLATION \
        SECURITY_LOCKDOWN_LSM KFENCE; do
        result=$(grep "CONFIG_${opt}=" "$config" 2>/dev/null)
        echo "  ${opt}: ${result:-NOT_SET}"
    done
fi

# 하드웨어 취약점 상태
echo "=== 하드웨어 취약점 완화 ==="
for f in /sys/devices/system/cpu/vulnerabilities/*; do
    echo "  $(basename $f): $(cat $f)"
done
자동화 권장: 위 점검 스크립트를 cron이나 systemd timer로 주기적으로 실행하고, 보안 정책 위반(KASLR 비활성화, Lockdown 해제 등)이 감지되면 알림을 보내는 것이 좋습니다. Ansible/Puppet 등 구성 관리 도구와 연동하면 클러스터 전체의 보안 상태를 일관되게 관리할 수 있습니다.

실전 익스플로잇 체인과 하드닝 효과

실제 커널 익스플로잇은 단일 취약점이 아닌 여러 단계의 체인으로 구성됩니다. 각 하드닝 옵션이 체인의 어느 단계를 차단하는지 이해하면 방어 우선순위(Priority)를 정할 수 있습니다.

전형적 커널 익스플로잇 체인 5단계와 하드닝 차단점 1. 취약점 트리거 UAF, 힙 오버플로, 정수 오버플로 2. 힙 레이아웃 조작 힙 스프레이, cross-cache 공격 3. 정보 유출 커널 주소 유출 (KASLR 우회) 4. 제어 흐름 탈취 ROP/JOP 체인, 함수 포인터 변조 5. 권한 상승 commit_creds() 차단 기술 FORTIFY_SOURCE HARDENED_USERCOPY Stack Protector KFENCE / KASAN 차단 기술 SLAB 랜덤화 RANDOM_KMALLOC Freelist 하드닝 INIT_ON_FREE 차단 기술 KASLR / FKASLR kptr_restrict=2 INIT_STACK_ALL_ZERO dmesg_restrict=1 차단 기술 kCFI / FineIBT SMEP / SMAP Shadow Call Stack W^X 정책 실제 CVE 익스플로잇 체인과 하드닝 효과 CVE-2021-4154 (cgroup UAF) 1. cgroup1 release_agent UAF 트리거 2. 힙 스프레이로 msg_msg 오버랩 3. 임의 읽기로 KASLR 우회 4. modprobe_path 덮어쓰기 → root 차단: RANDOM_KMALLOC(2단계), kCFI(4단계) CVE-2022-0185 (fsconfig 힙 오버플로) 1. fsconfig() 정수 언더플로 → 힙 오버플로 2. msg_msg 구조체 오버랩 3. 커널 주소 유출 4. ROP 체인으로 commit_creds() 차단: FORTIFY_SOURCE(1단계), KASLR+INIT_STACK(3단계) CVE-2023-2008 (netfilter UAF) 1. nft_set 요소 UAF 트리거 2. pipe_buffer 구조체로 재할당 3. pipe_buffer→ops 함수 포인터 변조 4. splice() 호출 시 ROP 트리거 차단: SLAB_FREELIST_HARDENED(2), kCFI(3-4) CVE-2022-0847 (Dirty Pipe) 1. splice()의 pipe 플래그 초기화 누락 2. PIPE_BUF_FLAG_CAN_MERGE 설정 3. 임의 파일 쓰기 (정보유출 불필요) 4. /etc/passwd 수정 → root 우회: 하드닝 대부분 무효 (논리 버그)
Dirty Pipe의 교훈:

CVE-2022-0847(Dirty Pipe)은 메모리 손상이 아닌 논리 버그(플래그 미초기화)로 발생했습니다. KASLR, CFI, SMEP/SMAP, 힙 하드닝 등 대부분의 하드닝이 무효했습니다. 이는 하드닝이 만능이 아님을 보여주며, 코드 감사(audit), 퍼징(fuzzing), 정적 분석의 중요성을 강조합니다. INIT_ON_ALLOC이 활성화되었다면 플래그가 0으로 초기화되어 이 취약점이 차단되었을 가능성이 있습니다.

KSPP (Kernel Self Protection Project)

KSPP(Kernel Self Protection Project)는 커널 자체의 보안 강화를 목표로 하는 Linux Foundation 프로젝트입니다. 익스플로잇 완화 기술의 커널 메인라인 통합을 추진하며, 하드닝 옵션의 개발 로드맵을 제시합니다.

KSPP 하드닝 로드맵 (2026년 기준) v4.x (2016~) v5.x (2019~) v6.x (2022~) v7.x+ (계획) 완료 (Mainline) KASLR (v3.14+) KPTI (v4.15+) Retpoline (v4.15+) STACKPROTECTOR_STRONG (v3.14+) FORTIFY_SOURCE (v4.13+) HARDENED_USERCOPY (v4.8+) STRICT_KERNEL_RWX (v4.11+) SLAB_FREELIST_HARDENED (v4.14+) Shadow Call Stack (v5.8+, arm64) kCFI (v6.1+) FineIBT (v6.2+) INIT_STACK_ALL_ZERO (v5.9+) RANDOM_KMALLOC_CACHES (v6.6+) KFENCE (v5.12+) KMSAN (v6.1+) RANDSTRUCT (v4.13+) ZERO_CALL_USED_REGS (v5.15+) PAGE_TABLE_CHECK (v6.0+) 진행 중 / 실험적 FKASLR (함수 단위 랜덤화) 구조체 바운드 검사 강화 가변 길이 배열(VLA) 완전 제거 함수 포인터 const 강제 signed integer overflow 검사 Clang bounds sanitizer CONFIG_LIST_HARDENED exec-only 메모리 매핑 cross-cache 격리 강화 tagged address (MTE/HWASAN) 계획 / 장기 목표 완전한 타입-안전 커널 (Rust) Hardware CFI 전면 적용 MTE 커널 전역 활성화 CHERI 능력 기반 포인터 분리 커널 (마이크로커널화) 형식적 검증 (seL4 수준) 모든 implicit cast 제거 커널 내 Rust 드라이버 비율 확대 완전한 backward-edge HW CFI
# KSPP 권장 옵션 자동 검증: kconfig-hardened-check 도구
pip install kconfig-hardened-check

# 현재 커널 설정 검사
kconfig-hardened-check -c /boot/config-$(uname -r)

# 출력 예시:
# CONFIG_RANDOMIZE_BASE          | y | kspp | OK
# CONFIG_STACKPROTECTOR_STRONG   | y | kspp | OK
# CONFIG_CFI_CLANG               | y | kspp | FAIL: not set
# CONFIG_INIT_STACK_ALL_ZERO     | y | kspp | FAIL: not set
# ...

# sysctl 설정도 함께 검사
kconfig-hardened-check -c /boot/config-$(uname -r) -l /proc/cmdline

자동 하드닝 검증 도구

수십 개의 CONFIG 옵션과 sysctl 설정을 수동으로 점검하는 것은 비효율적입니다. 자동화 도구를 활용하여 커널 하드닝 상태를 체계적으로 검증할 수 있습니다.

도구검사 대상특징사용법
kconfig-hardened-checkCONFIG, sysctl, 커널 커맨드라인KSPP/CLIP OS/GrapheneOS 권장 설정 기반pip install kconfig-hardened-check
lynis시스템 전체 보안 감사커널 설정 + sysctl + 파일 퍼미션 + 서비스apt install lynis && lynis audit system
checksec바이너리/커널 보안 기능RELRO, PIE, NX, ASLR, Stack Canarychecksec --kernel
spectre-meltdown-checkerCPU 취약점 완화 상태Spectre V1/V2, Meltdown, MDS, TAA 등spectre-meltdown-checker.sh
Ansible hardening rolessysctl + 서비스 + 설정CIS Benchmark, DISA STIG 준수ansible-galaxy install dev-sec.os-hardening
# kconfig-hardened-check: 결과를 JSON으로 저장하여 CI 연동
kconfig-hardened-check -c /boot/config-$(uname -r) -m json > hardening-report.json

# spectre-meltdown-checker: CPU 취약점 종합 검사
curl -L https://meltdown.ovh -o spectre-meltdown-checker.sh
chmod +x spectre-meltdown-checker.sh
sudo ./spectre-meltdown-checker.sh

# checksec: 커널 보안 기능 상태
checksec --kernel
# 출력:
# GCC stack protector support:            Enabled
# Strict user copy checks:                Enabled
# Enforce read-only kernel data:          Enabled
# Restrict /dev/mem access:               Enabled
# Restrict /dev/kmem access:              Enabled

# Ansible dev-sec.os-hardening 적용
ansible-playbook -i inventory hardening.yml
# sysctl.conf, modprobe.d, kernel 파라미터 일괄 강화
CI/CD 통합: kconfig-hardened-check를 커널 빌드 파이프라인(Pipeline)에 통합하면, 새로운 커널 설정이 보안 기준을 충족하는지 자동 검증할 수 있습니다. JSON 출력을 파싱하여 FAIL 항목이 있으면 빌드를 실패 처리하는 게이트를 구성하세요.

배포판별 하드닝 비교

주요 리눅스 배포판은 커널 하드닝 기본 설정이 다릅니다. 배포판의 기본 하드닝 수준을 이해하고, 부족한 부분을 추가로 강화하는 전략이 필요합니다.

배포판별 커널 하드닝 기본 설정 비교 (2026년 기준) Ubuntu 24.04 Fedora 41 RHEL 9 Debian 13 Android 15 GrapheneOS KASLR ON ON ON ON ON ON KPTI ON ON ON ON ON ON kCFI OFF OFF OFF OFF ON ON Shadow CS N/A N/A N/A N/A ON ON INIT_ON_ALLOC ON ON OFF OFF ON ON SLAB_HARDENED ON ON ON ON ON ON Lockdown integrity integrity integrity none dm-verity confidential LSM AppArmor SELinux SELinux AppArmor SELinux SELinux 컴파일러 GCC GCC GCC GCC Clang Clang 활성화 부분/기본 비활성화 주요 차이점: Android/GrapheneOS: Clang 빌드 → kCFI + Shadow Call Stack 기본 활성화 (데스크톱 배포판 대비 우위) RHEL 9: 안정성 우선으로 INIT_ON_ALLOC 비활성화 (성능 영향 고려) GrapheneOS: lockdown=confidentiality + hardened_malloc + exec-only 등 최고 수준 하드닝

컨테이너 환경 커널 하드닝

컨테이너(Docker, Kubernetes)는 호스트 커널을 공유하므로, 컨테이너 탈출(container escape)은 곧 호스트 커널 익스플로잇을 의미합니다. 컨테이너 환경에서는 공격 표면을 축소하는 추가 하드닝이 필수입니다.

컨테이너 환경 방어 심층 (Host Kernel Hardening) 공격자: 컨테이너 내부 (악성 워크로드) 컨테이너 탈출 시도: 커널 취약점 익스플로잇, 마운트 네임스페이스 탈출 계층 1: 시스템 콜 필터링 Seccomp-BPF (기본 ~50개 syscall 차단) | AppArmor/SELinux 프로파일 | Capabilities 제한 (CAP_SYS_ADMIN 제거) 계층 2: 네임스페이스/cgroup 격리 User Namespace (max_user_namespaces=0) | PID/Net/Mount NS | cgroup v2 리소스 제한 | read-only rootfs 계층 3: 커널 하드닝 (CONFIG) KASLR | kCFI | SLAB_FREELIST_HARDENED | RANDOM_KMALLOC | INIT_ON_ALLOC | HARDENED_USERCOPY | KFENCE 계층 4: 하드웨어 보호 SMEP/SMAP | Intel CET (IBT + Shadow Stack) | ARM64 MTE/PAN/GCS | IOMMU (DMA 격리) 컨테이너 호스트 필수 sysctl unprivileged_bpf_disabled=1 | unprivileged_userfaultfd=0 | kptr_restrict=2 | perf_event_paranoid=3 yama.ptrace_scope=2 | max_user_namespaces=0 (불필요 시) | dmesg_restrict=1
# 컨테이너 호스트 하드닝 sysctl 설정 (/etc/sysctl.d/99-container-hardening.conf)

# 비루트 BPF 차단 (컨테이너 내부에서 BPF 사용 방지)
kernel.unprivileged_bpf_disabled = 1

# userfaultfd 차단 (익스플로잇 레이스 윈도우 확대 방지)
vm.unprivileged_userfaultfd = 0

# 커널 포인터 완전 은닉
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1

# perf 이벤트 접근 차단 (사이드 채널 방지)
kernel.perf_event_paranoid = 3

# ptrace 제한 (디버거 연결 방지)
kernel.yama.ptrace_scope = 2

# User Namespace 비활성화 (필요 없는 경우)
# user.max_user_namespaces = 0
# 주의: Podman rootless 모드는 User NS가 필요

# SysRq 비활성화
kernel.sysrq = 0
ℹ️

gVisor/Kata Containers: 하드닝만으로 컨테이너 탈출을 완전히 방지할 수 없습니다. gVisor(사용자 공간 커널)는 호스트 커널 시스템 콜을 ~60개로 제한하고, Kata Containers는 각 컨테이너를 경량 VM에서 실행하여 커널 공격 표면을 근본적으로 축소합니다. 높은 보안이 요구되는 환경에서는 이러한 샌드박스(Sandbox) 런타임과 커널 하드닝을 병행하세요.

ARM64 아키텍처별 하드닝

ARM64는 x86_64와 다른 고유한 하드웨어 보안 기능을 제공합니다. 서버(Neoverse), 모바일(Cortex-A), IoT(Cortex-M) 환경에서 ARM64 커널 하드닝은 이러한 하드웨어 기능을 최대한 활용합니다.

ARM64 하드웨어 보안 기능 → 커널 하드닝 매핑 ARMv8.0 (기본) PXN (Page eXecute Never) → SMEP 동등 WXN (Write XOR eXecute) → W^X 하드웨어 강제 KASLR 지원 (v4.6+) SW TTBR0 PAN → 소프트웨어 SMAP 에뮬 ARMv8.1~8.3 PAN (Privileged Access Never) → HW SMAP 동등 (v8.1) VHE (Virtual Host Extension) → KVM 성능/보안 (v8.1) PAC (Pointer Authentication) → 포인터 서명 (v8.3) CONFIG_ARM64_PTR_AUTH BTI (Branch Target Ident.) → Forward-edge CFI (v8.5) CONFIG_ARM64_BTI ARMv8.5~9.x MTE (Memory Tagging) → HW KASAN (v8.5) CONFIG_KASAN_HW_TAGS GCS (Guarded Control Stack) → HW Shadow Stack (v9.4) CONFIG_ARM64_GCS RME (Realm Management) → CCA 기밀 컴퓨팅 (v9.2) THE (Translation Hardening) → 페이지 테이블 보호 (v9.x) ARM64 필수 CONFIG RANDOMIZE_BASE ARM64_PAN ARM64_PTR_AUTH ARM64_BTI SHADOW_CALL_STACK CFI_CLANG STACKPROTECTOR_STRONG FORTIFY_SOURCE KASAN_HW_TAGS (MTE) ARM64_GCS (v6.13+) ooh x86 대비 장점: PAC: 포인터 위조 탐지 BTI: HW forward CFI MTE: HW 메모리 태깅 GCS: HW shadow stack → 하드웨어 보안 기능이 x86보다 포괄적

PAC (Pointer Authentication Code) 상세

/* arch/arm64/include/asm/pointer_auth.h — PAC 커널 지원 */

/* PAC: 포인터에 암호학적 서명을 삽입하여 변조 탐지 */
/*
 * 포인터의 상위 비트(TBI 영역)에 QARMA 암호 기반 MAC 삽입
 * 5개 키: APIAKey, APIBKey, APDAKey, APDBKey, APGAKey
 *
 * 명령어:
 *   PACIA x30, sp   — LR에 서명 (함수 진입)
 *   AUTIA x30, sp   — LR 서명 검증 (함수 반환)
 *   RETAA           — 인증 후 반환 (AUTIA + RET)
 *
 * 효과: ROP 공격 시 리턴 주소 변조 → PAC 검증 실패 → fault
 * 커널에서는 per-task 키 + per-address-space 키 사용
 */

/* CONFIG_ARM64_PTR_AUTH_KERNEL=y */
/* 컴파일: -mbranch-protection=pac-ret+bti */
/* pac-ret: 리턴 주소 PAC 서명 (backward-edge) */
/* bti: BTI 명령 삽입 (forward-edge) */
ℹ️

ARM64 vs x86_64 보안 기능 비교: ARM64는 PAC(backward-edge)+BTI(forward-edge)+MTE(메모리 안전성)+GCS(하드웨어 shadow stack)를 하드웨어로 지원하여, x86_64의 CET(IBT+Shadow Stack)보다 포괄적인 보호를 제공합니다. 특히 MTE는 KASAN의 하드웨어 대체로, 프로덕션 환경에서 상시 활성화할 수 있는 유일한 메모리 안전성 도구입니다.

하드닝 우회 사례와 대응 진화

하드닝 기술은 공격자와의 군비 경쟁입니다. 새로운 방어가 도입될 때마다 우회 기법이 발견되고, 이에 대한 강화가 반복됩니다. 주요 우회 사례와 그에 따른 대응 진화를 이해하면 하드닝의 현재 위치와 한계를 파악할 수 있습니다.

커널 하드닝 공격-방어 군비 경쟁 진화 시간 → 공격: ret2usr (2010~) 커널 함수 포인터를 유저 공간 코드로 변조 → Ring 0에서 유저 공간 쉘코드 실행 방어: SMEP (2012) + SMAP (2014) 하드웨어가 커널→유저 코드 실행/데이터 접근 차단 → ret2usr 공격 원천 봉쇄 공격: 커널 공간 ROP (2013~) SMEP 우회: 커널 코드의 가젯만으로 ROP 체인 구성 → commit_creds(prepare_kernel_cred(0)) 방어: KASLR (2014) + CR4 핀닝 (2015) 주소 랜덤화로 가젯 위치 예측 차단 → CR4 핀닝으로 SMEP 비트 클리어 차단 공격: 정보 유출 → KASLR 우회 (2016~) 미초기화 변수, /proc/kallsyms, dmesg에서 포인터 유출 → KASLR 슬라이드 역산 → ROP 체인 구성 방어: kptr_restrict + INIT_STACK (2018~) 포인터 마스킹, 자동 초기화, HARDENED_USERCOPY → 정보 유출 경로 대폭 축소 공격: JOP/COOP (함수 포인터 변조, 2019~) file_operations 등 커널 구조체 함수 포인터 변조 → 기존 함수를 체인하여 임의 동작 수행 (Data-only) 방어: kCFI (2022) + FineIBT (2023) 간접 호출 대상 타입 해시 검증 (SW+HW) → 함수 포인터 변조 시 타입 불일치로 차단 공격: 힙 레이아웃 조작 (2021~) Cross-cache UAF, msg_msg 구조체 악용 → 예측 가능한 SLAB 할당 패턴 이용 방어: RANDOM_KMALLOC (2023) + KFENCE 16개 랜덤 캐시, freelist 하드닝, 프로덕션 UAF 탐지 → 힙 레이아웃 예측 극도로 어려움 현재: Data-only 공격 + 논리 버그 → Rust 커널 코드, 형식적 검증으로 대응 중
세대공격 기법필요 조건방어 기술현재 상태
1세대ret2usr임의 코드 실행SMEP/SMAP완전 차단
2세대커널 ROP가젯 주소KASLR + kCFI대부분 차단
3세대정보유출→ROPinfo leakINIT_STACK/ALLOC, kptr_restrict경로 축소
4세대JOP/COOP함수 포인터 제어kCFI/FineIBT대부분 차단
5세대Data-only데이터 변조만MTE, RANDSTRUCT, 코드 감사일부 탐지
6세대논리 버그설계 결함Rust, 형식적 검증, 퍼징연구 중
하드닝의 한계와 미래:

현재 하드닝은 메모리 손상(memory corruption) 기반 공격에 매우 효과적이지만, 논리 버그(Dirty Pipe), Data-only 공격(modprobe_path 덮어쓰기), 사이드 채널(Spectre 변종)에는 근본적 한계가 있습니다. 장기적으로 Rust 커널 코드 확대, CHERI 능력 기반 포인터, 형식적 검증(formal verification)이 이러한 한계를 극복할 것으로 기대됩니다.

ℹ️

런타임 무결성 검증: 컴파일 타임 하드닝 외에 런타임에서 커널 코드/모듈 텍스트 해시와 프로세스(Process) 자격증명(cred)을 주기적으로 검증하는 도구로 LKRG (Linux Kernel Runtime Guard)가 있습니다. IDT/GDT/MSR 변조 탐지, SELinux 우회 탐지 등 하드닝 계층을 보완합니다.

커널 하드닝과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.

외부 참고 자료