landlock-lsm / go-landlock

A Go library for the Linux Landlock sandboxing feature
MIT License
105 stars 7 forks source link

Minimal landlock not working with ffmpeg/ffprobe #27

Closed ikmckenz closed 8 months ago

ikmckenz commented 8 months ago

Trying to sandbox a Go wrapper that calls ffprobe, code looks something like this:

package main

import (
    "fmt"
    "github.com/landlock-lsm/go-landlock/landlock"
    "os"
    "os/exec"
)

func main() {
    ffprobe, _ := exec.LookPath("ffprobe")
    file := "/path/to/file"

    err := landlock.V3.BestEffort().RestrictPaths(
        landlock.ROFiles("/dev/null"),
        landlock.ROFiles(ffprobe),
        landlock.ROFiles(file),
    )

    cmd := exec.Command(ffprobe,"-print_format", "json","-show_streams", file)
    out, err := cmd.Output()
    if err != nil {
        fmt.Fprintf(os.Stderr, "ffprobe encountered an error.\nError JSON:\n%s\nError: %s", string(out), err.Error())
    }
}

This always returns

/usr/bin/ffprobe
ffprobe encountered an error.
Error JSON:

Error: fork/exec /usr/bin/ffprobe: permission denied

Using strace, I see it fails with epoll_ctl(4, EPOLL_CTL_ADD, 3, {events=EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLET, data={u32=1363365560, u64=139699469635256}}) = -1 EPERM (Operation not permitted)

Is this a limitation from Landlock? Or is this something from the Go implementation?

gnoack commented 8 months ago

The epoll_ctl clue is misleading here, "Permission denied" is the human-readable message for EACCES, not for EPERM (compare the errno(3) man page).

gnoack commented 8 months ago

The Permission denied error is in fact coming from the execve call:

[pid 10054] execve("/usr/bin/ffprobe", ["/usr/bin/ffprobe", "-print_format", "json", "-show_streams", "foo.mp3"], 0xc000114160 /* 41 vars */) = -1 EACCES (Permission denied)
gnoack commented 8 months ago

ffprobe depends on a lot of additional libraries, which you can query using ldd $(which ffprobe). These normally reside in /usr/lib (but it is technically configurable; the man page ld.so(8) goes into the more detail on the exact resolution of these. The most notable exception is probably that some systems have an additional lib64 directory to tell apart 32-bit and 64-bit libraries.)

If I add the following argument to the Landlock invocation, it starts working:

landlock.RODirs("/usr/lib"),

So while I can see that this resolves the issue, I have to admit that I'm slightly surprised that it is already execve which returns EACCES - that means that it's already the kernel which starts poking around in /usr/lib. I have so far been under the impression that glibc's startup routines were responsible for mmaping the shared libraries... So I would have expected the error slightly later in the execution, during the start of the program, but you would have to put the same additional Landlock parameter either way... :)

P.S.: Don't forget to check the error that is returned by RestrictPaths(). This can fail, for example if one of the passed file or directory paths does not exist. (If you want to ignore this error for individual paths that might or might not exist, you can use landlock.ROFiles("/usr/lib64").IgnoreIfMissing())

ikmckenz commented 8 months ago

Awesome, thank you for the pointers, this resolves my issue. If you don't mind my asking: How did you discover that epoll_ctl wasn't the source of the error, and the Permissions denied error was coming from execve, and that library loading was the thing to look at? I ran into the exact same issue when trying to sandbox the same code on OpenBSD with simultaneous pledge and unveil, and the resolution was the same (add /usr/lib/ et. al. to the unveil command), but on OpenBSD it was obvious from ktrace where the error was. Using strace on Linux didn't show me the exact error.

gnoack commented 8 months ago

A common mistake that I like to make is to forget the -f flag for strace. That one traces all child processes as well, which you need for tracing execve, because execve happens in the forked-off child.

Other than that I just had a vague memory that the error codes and their spelled-out human-readable forms are not exactly intuitive in this case... ;)

ikmckenz commented 8 months ago

Thank you!