Redox OS에서 네임스페이스 관리와 현재 작업 디렉터리를 capability로 재구현하여 커널을 단순화하고 샌드박싱 지원을 강화한 방법을 설명합니다.
안녕하세요, 여러분! 저는 Ibuki Omatsu입니다. 현재 “Redox를 위한 capability 기반 보안” 프로젝트를 진행하고 있으며, NGI Zero Commons와 NLnet의 아낌없는 지원을 받고 있습니다.
이 글에서는 “사용자 공간에서의 네임스페이스 관리”와 “capability로서의 CWD”를 설명하겠습니다. 이전에는 커널이 관리하던 네임스페이스와, 이전에는 문자열 기반이었던 CWD 관리를 capability를 사용해 어떻게 재구현했는지 살펴보겠습니다.
아직 익숙하지 않다면 Capability-based security에 대해 읽어보는 것도 좋습니다. 간단히 설명하면, 열린 파일 디스크립터는 어떤 자원인지와 그 자원에 대한 애플리케이션의 접근 권한을 식별하므로 capability입니다. Capability 기반 보안은 모든 자원 접근이 capability에서 시작될 것을 전제로 합니다.
이 섹션에서는 Redox 고유의 두 가지 개념을 설명하겠습니다. 자원 제공자 “Scheme”과 C 표준 라이브러리 구현인 “relibc”입니다.
아시다시피 Redox OS는 마이크로커널 기반 운영체제입니다. 이는 파일시스템과 프로세스 관리자 같은 대부분의 시스템 구성 요소와 드라이버가 사용자 공간의 별도 프로그램으로 실행된다는 뜻입니다. “Scheme”은 이러한 프로그램이 제공하는 서비스입니다.
예를 들어 RedoxFS(Redox OS의 파일시스템 서비스)는 file scheme을 제공하고, 프로세스 관리자는 proc scheme을 제공합니다. 모든 자원은 다음 형식의 “Scheme-rooted Path”를 통해 접근됩니다: /scheme/{scheme-name}/{resource-name}.
Scheme-rooted Path의 예:
/home/user/file.txt ->/scheme/file/home/user/file.txt127.0.0.1:8080 ->/scheme/tcp/127.0.0.1/8080Redox에서 “네임스페이스”는 scheme의 가시성을 제어합니다. Scheme은 네임스페이스에 등록되며, 프로세스는 자신에게 속한 네임스페이스에 등록된 scheme에만 접근할 수 있습니다. 이는 Scheme-rooted path를 사용해 제어됩니다. 예를 들어 ["]file", "uds"] 같은 네임스페이스에서는 프로세스가 파일과 Unix Domain Socket에는 접근할 수 있지만, 네트워크 스택에는 접근할 수 없습니다.
relibc는 주로 Redox OS를 대상으로 하는 C 표준 라이브러리이지만, 여러 운영체제도 지원합니다.
Linux에서는 단순히 로우 시스템 콜을 호출합니다. 하지만 Redox에서는 redox-rt(Redox 런타임 서비스)를 제공합니다. redox-rt는 Redox 서비스를 POSIX 호환 서비스로 변환하는 계층(POSIX 호환 계층)을 제공하며, 다른 Unix 계열 시스템에서 일반적으로 커널에 할당되는 기능 일부를 수행합니다. 여기에는 POSIX 표준 경로를 scheme-rooted path로 변환하는 기능과 Redox 내부 파일 디스크립터 관리 등이 포함됩니다. Redox OS에서 스레드와 프로세스는 redox-rt에 의해 파일 디스크립터로 관리됩니다.
capability로 전환되기 전 파일 접근이 어떻게 동작했는지 되돌아보겠습니다. 이 이전 설계에서는 네임스페이스가 커널 안에 존재했고, 정수 ID로 프로세스에 바인딩되어 있었습니다.
open("/home/user/some_file")를 호출합니다.redox-rt)가 경로를 scheme-rooted path /scheme/file/home/user/some_file로 변환하고 이를 엽니다.file을 추출하고, 호출한 프로세스의 네임스페이스에서 이를 찾습니다.file이 네임스페이스에 등록되어 있으면 커널이 시스템 콜 요청을 해당 scheme으로 전달합니다.file scheme이 시스템 콜 요청을 받습니다.Scheme과 네임스페이스를 모두 커널이 관리하는 이 설계에서는, 커널이 모든 scheme 이름을 문자열로 보관하고 대상 scheme을 식별하기 위해 경로를 파싱해야 했습니다.

