네임스페이스 (Namespaces)

Linux 커널 네임스페이스: PID, mount, network, user 네임스페이스 격리 기술.

관련 표준: OCI Runtime Specification 1.0 (네임스페이스 격리 요구사항), POSIX.1-2017 (프로세스 격리 기반) — 컨테이너 런타임의 네임스페이스 활용 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

네임스페이스 개요

네임스페이스는 커널 리소스를 격리하여 프로세스 그룹이 독립적인 시스템 뷰를 갖게 하는 메커니즘입니다. 컨테이너(Docker, LXC)의 핵심 기반 기술입니다.

네임스페이스 격리 원리

네임스페이스의 핵심 원리는 "같은 커널 위에서 서로 다른 시스템처럼 보이게 만든다"는 것입니다. 운영체제 수준의 가상화가 아니라, 커널 자원에 대한 뷰(view)를 분리하는 것입니다.

커널 구현: nsproxy 구조체

모든 프로세스(task_struct)는 nsproxy 포인터를 통해 자신이 속한 네임스페이스 집합을 참조합니다. 같은 네임스페이스를 공유하는 프로세스들은 동일한 nsproxy를 가리킵니다:

/* include/linux/nsproxy.h */
struct nsproxy {
    refcount_t count;              /* 참조 카운트 */
    struct uts_namespace  *uts_ns;   /* 호스트명 */
    struct ipc_namespace  *ipc_ns;   /* IPC 객체 */
    struct mnt_namespace  *mnt_ns;   /* 마운트 포인트 */
    struct pid_namespace  *pid_ns_for_children; /* 자식의 PID NS */
    struct net           *net_ns;   /* 네트워크 스택 */
    struct time_namespace *time_ns;  /* 시간 오프셋 */
    struct time_namespace *time_ns_for_children;
    struct cgroup_namespace *cgroup_ns; /* cgroup 뷰 */
};

/* task_struct에서 네임스페이스 접근 */
struct task_struct {
    /* ... */
    struct nsproxy *nsproxy;  /* 네임스페이스 프록시 */
    /* user_ns는 cred에서 별도 관리 */
};

네임스페이스 격리의 동작 방식은 다음과 같습니다:

PID 번역 원리

PID 네임스페이스는 계층적 번역(hierarchical translation) 모델을 사용합니다. 하나의 프로세스가 여러 PID 네임스페이스에서 서로 다른 PID를 가질 수 있습니다:

/* 커널 내부: 프로세스는 레벨별로 PID를 가짐 */
struct pid {
    refcount_t count;
    unsigned int level;        /* PID NS 깊이 */
    struct upid numbers[];      /* 각 레벨별 PID 번호 */
};

struct upid {
    int nr;                        /* 해당 NS에서의 PID 값 */
    struct pid_namespace *ns;      /* 소속 NS */
};

/* 예: 컨테이너 프로세스의 PID 매핑
 * Host NS (level 0): PID 1234
 * Container NS (level 1): PID 1
 * → numbers[0] = {nr=1234, ns=host_ns}
 * → numbers[1] = {nr=1, ns=container_ns}
 *
 * 자식 NS에서는 부모 NS의 PID를 볼 수 없지만,
 * 부모 NS에서는 자식의 모든 프로세스를 볼 수 있음
 */

User 네임스페이스의 특수성: User NS는 nsproxy가 아닌 cred(자격 증명) 구조체에서 관리됩니다. 이는 UID/GID 매핑이 프로세스의 보안 컨텍스트에 직접 영향을 미치기 때문입니다. User NS는 다른 모든 네임스페이스의 "소유자(owner)"를 결정하며, 비특권 사용자도 User NS 내에서 다른 네임스페이스를 생성할 수 있게 합니다.

네임스페이스 유형

네임스페이스격리 대상시스템 콜 플래그
PID프로세스 IDCLONE_NEWPID
Mount마운트 포인트CLONE_NEWNS
Network네트워크 스택CLONE_NEWNET
UserUID/GID 매핑CLONE_NEWUSER
UTS호스트명, 도메인명CLONE_NEWUTS
IPCSystem V IPC, POSIX MQCLONE_NEWIPC
CgroupCgroup 루트 디렉터리CLONE_NEWCGROUP
TimeCLOCK_MONOTONIC 등CLONE_NEWTIME

네임스페이스 API

#include <sched.h>

/* 새 네임스페이스로 프로세스 생성 */
clone(child_fn, stack, CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | SIGCHLD, arg);

/* 현재 프로세스를 새 네임스페이스로 이동 */
unshare(CLONE_NEWNS | CLONE_NEWPID);

/* 기존 네임스페이스에 진입 */
int fd = open("/proc/<pid>/ns/net", O_RDONLY);
setns(fd, CLONE_NEWNET);

PID Namespace

PID 네임스페이스는 프로세스 ID를 격리합니다. 새 PID NS 내의 첫 프로세스는 PID 1이 되며, 부모 NS에서는 다른 PID로 보입니다.

