2. 샌드박스 정책 만들기

실행자가 있으면 Sandboxee에 대한 샌드박스 정책을 정의하는 것이 좋습니다. 그렇지 않으면 Sandboxee가 기본 Syscall 정책에 의해서만 보호됩니다.

샌드박스 정책의 목표는 Sandboxee가 만들 수 있는 syscall과 인수, 그리고 Sandboxee가 액세스할 수 있는 파일을 제한하는 것입니다. 샌드박스 처리할 코드에 필요한 syscall에 대해 자세히 알아야 합니다. syscall을 관찰하는 한 가지 방법은 Linux의 명령줄 도구 strace로 코드를 실행하는 것입니다.

syscall의 목록이 준비되면 PolicyBuilder를 사용하여 정책을 정의할 수 있습니다. PolicyBuilder에는 여러 일반적인 작업을 허용하는 여러 편의성 및 도우미 함수가 함께 제공됩니다. 다음 목록은 사용 가능한 함수의 일부일 뿐입니다.

  • 프로세스 시작을 위한 모든 syscall을 허용합니다.
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • 모든 열린 /read/write* syscall을 허용합니다.
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • 모든 이탈/액세스/상태 관련 syscall을 허용합니다.
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • 모든 수면/시간 관련 syscall을 허용합니다.
    • AllowTime();
    • AllowSleep();

이러한 편의 함수는 모든 관련 syscall을 허용 목록에 추가합니다. 이렇게 하면 특정 syscall을 사용할 수 없는 여러 아키텍처에 대해 동일한 정책을 사용할 수 있다는 이점이 있습니다 (예: ARM64에는 OPEN syscall이 없음). 그러나 필요 이상으로 많은 sycsall을 사용 설정할 수 있는 사소한 보안 위험이 있습니다. 예를 들어 AllowOpen()은 Sandboxee가 공개 관련 syscall을 호출할 수 있도록 합니다. 특정 syscall만 허용 목록에 추가하려면 AllowSyscall();를 사용하여 한 번에 여러 syscall을 허용하면 됩니다. AllowSyscalls()를 사용하면 됩니다.

지금까지 정책은 syscall 식별자만 확인합니다. 정책을 더욱 강화해야 하고 특정 인수가 있는 syscall만 허용하는 정책을 정의하려면 AddPolicyOnSyscall() 또는 AddPolicyOnSyscalls()를 사용해야 합니다. 이러한 함수는 syscall ID를 인수로 사용할 뿐만 아니라 Linux 커널의 bpf 도우미 매크로를 사용하는 원시 seccomp-bpf 필터도 사용합니다. BPF에 관한 자세한 내용은 커널 문서를 참고하세요. 사용성 래퍼가 있어야 한다고 생각되는 BPF 코드를 반복적으로 작성하고 있다면 언제든지 기능 요청을 제출하세요.

syscall 관련 함수 외에도 PolicyBuilder는 파일/디렉터리를 샌드박스에 바인드 마운트하기 위한 AddFile() 또는 AddDirectory()와 같은 여러 파일 시스템 관련 함수를 제공합니다. AddTmpfs() 도우미를 사용하면 샌드박스 내에 임시 파일 저장소를 추가할 수 있습니다.

특히 유용한 함수는 바이너리에 필요한 라이브러리와 링커를 추가하는 AddLibrariesForBinary()입니다.

허용 목록을 위한 syscall을 만드는 것은 안타깝게도 여전히 약간의 수동 작업입니다. 바이너리에 필요한 syscall로 정책을 만들고 일반 워크로드로 실행합니다. 위반이 트리거되면 syscall을 허용 목록에 추가하고 프로세스를 반복합니다. 허용 목록에 추가하는 것이 위험할 수 있다고 생각되는 위반사항이 발견되고 프로그램에서 오류를 적절히 처리하면 대신 BlockSyscallWithErrno()를 사용하여 오류를 반환하도록 시도할 수 있습니다.

#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"

std::unique_ptr<sandbox2::Policy> CreatePolicy() {
  return sandbox2::PolicyBuilder()
    .AllowSyscall(__NR_read)  // See also AllowRead()
    .AllowTime()              // Allow time, gettimeofday and clock_gettime
    .AddPolicyOnSyscall(__NR_write, {
        ARG(0),        // fd is the first argument of write (argument #0)
        JEQ(1, ALLOW), // allow write only on fd 1
        KILL,          // kill if not fd 1
    })
    .AddPolicyOnSyscall(__NR_mprotect, {
        ARG_32(2), // prot is a 32-bit wide argument, so it's OK to use *_32
                   // macro here
        JNE32(PROT_READ | PROT_WRITE, KILL), // prot must be the RW, otherwise
                                             // kill the process
        ARG(1), // len is a 64-bit argument
        JNE(0x1000, KILL),  // Allow single page syscalls only, otherwise kill
                            // the process
        ALLOW,              // Allow for the syscall to proceed, if prot and
                            // size match
    })
    // Allow the openat() syscall but always return "not found".
    .BlockSyscallWithErrno(__NR_openat, ENOENT)
    .BuildOrDie();
}