Seccomp 필터와 BPF 기반 정책 소개

Seccomp(Secure Computing Mode)는 Linux 커널의 보안 기능으로, 프로세스가 사용할 수 있는 시스템 호출(syscall)을 제한하여 잠재적인 보안 취약점을 줄이는 데 사용됩니다. 특히, 악성 코드가 침투했을 때 시스템 전체에 미치는 영향을 최소화하는 데 효과적입니다. BPF(Berkeley Packet Filter)는 Seccomp의 필터링 정책을 정의하는 데 사용되는 강력한 도구입니다. BPF를 사용하면 단순히 시스템 호출을 허용하거나 거부하는 것뿐만 아니라, 시스템 호출의 인자 값에 따라 세밀하게 제어할 수 있습니다.

Seccomp의 중요성

Seccomp 작동 방식

Seccomp는 크게 두 가지 모드로 작동합니다.

    • Strict 모드: `exit()`, `sigreturn()`, `read()` 및 `write()` 시스템 호출만 허용합니다. 매우 제한적인 환경에서 사용됩니다.
    • Filter 모드: BPF 기반의 정책을 통해 시스템 호출을 필터링합니다. 더 유연하고 다양한 시나리오에 적용할 수 있습니다.

BPF란 무엇인가?

BPF는 원래 네트워크 패킷 필터링을 위해 설계되었지만, Linux 커널 3.17부터는 Seccomp 필터링 정책을 정의하는 데 사용될 수 있도록 확장되었습니다. eBPF(extended BPF)는 기존 BPF의 기능을 더욱 확장하여 다양한 커널 활동을 추적하고 제어할 수 있게 해줍니다. Seccomp에서 BPF를 사용하면 특정 시스템 호출의 인자 값을 검사하여 더욱 정교한 필터링 정책을 구현할 수 있습니다.

BPF 기반 정책의 구조

BPF 기반 Seccomp 정책은 일련의 규칙으로 구성됩니다. 각 규칙은 특정 시스템 호출과 해당 시스템 호출의 인자 값에 대한 조건을 정의합니다. 규칙은 순차적으로 평가되며, 규칙에 일치하는 시스템 호출은 해당 규칙에 정의된 액션(예: 허용, 거부, 오류 반환)을 따릅니다.

BPF 정책의 기본 구조는 다음과 같습니다.



struct sock_filter {

    __u16 code;   / BPF 명령어 코드 /

    __u8  jt;     / 참일 경우 점프할 오프셋 /

    __u8  jf;     / 거짓일 경우 점프할 오프셋 /

    __u32 k;      / 일반 목적의 32비트 상수 /

};

각 `sock_filter` 구조체는 하나의 BPF 명령어를 나타냅니다. 이러한 명령어들이 연결되어 하나의 BPF 프로그램을 구성합니다.

BPF 명령어 예시

Seccomp와 BPF를 활용한 실생활 예시

컨테이너 보안 강화

Docker와 같은 컨테이너 런타임은 Seccomp를 사용하여 컨테이너의 보안을 강화합니다. 컨테이너 내의 프로세스가 호스트 시스템에 영향을 미칠 수 있는 시스템 호출을 차단하여 컨테이너 격리를 강화합니다. 예를 들어, 컨테이너가 파일 시스템을 마운트하거나 커널 모듈을 로드하는 시스템 호출을 차단할 수 있습니다.

웹 브라우저 샌드박싱

웹 브라우저는 Seccomp를 사용하여 렌더링 프로세스를 샌드박싱합니다. 렌더링 프로세스는 웹 콘텐츠를 처리하는 과정에서 잠재적으로 악성 코드를 실행할 수 있으므로, Seccomp를 사용하여 시스템 호출을 제한함으로써 보안을 강화합니다. 예를 들어, 파일 시스템에 직접 접근하거나 네트워크 연결을 생성하는 시스템 호출을 차단할 수 있습니다.

클라우드 환경에서의 보안

클라우드 환경에서는 Seccomp를 사용하여 가상 머신 또는 컨테이너의 보안을 강화합니다. 클라우드 제공업체는 Seccomp를 사용하여 고객이 실행하는 애플리케이션이 클라우드 인프라에 영향을 미치지 않도록 보호합니다. 예를 들어, 커널 파라미터를 변경하거나 특정 장치에 접근하는 시스템 호출을 차단할 수 있습니다.

Seccomp 정책 작성 및 적용 방법

Seccomp 정책은 직접 BPF 코드를 작성하거나, libseccomp와 같은 라이브러리를 사용하여 작성할 수 있습니다. libseccomp는 Seccomp 정책을 더 쉽게 작성하고 관리할 수 있도록 도와주는 C 라이브러리입니다.

libseccomp를 사용한 정책 작성 예시

다음은 libseccomp를 사용하여 `open()` 시스템 호출을 특정 경로로만 허용하는 정책을 작성하는 간단한 예시입니다.



#include <seccomp.h>

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <errno.h>


int main() {

    scmp_filter_ctx ctx;

    int rc;


    // Seccomp 컨텍스트 생성

    ctx = seccomp_init(SCMP_ACT_KILL); // 기본 액션: 프로세스 종료


    if (ctx == NULL) {

        perror("seccomp_init");

        return 1;

    }


    // open 시스템 호출에 대한 규칙 추가

    rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1, SCMP_A0(SCMP_CMP_EQ, (scmp_datum_t)"/tmp/safe_file"));


    if (rc < 0) {

        perror("seccomp_rule_add");

        seccomp_release(ctx);

        return 1;

    }


    // 다른 시스템 호출 허용 (필요한 시스템 호출만 허용)

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);


    // 필터 로드

    rc = seccomp_load(ctx);


    if (rc < 0) {

        perror("seccomp_load");

        seccomp_release(ctx);

        return 1;

    }


    // 컨텍스트 해제

    seccomp_release(ctx);


    // 파일 열기 시도 (정책에 따라 허용되거나 거부될 수 있음)

    FILE *fp = fopen("/tmp/safe_file", "w");

    if (fp == NULL) {

        perror("fopen");

        return 1;

    }


    fprintf(fp, "Hello, Seccomp!\n");

    fclose(fp);


    // 다른 파일 열기 시도 (정책에 의해 거부되어야 함)

    fp = fopen("/etc/passwd", "r");

    if (fp == NULL) {

        perror("fopen"); // 예상대로 오류 발생

        return 1;

    }


    fclose(fp);


    return 0;

}

이 예시에서는 `/tmp/safe_file` 경로로 파일을 여는 `open()` 시스템 호출만 허용하고, 다른 모든 `open()` 시스템 호출은 차단합니다. 또한 `exit()`, `exit_group()`, `read()`, `write()` 시스템 호출은 허용하여 프로그램이 정상적으로 종료되고 기본적인 입출력을 수행할 수 있도록 합니다.

정책 적용

Seccomp 정책은 `prctl()` 시스템 호출을 사용하여 적용할 수 있습니다. libseccomp를 사용하는 경우, `seccomp_load()` 함수가 내부적으로 `prctl()`을 호출하여 정책을 적용합니다.

흔한 오해와 사실 관계

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다