PID Namespace 계층 Host PID NS (PID 1=systemd, PID 1234=container) Container PID NS (PID 1=init, PID 2=app) 컨테이너 내부에서는 PID 1, 호스트에서는 PID 1234

Network Namespace

네트워크 네임스페이스는 네트워크 인터페이스, 라우팅 테이블, iptables 규칙, 소켓 등을 완전히 격리합니다. veth 페어로 네임스페이스 간 통신을 구성합니다.

# 네트워크 네임스페이스 생성
ip netns add myns

# veth 페어 생성 및 연결
ip link add veth0 type veth peer name veth1
ip link set veth1 netns myns

# 네임스페이스 내에서 명령 실행
ip netns exec myns ip addr add 10.0.0.2/24 dev veth1
ip netns exec myns ip link set veth1 up
💡

lsns 명령으로 시스템의 모든 네임스페이스를 확인할 수 있습니다. /proc/[pid]/ns/ 디렉터리에서 프로세스가 속한 네임스페이스의 파일 디스크립터를 얻을 수 있습니다.

Mount Namespace

Mount namespace는 프로세스별로 독립적인 마운트 포인트 테이블을 제공합니다. 컨테이너가 호스트와 다른 파일시스템 뷰를 가질 수 있게 합니다. 내부적으로 각 Mount NS는 자체 struct mnt_namespace를 가지며, 이 안에 마운트 포인트의 트리 구조가 저장됩니다.

Mount Propagation 원리

Mount namespace 간의 마운트 이벤트 전파는 peer group 메커니즘으로 제어됩니다. 새 Mount NS를 생성하면 기본적으로 부모와 peer group을 형성하여 마운트 이벤트가 양방향으로 전파됩니다. propagation 유형에 따라 이 동작을 제어합니다:

/* 새 mount namespace에서 프로세스 생성 */
int flags = CLONE_NEWNS;
unshare(flags);

/* pivot_root: 컨테이너의 루트 파일시스템 변경 */
pivot_root("./newroot", "./newroot/oldroot");
umount2("/oldroot", MNT_DETACH);
# mount namespace 생성 및 확인
unshare --mount --propagation private /bin/bash
mount --bind /tmp/myroot /mnt
# 호스트에서는 /mnt가 변경되지 않음

# mount propagation 유형
mount --make-shared /mnt     # 양방향 전파
mount --make-slave /mnt      # 단방향 (호스트→컨테이너)
mount --make-private /mnt    # 전파 없음
mount --make-unbindable /mnt # bind mount도 불가

User Namespace

User namespace는 UID/GID 매핑을 격리합니다. 컨테이너 내에서 root(UID 0)로 보이지만 호스트에서는 일반 사용자입니다.

/* user namespace 생성 */
unshare(CLONE_NEWUSER);

/* UID 매핑 설정: /proc/PID/uid_map */
/* 형식: container_uid  host_uid  range */
/* 예: "0 1000 1" → 컨테이너 UID 0 = 호스트 UID 1000 */
# unprivileged 컨테이너 생성
unshare --user --map-root-user /bin/bash
id   # uid=0(root) gid=0(root)  (컨테이너 내부에서)

# UID 매핑 확인
cat /proc/self/uid_map
#          0       1000          1

Cgroup Namespace

Cgroup namespace는 프로세스의 cgroup 뷰를 격리합니다. 컨테이너 내에서 /proc/self/cgroup이 루트(/)로 보입니다.

UTS와 IPC Namespace

# UTS namespace: 호스트네임 격리
unshare --uts /bin/bash
hostname container-host
hostname  # container-host (호스트에는 영향 없음)

# IPC namespace: System V IPC, POSIX 메시지 큐 격리
unshare --ipc /bin/bash
ipcs  # 빈 IPC 테이블 (호스트의 IPC 객체 안보임)

네임스페이스 생명주기와 관리

/* 네임스페이스 생성 방법 3가지 */

/* 1. clone() 시 플래그로 생성 */
clone(child_fn, stack, CLONE_NEWPID | CLONE_NEWNET | SIGCHLD, arg);

/* 2. unshare()로 현재 프로세스의 namespace 분리 */
unshare(CLONE_NEWNS | CLONE_NEWPID);

/* 3. setns()로 기존 namespace에 진입 */
int fd = open("/proc/PID/ns/net", O_RDONLY);
setns(fd, CLONE_NEWNET);
close(fd);
# 프로세스의 네임스페이스 확인
ls -la /proc/self/ns/
# cgroup -> cgroup:[4026531835]
# ipc    -> ipc:[4026531839]
# mnt    -> mnt:[4026531841]
# net    -> net:[4026531840]
# pid    -> pid:[4026531836]
# user   -> user:[4026531837]
# uts    -> uts:[4026531838]

