서론
EpicAI의 요시자키입니다.
최근 AI Agent의 편의성을 누리면서도 안전하게 실행하는 기술로 다양한 도구나 OSS(Open Source Software)가 주목받고 있습니다. 대부분의 경우, 이러한 기술들은 컨테이너나 VM(Virtual Machine)과 같이 익숙한 기술들로 구성되어 있지만, 실제로 내부를 들여다보면 생소한 기술이 사용되는 경우가 적지 않습니다. 공식 문서를 읽다가 그러한 기술을 접하고 이해가 모호해진 경험을 하신 분들이 많으실 겁니다.
저에게 있어 대표적인 예시가 바로 Landlock입니다. Landlock은 Linux에서 샌드박스(Sandbox)를 구축하는 기술 중 하나로, Namespace 등과 비교하면 인지도는 낮지만, Agent의 안전한 실행 기반을 구성하는 핵심 요소로 자주 사용됩니다 (예: NVIDIA OpenShell 등).
따라서 이번 글에서는 Landlock을 API 레벨에서 개괄적으로 살펴보고, 유명 OSS에서의 채택 사례나 유사 기술과의 비교를 통해 응용 측면에서의 이해도를 높이는 것을 목표로 합니다.
대상 독자
본 기사에서는 샌드박스를 구현하는 기술 중 하나인 Landlock을 대략적이고 경험적으로 이해하는 것을 목표로 합니다.
따라서 AI Agent를 안전하게 운영하기 위한 베스트 프랙티스(Best Practice)나, Landlock을 이용한 Agent 실행 환경 구축 방법 등 현실적인 구체 구현에 깊이 파고들지는 않습니다. 미리 양해 부탁드립니다.
참고로, 본 기사에서는 공식 문서 등을 참조하여 AI 리뷰 기능이나 Claude Code를 사용해 여러 차례 본 기사의 타당성과 정확성을 검증했지만, 오류가 포함될 수 있습니다. 만약 오류가 있다면 지적해주시면 감사하겠습니다.
Landlock
여기서는 Landlock의 대략적인 개요와 그 배경이 되는 LSM에 대해 간단히 설명합니다.
개요
Landlock은 Linux 커널 5.13 이후부터 사용 가능한, 비특권(non-privileged)으로 샌드박스를 생성할 수 있는 보안 기능입니다. Landlock을 이용하면 특정 프로세스나 스레드에 대해 파일 시스템이나 네트워크의 권한 등을 제한할 수 있습니다.
Linux에서 보안이라고 하면 Namespace나 cgroup 등 컨테이너를 구성하는 기술이 가장 먼저 떠오르실 겁니다. Landlock은 Linux 커널 5.13이 출시된 2021년부터 정식 보안 모듈로 등장한 비교적 새로운 기술이며, Namespace만큼은 인지도가 높다고 보기 어렵습니다.
LSM과의 관계
Landlock은 LSM (Linux Security Modules) 기반의 보안 모듈이라고 표현할 수도 있습니다.
LSM이란 Linux 커널에 탑재된 프레임워크(Framework)로, 커널 내부 메커니즘에 개입하는 다양한 보안 기능을 구현하기 위한 함수 등을 제공합니다. LSM 자체가 접근 제어와 같은 보안 기능을 직접 제공하는 것이 아니라, 어디까지나 다양한 설계 사상에 대응 가능한 커널 고유의 라이브러리 같은 위치입니다.
LSM은 “특정 주체 S가 객체 OBJ에 대해 커널의 작업 OP를 실행할 수 있는가?”라는 질문에 답하는 것을 목표로 하며, 다시 말해 커널 내부 객체에 대한 조작 허용/불허를 전달하는 것입니다.
LSMの設計
Linux Security Modules: General Security Support for the Linux Kernel에서 인용
좀 더 구체적으로 설명하자면, LSM은 커널 내부의 객체에 대한 조작이 발생하기 직전 단계에서 '훅(hook)'을 제공합니다. Landlock과 같은 보안 모듈은 이 훅을 통해 커널에 의해 호출되며, 커널 내부 객체에 대한 접근 가능 여부를 판단합니다. LSM은 커널이 제공하는 기능이기 때문에, 커널 내부 데이터 구조에 접근할 수 있어 세밀한 제어가 가능하다는 장점이 있습니다. 또한 시스템 콜(system call) 호출부터 접근 제어 훅이 호출될 때까지가 커널 내부에서 완결되므로 TOCTOU 문제(Time-of-Check to Time-of-Use)를 회피하기 쉽다는 등의 이점도 있습니다.
Landlock은 이 LSM을 사용하여 구현되었으며, 이를 통해 컨테이너와는 다른 방식으로 접근 제어를 실현할 수 있습니다.
참고로, 유사하게 LSM을 이용해 구현된 보안 기능으로는 AppArmor, SELinux 등이 거론됩니다.
기본 조작
다음으로, Landlock을 사용하여 구체적으로 어떻게 접근 제어를 수행하는지 살펴보겠습니다. 이러한 기술은 설명서만 읽는 것만으로는 이미지를 떠올리기 어렵기 때문에, 우선 Landlock 공식에서 제공하는 Rust 샘플 애플리케이션을 활용하여 실제로 Landlock으로 샌드박스(Sandbox)를 만들어보는 경험부터 해보겠습니다. 그 후에는 Linux 커널 문서에 작성된 C 언어 구현을 참고하며, Landlock에서의 API 사용법을 따라가 보겠습니다.
rust-landlock
Landlock은 Go와 Rust를 위한 라이브러리를 제공합니다. 여기서는 Rust 라이브러리인 rust-landlock의 샘플 애플리케이션으로 Landlock을 체험해 보겠습니다. 참고로, 이 섹션에서는 Rust 지식이 거의 필요하지 않습니다.
사전 준비로 Rust 환경 구축을 완료해 주십시오. 리포지토리를 클론한 후, examples/ 폴더에 있는 코드를 다음과 같이 실행해 봅니다.
$ cargo run --example sandboxer
usage: LL_FS_RO="..." LL_FS_RW="..." [other environment variables] target/debug/examples/sandboxer <cmd> [args]...
Execute the given command in a restricted environment.
...
LL_FS_RO와 LL_FS_RW라는 환경 변수에 각각 읽기 전용 경로(read-only path)와 읽기/쓰기가 가능한 경로(read-writeable path)를 지정하여 임의의 프로그램을 실행하면 되는 것 같습니다.
자, 실행하기 전에 준비로 사용자 자신의 홈 디렉터리에 you_can_rw라는 실험용 디렉터리를 만들어 두겠습니다. 동작을 이해하는 실험에 도움이 될 것입니다.
그럼 실제로 실행해 보겠습니다. /home/swkima/...와 같은 디렉터리는 적절하게 자신이 읽기/쓰기를 허가하고 싶은 디렉터리 경로로 대체해 주십시오.
$ LL_FS_RO="/bin:/lib/:/usr:/proc:/etc:/dev/pts:/tmp" \
LL_FS_RW="/dev/null:/dev/full:/dev/zero:/dev/pts:/tmp:/home/swkima/you_can_rw" \
target/debug/examples/sandboxer bash -i
...
실행해 보니 가장 먼저 알 수 있는 것은, .bashrc의 읽기 권한을 부여하지 않았기 때문에 bash가 설정 파일인 .bashrc를 읽어오지 못했다는 점입니다.
시험 삼아 홈 디렉터리로 이동하여 ls를 해 보겠습니다.
swkima@linux:~/Develop/rust-landlock$ cd ~
swkima@linux:~$ ls
ls: cannot open directory '.': Permission denied
홈 디렉터리의 읽기 권한도 없어서 ls를 할 수 없는 것 같습니다. 참고로, cd ~가 가능하다는 점에서 알 수 있듯이 Landlock은 경로를 따라 이동하거나 chdir(디렉터리 변경)하는 행위 자체는 제한하지 않습니다.
만약 샘플 애플리케이션 실행 시 읽기 전용으로 특별히 어떤 디렉터리를 지정했다면, 이 타이밍에 한번 시험해 보는 것이 좋습니다. 그 디렉터리는 읽을 수 있어야 합니다.
자, 그럼 미리 만들어 둔 실험용 디렉터리에서 읽기/쓰기를 시도해 보겠습니다.
swkima@linux:~$ cd you_can_rw
swkima@linux:~/you_can_rw$ ls
hoge.txt
...
홈 디렉터리와 달리 읽기/쓰기가 가능한 것 같습니다.
이러한 실험을 통해 Landlock을 사용하면 적어도 파일 시스템에 대해 읽기/쓰기의 제한을 할 수 있다는 것을 알게 되었습니다.
C 언어 API 사양
Landlock을 이용해 샌드박스를 체험했으니, 한 단계 더 이해도를 높이기 위해 C 언어의 API 사양을 살펴보겠습니다. 여기부터는 아래 'The Linux Kernel' 기사에 게재된 샘플 코드를 읽어나가면서 Landlock의 다소 복잡한 API 사용법을 해석할 것입니다. 코드 설명 시에는 생략하므로, 구현을 더 깊이 보고 싶은 경우에는 원본 기사를 참조해 주십시오.
전체적인 흐름
Landlock의 API는 사용법이 복잡하여 전체적인 구조를 파악하기 어렵지만, Landlock으로 Sandbox를 만드는 기본적인 흐름은 다음과 같습니다.
landlock_ruleset_attr로 제어 대상을 선언합니다. 실행 중인 커널의 ABI 버전을 확인하고, 미지원 권한을 제외해야 합니다.
landlock_create_ruleset()으로 Sandbox 규칙 세트에 해당하는 파일 디스크립터(fd)를 얻습니다.
landlock_add_rule()로 허용할 작업을 추가합니다.
landlock_restrict_self()로 자신 스레드에 적용합니다.
앞서 언급했듯이 Landlock에서는 주로 파일 시스템(filesystem), 네트워크, IPC의 세 가지 권한을 제어할 수 있습니다. 본 기사의 구현 설명 파트에서는 주로 파일 시스템과 네트워크 접근 제어를 다룹니다.
구현 관찰
Landlock의 설명 기사 코드 예제를 바탕으로 실제 API 적용 과정을 살펴보겠습니다.
Landlock에서는 가장 먼저 Landlock으로 관리 대상으로 지정할 접근 권한을 명시적으로 지정합니다. Landlock에서는 landlock_ruleset_attr 구조체로 이를 규정합니다.
struct landlock_ruleset_attr ruleset_attr = {
.handled_access_fs =
LANDLOCK_ACCESS_FS_EXECUTE |
};
여기서 'handle되는 권리'란 거부되는 권리를 의미한다는 점에 유의해야 합니다. landlock_ruleset_attr 구조체는 단지 Landlock의 제한 대상을 결정할 뿐이며, 현재 상태에서는 제한 대상의 모든 작업이 거부됩니다.
이제 제한 대상을 선언했으니, 다음으로 실행 중인 커널이 지원하는 Landlock의 ABI 버전을 확인합니다. Landlock은 커널 버전 업데이트가 있을 때마다 제어할 수 있는 작업이 증가하고 있습니다 (예: ABI v1에서는 파일 시스템만, v4에서 TCP, v6에서 scope 추가 등). 따라서 오래된 커널이 알지 못하는 권한을 포함하여 규칙 세트를 만들려고 하면 오류가 발생합니다. 그러므로 ABI 버전에 따라 미지원 권한을 제외해 주어야 합니다.
int abi;
abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
if (abi < 0) {
}
switch 문을 사용하여, ABI 버전이 낮을수록 더 많은 권한이 제외되도록 작성되어 있는 것이 핵심입니다. 예를 들어, ABI 버전이 2라면, 2 이후 버전의 제외 규칙도 적용됩니다.
ABI 조정이 끝나면, 마침내 landlock_create_ruleset()으로 규칙 세트를 생성합니다. 규칙 세트는 파일 디스크립터(fd)로 반환됩니다.
int ruleset_fd;
ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
if (ruleset_fd < 0) {
}
이 시점의 규칙 세트는 '처리된 모든 작업을 거부하는' 상태이므로, landlock_add_rule()로 허용할 작업을 추가해 나갑니다. 파일 시스템에 대한 규칙은 다음과 같이 추가합니다.
int err = 0;
struct landlock_path_beneath_attr path_beneath = {
.allowed_access =
};
이 타이밍에야 비로소 특정 경로(path)에 대한 규칙을 규정합니다. 규칙의 종류인 LANDLOCK_RULE_PATH_BENEATH라는 이름에서 알 수 있듯이, 이 규칙은
struct landlock_net_port_attr tcp_conn = {
.allowed_access = LANDLOCK_ACCESS_NET_CONNECT_TCP,
.port = 53,
...
}
여기서 지정할 수 있는 것은 어디까지나 포트 번호이며, IP 주소나 도메인 이름에 의한 제어는 할 수 없다는 점에 주의해야 합니다.
더 나아가, 최종 단계에서 호출하는 landlock_restrict_self()에 전달되는 인수인 restrict_flags (어떻게 제한을 적용할지 보여주는 플래그)에서도 하위 호환성 검사가 필요합니다.
__u32 restrict_flags =
LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON |
LANDLOCK_RESTRICT_SELF_TSYNC |
...
또한, 주석에 명시된 바와 같이 ABI v8 미만에서는 Landlock 정책이 멀티스레드 환경에서 적용되지 않는 점에 유의해야 합니다.
마지막으로, 생성한 규칙 세트를 자체 스레드에 적용합니다. 비특권 사용자(non-privileged user)가 Landlock을 사용할 경우에는 CAP_SYS_ADMIN capability를 가지고 있는지 여부와 함께 no_new_privs를 파라미터로 지정하는 것이 필요합니다.
if (!(restrict_flags & LANDLOCK_RESTRICT_SELF_NO_NEW_PRIVS) &&
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
perror("Failed to restrict privileges");
...
}
landlock_restrict_self()를 호출하기 전에 prctl(PR_SET_NO_NEW_PRIVS, ...)를 호출하는 이유는 setuid된 바이너리 등을 실행하여 권한을 얻는 우회 경로를 막기 위함입니다. 특권(CAP_SYS_ADMIN)이 없는 프로세스가 Landlock을 사용할 경우, 이 설정은 필수적입니다.
참고로, 한번 적용된 제한은 해제할 수 없으며, 게다가 fork나 execve로 생성된 자식 프로세스에게도 상속됩니다. 또한, 이미 제한이 걸린 상태에서 규칙 세트를 다시 적용하면 제한은 중첩되어 더 엄격한 방향으로만 변화합니다. 맨 처음의 sandboxer에서 bash -i를 통해 시작된 ls나 cat까지 제한이 상속되었던 것도 이 때문입니다.
rust-landlock을 이용한 추상화
지금까지 살펴본 것처럼, C 언어 API는 지정된 절차에 따라 함수를 호출해야 할 뿐만 아니라, “권한을 비트 플래그로 나열한다”, “ABI 버전을 확인하여 수동으로 권한을 제거한다”, “경로를 open()하여 fd를 전달한다”, “prctl을 잊지 않고 호출한다” 등 올바르게 사용하기 위한 절차가 많아 번거롭습니다.
반면, 샘플로 구동해 본 rust-landlock 크레이트의 샘플 코드를 살펴보면 이러한 절차들이 잘 추상화되어 있음을 알 수 있습니다. 샘플 코드에서 규칙 세트를 구성하는 부분을 추출하여 보면 다음과 같습니다.
let abi = ABI::V9;
let mut ruleset = Ruleset::default().handle_access(AccessFs::from_all(abi))?;
~중략~
...
ABI의 호환성 문제가 흡수되어 있고, .add_rules를 사용하여 빌더 패턴과 같은 형태로 규칙 세트를 생성할 수 있다는 점을 알 수 있습니다.
C 언어 API 호출과 비교했을 때 상당히 간결해졌습니다.
관련 기술과의 비교
여기까지 Landlock의 개요와 이용 방법에 대해 설명했습니다.
그런데 Linux에는 이와 유사한 LSM 기반 보안 기능이나, 파일 권한 제어를 수행하는 커널 기능이 다른 것도 몇 가지 존재합니다.
따라서 Landlock의 위치를 파악하기 위해, 파일 시스템 접근을 제어한다는 점에서 비슷한 기술들을 몇 가지 선정하여 간단히 비교해 보겠습니다.
AppArmor
AppArmor는 Landlock과 마찬가지로 LSM을 사용한 보안 모듈이며, 경로 기반으로 프로그램별 접근을 제어한다는 점도 매우 유사합니다.
차이점은 AppArmor의 경우 시스템 관리자가 특권을 사용하여 프로파일을 정의해야 한다는 것입니다.
Landlock은 비특권(non-privileged) 프로세스가 스스로에게 제한을 가하는 메커니즘이기 때문에, 일반 사용자나 특권 운영을 전제로 하지 않는 애플리케이션에서 활용할 수 있습니다.
한편, Landlock은 AppArmor나 SELinux와 공존(stackable)하도록 설계되었으며, 기존의 접근 제어에 추가적인 계층으로 덧씌울 수 있습니다. (예: Landlock과 AppArmor를 조합하여 사용 가능)
관심이 있다면 stackable LSM을 검색해 보세요.
Mount Namespace
Mount Namespace는 프로세스마다 서로 다른 마운트 포인트 목록을 가지는 기능입니다. Codex나 Claude Code 등에서 사용되는 bubblewrap 같은 컨테이너에서는 Mount Namespace를 사용하여 필요한 디렉터리만 bind mount하고 pivot_root함으로써 샌드박스를 구현합니다.
Landlock과의 차이점은 AppArmor와 마찬가지로 Mount Namespace에 일반적으로 특권(privilege)이 필요하다는 점입니다. Mount Namespace를 생성하려면 보통 특권 또는 User Namespace 내의 CAP_SYS_ADMIN이 필요하며, User Namespace 생성이 제한된 환경에서는 사용할 수 없는 경우가 있습니다. 반면, bind mount를 중첩하여 사용함으로써 '쓰기 가능한 디렉터리 중 일부만 읽기 전용으로 설정'하는 등 유연한 구성을 할 수 있다는 것이 Mount Namespace의 강점입니다. Landlock은 허용(permission)을 추가해 나가는 것만 가능하기 때문에, 부모 디렉터리에 쓰기를 허용하면 그 하위 특정 디렉터리만 다시 읽기 전용으로 되돌릴 수는 없습니다.
다만, Mount Namespace에는 마운트 프로파게이션(mount propagation)이라는 개념이 있는데, 이를 적절하게 설정하지 않으면 네임스페이스 내에서 수행된 동작이 네임스페이스 외부에 영향을 미칠 수 있습니다. 따라서 Mount Namespace를 샌드박스로 사용하려면 마운트 프로파게이션 설정을 주의 깊게 다루어야 합니다.
종합적으로 볼 때, User Namespace 등 다른 네임스페이스와 병용할 필요가 있다는 점과, 마운트 프로파게이션처럼 네임스페이스 외부에 영향을 줄 수 있는 설정 항목이 존재한다는 점이 Landlock과의 차이라고 할 수 있습니다.
Agent 관련 프로젝트에서의 채택 사례
마지막으로, 실제 Agent 관련 프로젝트에서 Landlock이 어떻게 사용되고 있는지 살펴보겠습니다.
Codex (Legacy)
OpenAI의 Codex CLI는 Linux 환경에서 Agent가 실행하는 명령어를 샌드박스화하는 메커니즘을 가지고 있습니다. 초기 Codex에서는 이 샌드박스의 핵심 역할을 Landlock이 담당했습니다. 참고로, 네트워크 제한에는 Landlock이 아닌 seccomp가 사용되었습니다.
하지만 현재의 Codex에서는 Linux의 기본 샌드박스가 bubblewrap으로 대체되었으며, Landlock은 레거시(legacy) 취급을 받고 있습니다. 2026년 3월에 bubblewrap이 기본값으로 지정되었고, 같은 해 4월에는 Landlock 사용을 위한 use_legacy_landlock 플래그 자체가 비권장(Deprecated)되었습니다. 위에 언급된 Landlock 코드에도 'filesystem sandboxing is performed via bubblewrap'라는 주석이 달려 있어 참고용 또는 폴백(fallback) 용도로만 남아있는 상태입니다.
배경 중 하나는 Landlock이 ABI 버전에 따라 Unix 도메인 소켓을 격리할 수 없다는 특성 때문입니다. 이것이 이유라고 할 수 있는 근거로, Codex 리포지토리의 codex-rs/linux-sandbox/README.md에는 다음과 같은 설명이 있습니다.
Filesystem-restricted execution requires bubblewrap. The legacy Landlock option is rejected for these policies because it cannot isolate app-server Unix sockets. Disable features.use_legacy_landlock when upgrading.
번역: 파일 시스템 제한 실행에는 bubblewrap이 필요합니다. 기존의 Landlock 옵션은 애플리케이션의 Unix 소켓을 격리할 수 없기 때문에 이러한 정책에서는 사용할 수 없고 거부됩니다. 업그레이드 시에는 features.use_legacy_landlock을 비활성화하십시오.
여기서 말하는 Unix sockets는 아마도 프로세스 간 통신(IPC)의 일종인 유닉스 도메인 소켓을 지칭하는 것으로 보입니다. 유닉스 도메인 소켓은 파일 시스템을 이용하여 소켓 통신처럼 데이터를 주고받는 방식입니다. 일반적인 소켓 통신(통신 대상의 도메인 이름, IP 주소, 포트 번호를 기반으로 네트워크를 통해 데이터를 주고받는 것)과 달리 파일 시스템이 주소 역할을 하기 때문에, 파일 시스템을 다루는 Landlock의 격리 대상으로 유닉스 도메인 소켓을 처리할 수 있습니다.
하지만 The Linux Kernel 문서에 나와 있듯이, 시스템 파일 경로가 있는 pathname 형태의 유닉스 도메인 소켓 지원은 ABI(Application Binary Interface) >= 9 버전이 필요합니다. ABI v9가 나온 것이 2026년 6월(Linux 커널 7.1)이기 때문에, Codex에서 Bubblewrap으로 전환된 2026년 3월 시점에서는 유닉스 도메인 소켓을 Landlock만으로 완벽하게 격리할 수 없었을 것으로 추정됩니다. (게다가 Landlock이 대응하더라도 ABI가 오래된 커널의 머신에서는 유닉스 도메인 소켓을 격리할 수 없습니다.)
구체적으로 유닉스 도메인 소켓을 격리하지 못하면 어떤 위험이 있을까요? 예를 들어, Codex는 Landlock에 의해 샌드박스 내에서 네트워크 접근이 제한된 상태이고, 애플리케이션 서버가 네트워크에 접근 가능한 강력한 권한을 가지고 있는 경우를 가정해 봅시다. 이런 경우, 원래 네트워크 접근이 불가능해야 하는 Codex가 유닉스 도메인 소켓을 통해 간접적으로 통신할 수 있게 되므로 위험하다고 판단했을 것입니다.
또한, 이 부분은 문서 등에서 명확히 언급된 바는 아니기에 제 추측의 영역이지만, Codex가 Landlock에서 Bubblewrap으로 전환한 이유 중 하나에는 '예외적으로 특정 파일을 불허하는 것이 어렵다'는 점이 있다고 생각합니다. Codex와 같은 코딩 에이전트(Coding Agent)는 작업 디렉터리를 쓰기 가능하게 유지하면서도, 그 하위의 .git이나 .codex 폴더는 읽기 전용 또는 읽기 불가로 보호하고 싶다는 요구사항이 있습니다. Landlock에서는 부모 디렉터리에 한 번 부여한 권한을 자식 디렉터리에서 회수할 수 없기 때문에, 이러한 구성을 순순히 표현하기 어렵습니다. 실제로 코딩 작업을 맡길 때는 API 키의 저장 위치나 .으로 시작하는 설정 파일 등, 디렉터리 내부의 일부 파일에 대한 접근을 거부해야 할 것입니다. 이렇게 국소적인 파일 접근 제어가 어려운 점도 Landlock 사용이 폐지된 이유가 아닐까 추측합니다.
NVIDIA OpenShell
NVIDIA OpenShell은 AI Agent를 격리된 샌드박스 내에서 실행하고 정책에 기반하여 접근을 제어하기 위한 실행 환경입니다. OpenShell은 샌드박스의 보안을 다음 네 가지 레이어로 구성하며, Landlock은 이 중 파일 시스템(Filesystem) 레이어만 담당합니다.
| 레이어 | 보호 대상 | 구현 방법 |
|---|
| Network | 부정확한 외부 통신, 데이터 유출 | 프록시 + OPA에 의한 정책 평가 |
| Filesystem | 시스템 바이너리 변조, 인증 정보 탈취 | Landlock |
| Process | 권한 상승, 위험한 시스템 콜 | seccomp + 권한 하향 조정(setuid/setgid) |
| Provider credentials | 인증 정보 유출 | 네트워크 정책 + 프록시를 통한 인증 정보 대체 |
OpenShell에서 Landlock의 역할은 크게 두 가지로 나뉩니다.
첫 번째는 필수적인 베이스라인 정책으로서의 역할입니다. OpenShell은 Agent 코드를 실행하기 전에 스스로를 보호하기 위한 규칙 세트를 반드시 적용합니다. 이 정책은 Supervisor(Sandbox 외부에 있는 신뢰받는 관리 프로세스)와의 통신 경로 및 시작용 파일을 Agent로부터 숨기기 위한 것이며, ABI v3 이상을 요구하고 이를 적용할 수 없으면 아예 구동 자체를 실패시킵니다.
두 번째로는 사용자 정의 파일 시스템 정책을 적용하는 역할이 있습니다. 정책 파일의 filesystem_policy에서 지정한 read_only/read_write 경로를 베이스라인 위에 추가로 겹쳐서 적용합니다. 반면, Landlock은 도중에 권한을 완화할 수 없기 때문에, 베이스라인보다 느슨한(덜 엄격한) 권한을 부여하는 것은 불가능합니다.
흥미로운 점은 Landlock이 네트워크 제어를 가지고 있음에도 불구하고 OpenShell에서는 네트워크 제어에 사용되지 않는다는 것입니다. OpenShell의 네트워크 정책은 '어떤 바이너리가, 어떤 호스트의, 어떤 API를 호출할 수 있는지'와 같은 세밀한(granularity) 수준으로 기술되지만, 앞서 설명했듯이 Landlock의 네트워크 제어는 포트 번호 단위로만 작성할 수 있습니다. 따라서 네트워크는 프록시(Proxy), Network Namespace, 정책 평가 등을 통해 구현되는 구조입니다.
또한, 파일 시스템 정책은 Sandbox 구동 후에 변경할 수 없다는 반면, 네트워크 정책은 실행 중에도 업데이트할 수 있다는 차이점이 있습니다. 이 또한 '한 번 적용된 Landlock의 제한은 완화될 수 없다'는 특성을 가진 Landlock을 채택했다면 구현하기 어려웠을 것입니다.
맺음말
본 기사에서는 Linux의 Sandbox 기술인 Landlock에 대해 LSM과의 관계, 실제 Sandbox 경험, C 언어 API와 rust-landlock을 통한 추상화, 그리고 Agent 관련 프로젝트에서의 도입 사례들을 살펴보았습니다.