ptrace를 사용하여 확인된 컴퓨팅 샌드박스

ptrace를 사용하여 확인된 컴퓨팅 샌드박스

Linux 서버에서 신뢰할 수 없는 계산 전용 실행 파일을 시작하고 싶습니다. 프로세스는 다음을 제외 stdin하고 시스템 및 파일에 액세스할 수 없습니다.stdout

내 생각은 ptraceLinux 커널에 대한 시스템 호출을 잡아서 차단하는 것입니다. 또한 프로세스 내부 상태(레지스터 + RAM)를 가져오고 설정하는 데에도 사용합니다. 샌드박스는 안전한가요? 어떤 제동 방법을 알고 있나요?

또한 DOS를 피하기 위해 RAM 및 CPU 시간 사용량을 제한하고 싶습니다.

답변1

이것이 seccomp의 목적입니다. 대부분의 최신 Linux 커널은 시스템 호출을 필터링하도록 설계된 Seccomp를 지원합니다. 모드 1과 모드 2라는 두 가지 형태로 제공됩니다.

모드 1초 압축

이 프로세스에서는 read(), write(), rt_sigreturn()및 4개의 시스템 호출만 허용합니다 exit()(이러한 exit()시스템 호출은 함수가 아니라 시스템 호출입니다. glibc 함수는 화이트리스트에 없는 exit_group()시스템 호출을 사용합니다). 다른 호출을 시도하면 응답하지 않으며 프로그램이 종료됩니다. 이는 보안 에이전트 프로세스 내에서 신뢰할 수 없는 바이트코드를 평가하기 위한 것입니다. 신뢰할 수 있는 코드는 모드 1 seccomp가 활성화된 경우 잠재적으로 위험한 바이트코드를 실행하고 파이프를 통해 상위 프로세스와 통신할 수 있는 신뢰할 수 없는 프로세스를 생성할 수 있습니다.

모드 2초 계산

이는 eBPF 바이트코드를 사용하여 숫자와 매개변수를 기반으로 시스템 호출을 제한하는 동적 필터를 생성하기 때문에 seccomp-bpf라고도 합니다. 또한 프로세스를 강제 종료하는 것부터 시스템 호출을 거부하고 프로세스를 종료하지 않고 포착하도록 신호를 내보내는 것, 사용자 정의 오류 번호를 반환하는 것, 단순히 시스템 호출을 거부하는 것까지 위반에 대해 다양한 조치를 취하도록 설정할 수 있습니다. 테스트 목적으로. libseccomp 라이브러리는 이 중 많은 부분을 추상화하므로 eBPF 바이트코드를 직접 작성할 필요가 없습니다.

두 방법 모두 상당한 오버헤드를 발생시키는 ptrace 기반 샌드박싱보다 훨씬 빠릅니다. 또한 ptrace-sandbox는 반드시 필터를 하위 프로세스로 보내는 것은 아니므로 TOCTOU 경쟁 execve()조건 에 취약하지 않도록 , , 및 같은 호출을 비활성화해야 합니다 . 반면 두 seccomp 모드는 모든 실행 또는 포크에서 필터를 유지합니다.fork()vfork()clone()

바이트코드에서 "return 42"를 안전하게 실행하기 위해 모드 1 seccomp를 사용하는 예:

#include <unistd.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <linux/seccomp.h>

void main(void)
{
    /* "mov al,42; ret" aka "return 42" */
    static const unsigned char code[] = "\xb0\x2a\xc3";
    int fd[2], ret;

    /* spawn child process, connected by a pipe */
    pipe(fd);
    if (fork() == 0) {
        /* we're the child, so let's close this end of the pipe */
        close(fd[0]);

        /* enter mode 1 seccomp and execute untrusted bytecode */
        prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
        ret = (*(uint8_t(*)())code)();

        /* send result over pipe, and exit */
        write(fd[1], &ret, sizeof(ret));
        syscall(SYS_exit, 0);
    } else {
        /* we're the parent, so let's close this end of the pipe */
        close(fd[1]);

        /* read the result from the pipe, and print it */
        read(fd[0], &ret, sizeof(ret));
        printf("untrusted bytecode returned %d\n", ret);
    }
}

여러 개의 임의 시스템 호출 필터와 함께 모드 2 seccomp를 사용하는 예:

#include <seccomp.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>

void main(void)
{
    /* initialize the libseccomp context */
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);

    /* allow exiting */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

    /* allow getting the current pid */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);

    /* allow changing data segment size, as required by glibc */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);

    /* allow writing up to 512 bytes to fd 1 */
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 2,
        SCMP_A0(SCMP_CMP_EQ, 1),
        SCMP_A2(SCMP_CMP_LE, 512));

    /* if writing to any other fd, return -EBADF */
    seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EBADF), SCMP_SYS(write), 1,
        SCMP_A0(SCMP_CMP_NE, 1));

    /* load and enforce the filters */
    seccomp_load(ctx);
    seccomp_release(ctx);

    printf("this process is %d\n", getpid());
}

seccomp를 사용할 때 기억해야 할 몇 가지 중요한 사항이 있습니다.

  • "실제" 시스템 호출은 아니지만 vDSO(예: gettimeofday()및 ) 호출 time()은 필터링할 수 없습니다. 성능을 향상시키기 위해 사용자 공간에서 실행되므로 비용이 많이 드는 컨텍스트 전환을 방지합니다. 그러나 이로 인해 seccomp가 실행 중인지 알 수 없게 됩니다. vDSO로 구현될 수 있는 유일한 시스템 호출은 일반적으로 매우 간단하고 공격 표면을 거의 또는 전혀 노출하지 않기 때문에 일반적으로 문제가 되지 않습니다.

  • Linux 4.8(?) 이전에는 ptrace()호출이 허용된 후 실제로 실행되기 전에 레지스터를 수정하여 화이트리스트 호출을 사용하여 샌드박스를 탈출할 수 있었습니다. 4.8 이전 커널의 경우 해결책은 이 호출을 화이트리스트에 추가하지 않는 것입니다.

  • 시스템 호출은 레지스터를 커널에 전달하여 작동하므로 seccomp(및 모든 ptrace 기반 샌드박스)는 레지스터 자체의 내용을 기준으로만 필터링할 수 있습니다. 이는 open()제공된 파일 이름과 같은 메모리 포인터가 포함된 인수를 필터링할 수 없음을 의미합니다. Seccomp는 메모리가 아닌 레지스터의 내용만 확인합니다.

  • 필터를 적용한 후에는 실행 취소하거나 변경할 수 없습니다. 샌드박스의 여러 단계를 사용하려면 보다 관대한 정책으로 시작하고 새 필터를 추가해야 하기 때문에 다음 단계가 로드될 때까지 화이트리스트에 추가되는지 확인하세요 seccomp()(>= Linux 3.17에서) . prctl()두 번째 단계 샌드박스는 첫 번째 단계와 동일한 시스템 호출을 허용 목록에 추가하고 비활성화하려는 시스템 호출을 제외하거나 선택적으로 비활성화하려는 시스템 호출을 블랙리스트에 추가해야 합니다.

관련 정보