# 네임스페이스를 파일로 유지 (bind mount)
touch /run/netns/my_netns
mount --bind /proc/self/ns/net /run/netns/my_netns
# ip netns는 이 방식 사용

# nsenter: 기존 네임스페이스에 진입
nsenter --target $PID --net --pid --mount /bin/bash

컨테이너 런타임과의 관계

Docker, Podman 등의 컨테이너 런타임은 namespace를 조합하여 격리된 환경을 구성합니다:

Namespace격리 대상커널 버전clone 플래그
Mount파일시스템 마운트 포인트2.4.19CLONE_NEWNS
UTS호스트네임, 도메인네임2.6.19CLONE_NEWUTS
IPCSystem V IPC, POSIX MQ2.6.19CLONE_NEWIPC
PID프로세스 ID2.6.24CLONE_NEWPID
Network네트워크 스택2.6.29CLONE_NEWNET
UserUID/GID 매핑3.8CLONE_NEWUSER
Cgroupcgroup 루트 디렉토리4.6CLONE_NEWCGROUP
Time부트 시간, 모노토닉 시계5.6CLONE_NEWTIME
💡

참고 자료: LWN: Namespaces in operation 시리즈, man 7 namespaces, man 7 pid_namespaces, man 7 network_namespaces

네임스페이스 관련 주요 취약점

User namespace는 비특권 사용자에게 네임스페이스 내부의 CAP_SYS_ADMIN을 부여하므로, 커널의 다양한 공격 면적을 노출합니다. 많은 커널 취약점이 user namespace를 전제 조건으로 요구하며, 이는 namespace 자체의 보안 설계와 밀접하게 연관됩니다.

CVE-2022-0185 — fsconfig 힙 오버플로우 (네임스페이스 탈출):

fsconfig() 시스템 콜의 legacy_parse_param()에서 파라미터 길이 검증 부족으로 정수 언더플로우가 발생합니다. User namespace 내의 CAP_SYS_ADMIN만으로 트리거 가능하며, 힙 오버플로우를 통해 init namespace로 탈출할 수 있습니다. Linux 5.1~5.16에 영향을 미칩니다.

CVE-2023-2640 / CVE-2023-32629 (GameOver(lay)) — OverlayFS + User NS 권한 상승:

Ubuntu 커널에 적용된 OverlayFS 패치에서, user namespace 내에서 OverlayFS를 마운트할 때 trusted.overlayfs.metacopy xattr 설정이 권한 검사를 우회합니다. 이를 통해 비특권 사용자가 임의 파일에 setuid/setcap 속성을 설정하여 root 권한을 획득할 수 있습니다.

/* User Namespace가 공격 전제 조건인 주요 CVE 목록 */

/*
 * CVE-2022-0185: fsconfig() 힙 오버플로우
 *   → user NS의 CAP_SYS_ADMIN으로 fsconfig() 호출 가능
 *
 * CVE-2022-0492: cgroup v1 release_agent
 *   → user NS + cgroup NS로 cgroup 마운트 가능
 *
 * CVE-2022-1015: nf_tables 스택 버퍼 오버플로우
 *   → user NS의 CAP_NET_ADMIN으로 nf_tables 규칙 생성
 *
 * CVE-2023-0386: OverlayFS + FUSE setuid 우회
 *   → user NS에서 FUSE + OverlayFS 마운트 가능
 *
 * CVE-2023-32233: nf_tables 익명 set UAF
 *   → user NS의 CAP_NET_ADMIN으로 nf_tables 조작
 *
 * CVE-2024-1086: nf_tables verdict UAF
 *   → user NS의 CAP_NET_ADMIN으로 트리거
 */

/* User Namespace 공격 면적 제한 방법 */

# 방법 1: User NS 완전 비활성화 (가장 강력)
sysctl -w user.max_user_namespaces=0

# 방법 2: 비특권 User NS 비활성화 (Debian/Ubuntu)
sysctl -w kernel.unprivileged_userns_clone=0

# 방법 3: BPF와 perf 제한 (user NS 내 CAP_SYS_ADMIN 영향 축소)
sysctl -w kernel.unprivileged_bpf_disabled=2
sysctl -w kernel.perf_event_paranoid=3

# 방법 4: AppArmor로 user NS 내 특정 작업 제한 (Ubuntu 23.10+)
# /etc/apparmor.d/unprivileged_userns 프로파일 활용
PID Namespace 보안 고려사항:

PID 재사용 공격: PID namespace 내에서 프로세스가 종료되고 동일 PID가 재할당될 때, kill()이나 ptrace() 등의 시스템 콜이 의도하지 않은 프로세스에 작용할 수 있습니다. pidfd_open()(5.3+)을 사용하여 PID 대신 파일 디스크립터로 프로세스를 참조하면 이 문제를 방지할 수 있습니다.
프로세스 정보 누출: /proc 파일시스템의 hidepid=2 마운트 옵션을 사용하여 다른 사용자의 프로세스 정보를 숨길 수 있습니다.