그림 1: capability 이전의 파일 접근 흐름.
이전에는 상대 경로가 처리되기 전에 항상 절대 경로로 변환되었습니다. relibc가 CWD를 문자열로 저장했기 때문에, 모든 상대 경로는 CWD 문자열에 이어 붙여 절대 경로로 바뀌었습니다.
이전 설계에서는 상대 경로가 주어질 때마다 절대 경로를 다시 구성해야 했습니다. 또한 이 방식은 O_RESOLVE_BENEATH 같은 제한을 CWD에 적용하는 것도 어렵게 만들었습니다.
우리의 목표는 이러한 경로 기반 관리로 인해 발생하는 문제를 해결하는 것입니다. 이 전환의 핵심은 openat 시스템 콜입니다.
openat(dir_fd, path)openat 시스템 콜은 디렉터리 파일 디스크립터를 기준으로 파일을 엽니다. 제한이 없는 모드에서는 이는 단지 편의 기능에 불과합니다. 하지만 경로를 그 dir_fd 아래로만 제한하고 프로그램이 openat만 사용하도록 제한하면, 그 dir_fd는 사실상 샌드박스가 됩니다. 프로그램은 그 디렉터리 밖의 어떤 것도 보거나 접근할 수 없습니다.
openat를 기반으로, 우리는 사용자 공간에 Namespace Manager(nsmgr)를 도입했습니다. 이 새로운 설계에서 nsmgr는 사용자 공간의 scheme 유형 서비스 데몬이며, 애플리케이션과 다른 scheme들 사이에 위치합니다. 네임스페이스는 더 이상 프로세스에 바인딩된 단순한 ID가 아니라, redox-rt가 관리하는 파일 디스크립터로 표현됩니다.
pub struct DynamicProcInfo {
pub pgid: u32,
...
pub sgid: u32,
pub ns_fd: Option<FdGuardUpper>,
}
동작 순서는 다음과 같습니다.
open("/home/user/some_file")를 호출합니다.redox-rt)가 경로를 scheme-rooted path(이 경우 "/scheme/file/home/user/some_file")로 변환한 뒤, 프로세스의 네임스페이스 파일 디스크립터를 dir_fd로 하여 openat를 호출합니다.nsmgr가 openat 요청을 받습니다.nsmgr가 대상 scheme 이름(file)을 추출하고, 파일 디스크립터에 바인딩된 네임스페이스에서 이를 찾습니다.file이 네임스페이스에 등록되어 있으면 nsmgr 데몬이 openat 요청을 해당 scheme으로 전달합니다.file scheme이 openat 요청을 받고, 파일의 파일 디스크립터를 nsmgr에 보냅니다.nsmgr가 그 파일 디스크립터를 애플리케이션으로 전달합니다. 새 파일 디스크립터에 대한 이후의 연산에는 nsmgr가 참여할 필요가 없습니다.
그림 2: capability 이후의 파일 접근 흐름.
애플리케이션은 제공된 네임스페이스 파일 디스크립터를 기준으로만 동작합니다. 이를 통해 복잡한 scheme 및 네임스페이스 관리를 커널에서 제거할 수 있습니다. Scheme은 커널에서 익명으로 생성되므로, 커널은 더 이상 어떤 scheme 이름도 알 필요가 없습니다. 이제 커널은 dir_fd를 기반으로 시스템 콜 요청을 scheme으로 전달하기만 하면 되며, 경로를 파싱할 필요가 없습니다.
비슷하게, 우리는 relibc가 CWD를 capability로 처리하도록 업데이트했습니다. 이제 relibc는 CWD를 단순한 문자열 경로가 아니라 파일 디스크립터로 보유합니다. 애플리케이션이 open에 상대 경로를 전달하거나 libc openat 함수에 AT_FDCWD가 전달되면, relibc는 단순히 CWD 파일 디스크립터를 사용해 내부 openat를 호출합니다. 이 전환 덕분에 상대 경로를 절대 경로로 변환하지 않고 처리할 수 있게 되었습니다. 또한 O_RESOLVE_BENEATH를 간단한 구현으로 지원할 수 있게 되었습니다. 파일 디스크립터에 이 플래그를 설정하면 scheme이 ../ 사용을 제한하여 샌드박스 탈출을 막습니다. 추가로, 네임스페이스를 통해 절대 경로 접근을 제한함으로써 CWD 파일 디스크립터를 샌드박스로 사용할 수도 있습니다.
pub struct Cwd {
pub path: Box<str>,
pub fd: FdGuardUpper,
}
getcwd()의 경우에는 여전히 CWD 경로 문자열을 캐시한다는 점에 유의하세요. 다만 더 많은 샌드박싱 기능을 구현해 나가면서 이는 바뀔 수 있습니다.
이러한 기능을 capability를 사용해 재구현함으로써, 복잡한 scheme 및 네임스페이스 관리를 커널 밖으로 옮겨 커널을 더 단순하게 만들었고, 공격 표면과 잠재적 버그를 줄여 보안성과 안정성을 향상시켰습니다. 동시에 CWD 파일 디스크립터를 사용해 더 많은 샌드박싱 기능을 지원할 수 있는 수단도 확보했습니다. 이 프로젝트는 Redox OS에서 앞으로의 샌드박싱 지원을 위한 길을 열어 줍니다. 운영체제가 capability 기반 보안을 향해 계속 나아감에 따라, 더 현대적인 보안 기능을 제공할 수 있게 될 것입니다.
이 글을 읽어 주셔서 감사합니다. 유익하고 흥미롭게 느끼셨기를 바랍니다.

한국어
中文EspañolРусскийFrançaisDeutschItalianoTürkçeSvenskaNederlandsDanskNorskČeštinaEsperantoPortuguês日本語한국어MagyarPolskiУкраїнськаالعربية
Copyright © 2015-2025 by The Redox Developers. Redox OS는 Redox OS nonprofit의 상표입니다.