Ryotta's Linux 7.0 MM

메모리 관리 서브시스템 완전 분석

25. Memory Sealing (mseal)

개요 (Overview)

mseal()은 Linux 커널의 시스템 호출로, 사용자 공간에서 가상 메모리 영역(VMA)의 메타데이터를 변경할 수 없도록 "봉인(sealing)"하는 보안 기능을 제공합니다. 메모리 봉인은 RWX(Read/Write/Execute) 비트 외에 매핑 자체를 보호하여, 손상된 포인터가 메모리 관리 시스템을 악용하는 것을 방지합니다. 이는 XNU 커널의 VM_FLAGS_PERMANENT이나 OpenBSD의 mimmutable과 유사한 기능입니다.

Linux 7.0에서 mseal()은 64비트 아키텍처에서만 지원되며(32비트는 VM_SEALEDVM_NONE으로 정의), CONFIG_64BIT이 활성화된 시스템에서만 동작합니다. 봉인된 매핑은 프로세스가 종료되거나 exec 시스템 호출이 발생할 때까지 해제될 수 없으며, 한번 봉인된 메모리는 munseal 기능이 존재하지 않습니다.

소스 파일 경로

mm/mseal.c                           # 핵심 구현 (191줄)
mm/vma.h                             # vma_is_sealed() 헬퍼
mm/vma.c                             # munmap 경로에서 봉인 검사
mm/mremap.c                          # mremap 차단 검사
mm/mprotect.c                        # mprotect 차단 검사
mm/madvise.c                         # madvise 차단 검사
fs/binfmt_elf.c                      # ELF 로더에서 page 0 자동 봉인
include/linux/mm.h                   # VM_SEALED 플래그 정의
init/Kconfig                         # CONFIG_MSEAL_SYSTEM_MAPPINGS
Documentation/userspace-api/mseal.rst  # 공식 문서

빠른 점검 명령

# 1. mseal 시스템 호출 지원 여부 확인
grep -r "CONFIG_64BIT" /boot/config-$(uname -r)

# 2. do_mseal 심볼 확인
grep -w "do_mseal" /proc/kallsyms 2>/dev/null || echo "do_mseal 심볼 미노출"

# 3. 봉인 관련 커널 모듈/설정 확인
zgrep "MSEAL" /proc/config.gz 2>/dev/null || grep "MSEAL" /boot/config-$(uname -r) 2>/dev/null

# 4. 시스템 매핑 봉인 활성화 확인
cat /proc/sys/vm/mmap_min_addr  # 최소 주소 확인
grep -n "vdso\|vvar" /proc/self/maps  # 시스템 매핑 확인

# 5. mseal 관련 커널 로그 확인
dmesg | grep -i "seal\|mseal" 2>/dev/null

# 6. seccomp에서 mseal 호출 허용 여부 확인
grep -i seccomp /proc/self/status

# 7. VMA 개수 제한 확인 (봉인 시 VMA 분할 가능성)
cat /proc/sys/vm/max_map_count

# 8. 특정 프로세스의 봉인된 매핑 확인 (VmFlags에 'sl' 표시)
grep -A 5 "VmFlags:.*sl" /proc/<pid>/smaps

핵심 자료구조

mm_struct / vm_area_struct

mseal()은 현재 작업의 주소 공간(struct mm_struct)과 그 안의 VMA(struct vm_area_struct)를 함께 다룹니다. mm_struct는 잠금과 범위 검색의 기준이 되고, vm_area_structvm_start, vm_end, vm_flags를 기준으로 봉인 여부를 바꿉니다.

