Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[SPARCS TeaParty] Go 언어와 ptrace로 만드는 seccomp 샌드박스

Joongi Kim
February 26, 2017

[SPARCS TeaParty] Go 언어와 ptrace로 만드는 seccomp 샌드박스

Sorna Jail 프로젝트 소개 및 내부 샌드박싱 원리 설명

Joongi Kim

February 26, 2017
Tweet

More Decks by Joongi Kim

Other Decks in Programming

Transcript

  1. Sorna Jail 프로젝트 § http://sorna.io 및 https://codeonweb.com을 위한 샌드박스 §

    API로 날아오는 “임의의” 코드를 안전하게 실행하기 위해 개발 § 1차 방어막 ‒ Linux container via Docker • 파일시스템, 프로세스/IPC/네트워크 네임스페이스 분리 • 메모리 및 CPU 사용률 제어 § 2차 방어막 ‒ Sorna Jail • Docker나 Linux container가 지원하지 않는 세밀한 정책 구현 • 예 : 프로세스/쓰레드 개수 제한, execve 시스템콜의 대상 제한, 관리 프로 세스에 대한 SIGKILL 제한, 네트워크 접근 제어 등 § 코드 : https://github.com/lablup/sorna-repl/tree/master/jail
  2. 역사 § Sorna Jail • 2015년 12월 개발 시작 •

    2016년 1월 프로덕션 적용 § Docker seccomp profile • https://docs.docker.com/engine/security/seccomp/ • Docker v1.10부터 적용 (2016년 2월 릴리즈) • 이전까지는 apparmor 이용 § 결과 • docker run --security-opt seccomp:unconfined ...
  3. Docker container aka “Sorna kernel” Sorna Jail 구조 jail <policyname>

    /path/to/runtime /path/to/REPLscript Runtime Process (Python, Julia, R, bash, NodeJS, etc.) User code PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1 work 20 0 8628 784 688 S 0.0 0.0 0:00.01 sh 7 work 20 0 188664 3628 2992 S 0.0 0.2 0:00.39 jail 94 work 20 0 179552 14396 7516 S 0.0 0.7 0:00.10 python
  4. Go 언어 § 구글에서 만든 시스템 프로그래밍용 언어 • 애플리케이션

    개발 시 C/C++을 대체하기 위한 목적 § 주요 특징 • 간단하고 배우기 쉬운 문법 (C/Java 유경험자는 1~2일이면 대충 사용) • 메모리 관리는 garbage collection • goroutine + channel을 이용한 멀티태스킹 • 패키지 시스템 제공 (go get) • fast & static build ‒ 바이너리 배포가 매우 편리함 • 아직은 generic을 잘 지원하지 않음 • 표준화된 코딩스타일 (gofmt 도구 제공, $GOPATH) CC BY 3.0 Renee French
  5. ptrace § strace 써본 사람? • 한번도 써본 적이 없다면,

    당장 리눅스에서 strace python helloworld.py 라도 해보세요! § gdb는 어떻게 만들었을까요? • 한번쯤 궁금하지 않았나요? § ptrace system call • 다른 프로세스의 모든 시스템콜 호출 모니터링 • 다른 프로세스의 레지스터를 보거나 메모리를 읽고 쓸 수 있음 • 대부분의 Unix/Linux/BSD 계열 운영체제에서 제공 • man ptrace
  6. ptrace 초기화 (방법 1) pid = fork() execve() ptrace(PTRACE_TRACEME, …)

    waitpid(…, WSTOPPED) kill(getpid(), SIGSTOP) ptrace(PTRACE_SETOPTS, …) kill(pid, SIGCONT) (blocked) (blocked) (other initialization) tracer tracee
  7. ptrace 초기화 (방법 2) pid = fork() execve() waitpid(…, WSTOPPED)

    kill(getpid(), SIGSTOP) ptrace(PTRACE_ATTACH, …) kill(pid, SIGCONT) (blocked) (blocked) (other initialization) Linux 3.4부터 PTRACE_ATTACH 대신 PTRACE_SEIZE 이용 가능 (차이점은 뒤에서 설명!) tracer tracee
  8. ptrace syscall tracing syscall(…) waitpid(…, &status, WALL) ptrace(PTRACE_CONT, …) Do

    some tracing depending on status (e.g., log syscall params, modify syscall params/return values, etc.) waitpid(…, &status, WALL) syscall returns and continue execution tracer tracee loop
  9. “ptrace-stop” 종류 § syscall-stop • tracee가 시스템콜을 호출했을 때 •

    PTRACE_O_TRACESYSGOOD § signal-delivery-stop • tracee가 운영체제, 자신, 또는 다른 프로세스가 발생시킨 signal 받은 경우 § group-stop • tracee가 받은 signal이 “stopping”인 경우 signal-delivery-stop 이후에 추가로 발생. tracee가 정지 상태임을 알려준다. § ptrace-event-stop • tracee가 지정된 이벤트를 발생시키는 경우 (execve, fork, clone 등) • PTRACE_O_TRACECLONE, PTRACE_O_TRACEEXEC, PTRACE_O_TRACEFORK, PTRACE_O_TRACESECCOMP, ... SIGSTOP, SIGTSP, SIGTTIN, SIGTTOU
  10. group-stop 다루기 § 이걸 제대로 못 다루면 발생하는 현상 •

    jail 안에서 도는 shell이 job control을 하지 못한다. • Ctrl+Z가 작동하지 않는다. § Linux 3.4 이전까지 문제점 • group-stop인지 signal-delivery-stop인지 구분하려면 원래는 PTRACE_GETSIGINFO 불러서 EINVAL 리턴 확인해야 함 • tracee가 stop 상태를 유지해야 하는데 PTRACE_CONT 해버리면 stop 상태 가 풀려버림 § Linxu 3.4 이후 • PTRACE_ATTACH 대신 PTRACE_SEIZE 사용하면 group-stop 여부를 (status>>16) == PTRACE_EVENT_STOP 조건으로 정확히 검출 가능 • group-stop인 경우 PTRACE_CONT 대신 PTRACE_LISTEN 사용
  11. seccomp § 초기 구현 • exit(), sigreturn() 및 이미 열려있는

    file descriptor에 대한 read(), write() 만 가능하도록 프로세스 샌드박싱 • 이외의 시스템콜을 부르면 커널이 SIGKILL § seccomp-bpf 확장 • BPF (Berkeley Packet Filter) 기반으로 allow, block, trace할 시스템콜들 을 인자에 대한 조건식과 함께 지정 가능 • block하는 경우 오류 리턴값 지정 가능 (예: EPERM) • trace하는 경우 ptrace-event-stop을 통해 보다 직접 구현한 필터링 조건 적용이나 인자·리턴값 조작 가능 • libseccomp2 라이브러리를 통해 사용
  12. ptrace + seccomp in Go: tracer runtime.GOMAXPROCS(1) /* limit Go’s

    threads */ runtime.LockOSThread() /* ensure signal delivery to the main Go thread */ pid := syscall.ForkExec("intra-jail”, ...) /* we follow parent path here */ loop { var status syscall.WaitStatus traceePid, _ := syscall.Wait4(-1, &status, syscall.WALL, nil) if status.Exited() { if pid == traceePid { break /* our first child exited */ } } if status.Signaled() { continue } /* bypass signals */ if !status.Stopped() { continue } stopsig := status.StopSignal() event := uint(status) >> 16 if event == PTRACE_EVENT_STOP { /* this is group-stop */ ptraceListen(traceePid) } else if stopsig == syscall.SIGTRAP { eventCause := ((uint(status) >> 8) & (^uint(syscall.SIGTRAP))) >> 8 switch eventCause { case PTRACE_EVENT_SECCOMP: var regs syscall.PtraceRegs syscall.PtraceGetRegs(traceePid, &regs) /* modify regs according to Jail policy */ syscall.PtraceSetRegs(traceePid, &regs) } ptraceCont(traceePid) } } 최초 stop-continue 및 PTRACE_SEIZE 호출 생략
  13. ptrace + seccomp in Go: tracee import seccomp "github.com/seccomp/libseccomp-golang" earlyFilter,

    _ := seccomp.NewFilter(seccomp.ActErrno.SetReturnCode(int16(syscall.EPERM))) laterFilter, _ := seccomp.NewFilter(seccomp.ActErrno.SetReturnCode(int16(syscall.EPERM))) for _, syscallName := range policy.AllowedSyscalls { // earlyFilter & laterFilter: AddRuleExact(..., seccomp.ActAllow) } for _, syscallName := range policy.TracedSyscalls { // earlyFilter & laterFilter: AddRuleExact(..., seccomp.ActTrace) } killSyscalls := []string{"kill", "killpg", "tkill", "tgkill"} for _, syscallName := range killSyscalls { // earlyFilter.AddRuleExact(..., seccomp.ActAllow) /* allow while setting up */ // laterFilter.AddRuleExact(..., seccomp.ActTrace) /* trace after setting up */ } for syscallName, cond := range policy.ConditionallyAllowedSyscalls { // earlyFilter & laterFilter: AddRuleConditional(..., seccomp.ActAllow, cond) } earlyFilter.SetNoNewPrivsBit(true) laterFilter.SetNoNewPrivsBit(true) earlyFilter.Load() syscall.Kill(os.Getpid(), syscall.SIGSTOP) laterFilter.Load() syscall.Exec(os.Args[1], os.Args[1:], os.Environ())