seccomp / libseccomp-golang

The libseccomp golang bindings repository
BSD 2-Clause "Simplified" License
268 stars 56 forks source link

RFE: add a list of syscalls required for normal go operation #31

Open bobrik opened 5 years ago

bobrik commented 5 years ago

Go runtime requires some syscalls for normal operation (like mmap for memory allocation).

It seems like it's better to provide a list from the library rather than make developers guess.

At least the following are required:

    "mmap",
    "munmap",
    "mprotect",
    "futex",
    "clone",
    "rt_sigreturn",
    "rt_sigprocmask",
    "set_robust_list",
    "sigaltstack",
mheon commented 5 years ago

Add to that list gettid and sched_getaffinity at a minimum, probably nanosleep and getpid. If you're using any of the Golang concurrency primitives you probably hit a sched_yield or two as well.

bobrik commented 5 years ago

Yes, my list is for a tiny program that doesn't do much.

I'm not sure if nanosleep needs to be explicitly allowed. I followed this example and without whitelisting nanosleep I can still see it allowed in strace.

mheon commented 5 years ago

I'm fairly certain Go uses it internally in the runtime for scheduling Goroutines, so while you can probably get away without it for trivial programs, I wouldn't recommend leaving it out.

(I'm also quite certain blacklisting it does block it - I used it extensively as a testcase while developing the bindings. Very easy to call fork off a sleep and see whether it takes milliseconds or seconds to return)

pjbgf commented 4 years ago

I have recently built a tool that goes through the execution path of go applications and extract all syscalls it finds. On this source code:

package main

import "fmt"

func main() {
    fmt.Println("test")
}

this is what it extracts:

"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",

here's how to use it:

go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf "\"%s\",\n" .Name}}{{- end}}' application-path
gsauthof commented 4 years ago

A default list would certainly be useful since to come up with one isn't trivial.

Just running the program under strace and collecting the observed syscalls isn't sufficient.

In my experience, even running the program with the same input several times in a row gives a different sets of syscalls, e.g. mprotect and set_robust_list don't necessarily turn up always.

And then there are non-regular circumstances that lead to syscalls you don't see usually such as restart_syscall which is executed only if you manage to send a signal that interrupts your program during a syscall.

In can confirm that nanosleep definitely needs to be whitelisted as it's called a lot, at least if you use Go routines. Not whitelisting it definitely yields a seccomp action.

On the other hand, I haven't seen rt_sigreturn so far.

Ideally one would have a look at the Go runtime code and grep the syscalls out of it.

Since Go statically links everything (unless you import a module with C bindings), the syscalls in the resulting binary should contain all the syscalls the program and the Go runtime does. And not much else since one should expect that Go eliminates dead runtime code.

I've done this for a medium size Go program of mine (gonzofilter, cf. its whitelist) which does a bit file IO, uses Go routines and does a lot of lexing and parsing:

Get syscall table:

curl -O https://raw.githubusercontent.com/torvalds/linux/v5.6/arch/x86/entry/syscalls/syscall_64.tbl

Get all direct syscalls:

join <(objdump -d gonzofilter -Mintel | grep 'syscall ' -B1 | grep eax | cut -f2 -d, \
    | sort -u | awk '{printf("%d\n", strtonum($1));}' | sort -k1,1) \
    <(awk '/^[0-9]/ {print $1, $3}' syscall_64.tbl | sort -k1,1) | sort -k1,1 -n > l1

Get all indirect syscalls (i.e. via syscall.Syscall and syscall.Syscall6):

join <(objdump -d gonzofilter -Mintel | grep 'call .*Syscall' -B18 \
    | grep '\[rsp\]\|Syscall' | grep QWORD | cut -f2 -d, | sort -u  \
    | awk '{printf("%d\n", strtonum($1));}' | sort -k1,1) \
    <(awk '/^[0-9]/ {print $1, $3}' syscall_64.tbl | sort -k1,1) | sort -k1,1 -n > l2

Combined I get this list:

clone
close
epoll_create
epoll_create1
epoll_ctl
epoll_pwait
exit
exit_group
fcntl
fdatasync
flock
fstat
fsync
ftruncate
futex
getpid
gettid
kill
lseek
madvise
mincore
mmap
munmap
nanosleep
openat
pread64
pwrite64
read
readlinkat
rt_sigaction
rt_sigprocmask
sched_getaffinity
sched_yield
setitimer
tgkill
write

When I link against a module with C bindings (such as this libseccomp module), of course, there are some additional syscall originating from the shared libraries (e.g. libc+libpthread+libseccomp).

kolyshkin commented 2 years ago

This is a moving target, meaning any new golang releases and/or Linux kernel releases can add more system calls.

pcmoore commented 2 years ago

I agree with @kolyshkin, trying to maintain a default list of syscalls is going to be extremely difficult. Maybe someday we could revisit this idea, but at this point in time I think this needs to be a WONTFIX.