/* mm/mseal.c:55-65 */
static int mseal_apply(struct mm_struct *mm,
		unsigned long start, unsigned long end)
{
	struct vm_area_struct *vma, *prev;
	VMA_ITERATOR(vmi, mm, start);

	vma = vma_iter_load(&vmi);
	prev = vma_prev(&vmi);
	if (start > vma->vm_start)
		prev = vma;
  • struct mm_struct: 현재 프로세스의 주소 공간 전체를 대표합니다. mmap_write_lock_killable(mm)range_contains_unmapped(mm, ...)의 기준입니다.
  • struct vm_area_struct: 봉인 대상 구간입니다. vma_modify_flags()가 이 구조체의 vm_flags를 갱신합니다.
  • VM_SEALED 플래그

    VM_SEALEDvm_area_struct.vm_flags에 설정되는 비트 플래그로, 해당 VMA가 봉인되었음을 나타냅니다.

    /* include/linux/mm.h:500-506 */
    #ifdef CONFIG_64BIT
    #define VM_SEALED		INIT_VM_FLAG(SEALED)   /* 64비트에서만 유효 */
    #else
    #define VM_SEALED		VM_NONE                /* 32비트에서는 비활성 */
    #endif
    /* include/linux/mm.h:536-540 - 시스템 매핑 봉인 */
    #ifdef CONFIG_MSEAL_SYSTEM_MAPPINGS
    #define VM_SEALED_SYSMAP	VM_SEALED  /* 시스템 매핑 자동 봉인 */
    #else
    #define VM_SEALED_SYSMAP	VM_NONE   /* 시스템 매핑 봉인 비활성 */
    #endif
  • /proc//smapsVmFlags에는 sl로 표시됩니다.
  • vma_is_sealed() 헬퍼 함수

    VMA가 봉인되었는지 확인하는 인라인 함수입니다.

    /* mm/vma.h:661-671 */
    #ifdef CONFIG_64BIT
    static inline bool vma_is_sealed(struct vm_area_struct *vma)
    {
    	return (vma->vm_flags & VM_SEALED);  /* VM_SEALED 비트 검사 */
    }
    #else
    static inline bool vma_is_sealed(struct vm_area_struct *vma)
    {
    	return false;  /* 32비트에서는 항상 false */
    }
    #endif

    range_contains_unmapped() - VMA 홀 검사

    mseal() 호출 시 시작/끝 주소 사이에 매핑되지 않은 영역(VMA hole)이 있는지 확인합니다.

    /* mm/mseal.c:38-53 */
    static bool range_contains_unmapped(struct mm_struct *mm,
    		unsigned long start, unsigned long end)
    {
    	struct vm_area_struct *vma;
    	unsigned long prev_end = start;
    	VMA_ITERATOR(vmi, current->mm, start);
    
    	for_each_vma_range(vmi, vma, end) {
    		if (vma->vm_start > prev_end)
    			return true;  /* 이전 끝과 현재 시작 사이에 홀 존재 */
    		prev_end = vma->vm_end;
    	}
    
    	return prev_end < end;  /* 마지막 VMA가 end보다 먼저 끝나는 경우 */
    }

    mseal_apply() - 봉인 적용

    실제로 VMA에 VM_SEALED 플래그를 설정하는 함수입니다. VMA 분할/병합이 필요할 수 있습니다.

    /* mm/mseal.c:55-85 */
    static int mseal_apply(struct mm_struct *mm,
    		unsigned long start, unsigned long end)
    {
    	struct vm_area_struct *vma, *prev;
    	VMA_ITERATOR(vmi, mm, start);
    
    	vma = vma_iter_load(&vmi);
    	prev = vma_prev(&vmi);
    	if (start > vma->vm_start)
    		prev = vma;
    
    	for_each_vma_range(vmi, vma, end) {
    		const unsigned long curr_start = MAX(vma->vm_start, start);
    		const unsigned long curr_end = MIN(vma->vm_end, end);
    
    		if (!(vma->vm_flags & VM_SEALED)) {
    			vm_flags_t vm_flags = vma->vm_flags | VM_SEALED;
    			/* VMA 수정 (분할/병합 필요 시) */
    			vma = vma_modify_flags(&vmi, prev, vma, curr_start,
    					       curr_end, &vm_flags);
    			if (IS_ERR(vma))
    				return PTR_ERR(vma);
    			vm_flags_set(vma, VM_SEALED);
    		}
    		prev = vma;
    	}
    	return 0;
    }

    핵심 함수

    1. do_mseal() - 메인 진입점

    /* mm/mseal.c:139-185 */
    int do_mseal(unsigned long start, size_t len_in, unsigned long flags)
  • 역할: mseal() 시스템 호출의 메인 로직. 입력 검증 후 봉인 적용
  • 분기 로직:
  • - flags != 0-EINVAL (예약된 플래그)

    - start가 페이지 정렬되지 않음 → -EINVAL

    - len_in이 0으로 라운딩됨 → -EINVAL

    - end < start (오버플로우) → -EINVAL

    - end == start → 성공 (아무 작업 없음)

    - range_contains_unmapped()가 true → -ENOMEM

    - mseal_apply() 실패 → 해당 에러 코드 반환

    2. SYSCALL_DEFINE3(mseal) - 시스템 호출 정의

    /* mm/mseal.c:187-191 */
    SYSCALL_DEFINE3(mseal, unsigned long, start, size_t, len, unsigned long, flags)
    {
    	return do_mseal(start, len, flags);
    }
  • 역할: 사용자 공간에서 호출 가능한 시스템 호출 인터페이스
  • 시그니처: int mseal(void *addr, size_t len, unsigned long flags)
  • 3. can_madvise_modify() - madvise 허용 검사

    /* mm/madvise.c:1297-1331 */
    static bool can_madvise_modify(struct madvise_behavior *madv_behavior)
    {
    	struct vm_area_struct *vma = madv_behavior->vma;
    
    	/* 봉인되지 않은 VMA면 허용 */
    	if (!vma_is_sealed(vma))
    		return true;
    
    	/* 봉인된 VMA라도 discard가 아니면 허용 */
    	if (!is_discard(madv_behavior->behavior))
    		return true;
    
    	/* 익명 read-only SEALED 매핑의 일부 discard는 허용 */
    	if (!vma_is_anonymous(vma))
    		return true;
    
    	/* 쓰기 가능한 매핑이면 허용 */
    	if ((vma->vm_flags & VM_WRITE) &&
    	    arch_vma_access_permitted(vma, /* 쓰기= */ true,
    				      /* 실행= */ false, /* 외부= */ false))
    		return true;
    
    	/* 그 외에는 허용하지 않음 */
    	return false;
    }
  • 역할: 봉인된 VMA에서 madvise() 호출이 허용되는지 판단
  • 분기 로직:
  • - VMA가 봉인되지 않음 → 허용

    - discard 연산이 아님 → 허용

    - 파일 기반 매핑 (shared/private) → 허용

    - 쓰기 가능 매핑 → 허용

    - 그 외 (익명 읽기 전용 봉인 매핑) → 차단

    4. is_discard() - discard 계열 판별

    /* mm/madvise.c:1273-1284 */
    static bool is_discard(int behavior)
    {
    	switch (behavior) {
    	case MADV_FREE:
    	case MADV_DONTNEED:
    	case MADV_DONTNEED_LOCKED:
    	case MADV_REMOVE:
    	case MADV_DONTFORK:
    	case MADV_WIPEONFORK:
    	case MADV_GUARD_INSTALL:
    		return true;
    	}
    
    	return false;
    }
  • madvise()가 데이터 폐기 계열인지 먼저 가려낸 뒤, 봉인된 익명 읽기 전용 VMA에서만 제한을 걸어둡니다.
  • 5. vma_is_sealed() - 봉인 상태 확인

    /* mm/vma.h:662-664 */
    static inline bool vma_is_sealed(struct vm_area_struct *vma)
    {
    	return (vma->vm_flags & VM_SEALED);
    }
  • 역할: VMA의 vm_flags에서 VM_SEALED 비트 검사
  • 사용처: mremap, mprotect, munmap, madvise 등에서 호출
  • 6. range_contains_unmapped() - VMA 홀 검증

    /* mm/mseal.c:38-53 */
    static bool range_contains_unmapped(struct mm_struct *mm,
    		unsigned long start, unsigned long end)
  • 역할: 봉인 범위 내에 매핑되지 않은 영역이 있는지 확인
  • 반환값: 홀이 있으면 true, 없으면 false

  • 호출 흐름

    사용자 공간
      │
      ▼
    mseal(addr, len, flags)          ← 시스템 호출
      │
      ▼
    SYSCALL_DEFINE3(mseal)           ← mm/mseal.c:187
      │
      ▼
    do_mseal(start, len, flags)      ← mm/mseal.c:139
      │
      ├─► flags 검증                 ← flags != 0 → -EINVAL
      ├─► untagged_addr(start)       ← 태그 제거 (MTE 등)
      ├─► PAGE_ALIGNED(start) 검증   ← 페이지 정렬 확인
      ├─► len = PAGE_ALIGN(len_in)   ← 길이 정렬
      ├─► end = start + len          ← 오버플로우 검사
      │
      ├─► mmap_write_lock_killable(mm)  ← 쓰기 잠금 획득
      │
      ├─► range_contains_unmapped(mm, start, end)  ← VMA 홀 검사
      │     │
      │     └─► for_each_vma_range() → 홀 발견 시 -ENOMEM
      │
      ├─► mseal_apply(mm, start, end)  ← VM_SEALED 플래그 설정
      │     │
      │     └─► for_each_vma_range()
      │           ├─► vma_modify_flags()  ← VMA 분할/병합
      │           └─► vm_flags_set(vma, VM_SEALED)
      │
      └─► mmap_write_unlock(mm)      ← 쓰기 잠금 해제

    봉인 후 차단되는 시스템 호출 흐름

    봉인된 VMA에서 호출 시
      │
      ├─► munmap()     → mm/vma.c:1403,1423  → -EPERM
      ├─► mremap()     → mm/mremap.c:1666    → -EPERM
      ├─► mprotect()   → mm/mprotect.c:706   → -EPERM
      └─► madvise()    → mm/madvise.c:1302   → 허용/차단 분기

    조건별 비교

    봉인 후 시스템 호출 허용 여부

    시스템 호출허용 여부검사 위치비고
    `munmap()`차단 (-EPERM)mm/vma.c:1403봉인된 VMA 분할 시도 시
    `mremap()`차단 (-EPERM)mm/mremap.c:1666이동/확장/축소 모두 차단
    `mprotect()`차단 (-EPERM)mm/mprotect.c:706RWX 보호 비트 변경 차단
    `pkey_mprotect()`차단 (-EPERM)mm/mprotect.c:706pkey 포함 mprotect
    `madvise(discard 계열)`조건부 차단mm/madvise.c:1274,1302`MADV_FREE`, `MADV_DONTNEED`, `MADV_DONTNEED_LOCKED`, `MADV_REMOVE`, `MADV_DONTFORK`, `MADV_WIPEONFORK`, `MADV_GUARD_INSTALL`
    `madvise(비-discard)`허용mm/madvise.c:1306`is_discard()`가 false이면 허용
    `mmap(MAP_FIXED)`차단 (간접)mm/vma.c:1403기존 매핑 교체 불가

    mseal() 호출 조건별 결과

    조건결과비고
    `flags != 0`-EINVAL예약된 플래그
    시작 주소 미정렬-EINVAL페이지 정렬 필요
    범위 오버플로우-EINVALstart + len > ULONG_MAX
    빈 범위 (len=0)성공 (0)아무 작업 없음
    VMA 홀 존재-ENOMEM시작/끝 사이 매핑 없음
    32비트 아키텍처-EPERMmseal 미지원
    이미 봉인된 VMA성공 (0)멱등성 (idempotent)
    정상적인 봉인성공 (0)VM_SEALED 플래그 설정

    시스템 매핑 봉인 (CONFIG_MSEAL_SYSTEM_MAPPINGS)

    설정동작지원 아키텍처
    `CONFIG_MSEAL_SYSTEM_MAPPINGS=y`vdso, vvar 등 시스템 매핑 자동 봉인x86-64, arm64, loongarch, s390
    `CONFIG_MSEAL_SYSTEM_MAPPINGS=n`시스템 매핑 봉인 비활성모든 아키텍처
    `CONFIG_ARCH_SUPPORTS_MSEAL_SYSTEM_MAPPINGS`아키텍처 수준 지원x86-64, arm64, loongarch, s390

    봉인 사용 사례

    1. ELF 로더 자동 봉인

    /* fs/binfmt_elf.c:1358 - SVr4 호환 page 0 자동 봉인 */
    if (current->personality & MMAP_PAGE_ZERO) {
    	error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,
    			MAP_FIXED | MAP_PRIVATE, 0);
    	retval = do_mseal(0, PAGE_SIZE, 0);  /* page 0 봉인 */
    }

    2. 보안 적용 시나리오

  • Chrome 브라우저: 보안 중요 데이터 구조 보호
  • glibc: 동적 로더가 ELF 세그먼트 봉인
  • 시스템 매핑: vdso, vvar 등 커널 매핑 보호
  • 3. 사용 불가 시나리오

  • aio/shm: 프로세스 수명과 무관하게 매핑 관리 → 누수 위험
  • malloc 힙: 힙 관리자 동작 방해 → 비결정적 결과

  • 봉인 매핑에서의 쓰기 가능 경로

    봉인된 읽기 전용 매핑에도 쓰기가 가능한 경로가 있습니다 (보안 고려사항):

    경로메커니즘차단 방법
    `/proc/self/mem`FOLL_FORCE로 쓰기SELinux/SMACK
    `ptrace`PTRACE_POKETEXTseccomp
    `userfaultfd`페이지 폴트 처리seccomp

    관련 문서

  • 00-overview.html - 메모리 관리 개요
  • 03-vma_mmap.html - VMA / mmap
  • 36-mprotect.html - mprotect
  • 37-mremap.html - mremap
  • 08-oom.html - OOM Killer
  • mseal.rst - 공식 커널 문서

  • SVG 다이어그램

    호출 흐름

    mseal 호출 흐름

    봉인 검사 위치

    봉인 검사 위치