golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.8k stars 17.51k forks source link

proposal: syscall: support process sandboxing using Landlock on Linux #68595

Open edigaryev opened 1 month ago

edigaryev commented 1 month ago

Proposal Details

This is a continuation of https://github.com/golang/go/issues/47049, which was retracted with the following comment:

Treating this as retracted, but it seems like we reached a reasonable API change. What we don't know is whether it is good enough in practice or whether anyone needs it. If someone does need it, please feel free to open a new proposal and we can continue this discussion. Thanks.

I have a Golang binary that gets deployed once and then it os/exec's the git binary with various arguments under the hood hundreds of thousands of times to process lots of untrusted repositories on the internet.

This git invocation ideally should only access a single temporary directory that we create for it and doesn't need anything else, yet the invoking Golang binary needs an access to much more files and directories, including the networking. So basically, I'm looking to exercise the least privilege principle here.

Unfortunately due to Go's runtime and the use of threads, it seems to be somewhat complicated to use fork(2), and even if it would work, it would mean re-inventing the os/exec.

It'd be great if one could do something like this on instead:

cmd.SysProcAttr = &syscall.SysProcAttr{
    UseLandlock:     true,
    LandlockRuleset: fd,
}

There's a nice github.com/landlock-lsm/go-landlock package with a very ergonomic API that can be adapted to emit the ruleset file-descriptor instead of restricting the current process with a couple of line changes.

To get the fd for the *syscall.SysProcAttr above, the user might do something like this:

fd, err := landlock.V5.BestEffort().OnlyPaths().Ruleset(
    landlock.ROFiles("/usr"),
    landlock.RWFiles(tmpDir),
)

/cc @rsc

gabyhelp commented 1 month ago

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

edigaryev commented 1 month ago

I've actually toyed a bit with the Golang's source code, and this patch seems to work just fine on GOARCH=amd64:

diff --git a/src/syscall/exec_linux.go b/src/syscall/exec_linux.go
index e4b9ce1bf4..6359b129ff 100644
--- a/src/syscall/exec_linux.go
+++ b/src/syscall/exec_linux.go
@@ -109,6 +109,9 @@ type SysProcAttr struct {
        // functionality is supported by the kernel, or -1. Note *PidFD is
        // changed only if the process starts successfully.
        PidFD *int
+       // Landlock LSM
+       UseLandlock     bool
+       LandlockRuleset int
 }

 var (
@@ -645,6 +648,19 @@ func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, att
                }
        }

+       // Landlock LSM
+       if sys.UseLandlock {
+               _, _, err1 = RawSyscall(SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0)
+               if err1 != 0 {
+                       goto childerror
+               }
+
+               _, _, err1 = RawSyscall(SYS_LANDLOCK_RESTRICT_SELF, uintptr(sys.LandlockRuleset), 0, 0)
+               if err1 != 0 {
+                       goto childerror
+               }
+       }
+
        // Time to exec.
        _, _, err1 = RawSyscall(SYS_EXECVE,
                uintptr(unsafe.Pointer(argv0)),
diff --git a/src/syscall/zsysnum_linux_amd64.go b/src/syscall/zsysnum_linux_amd64.go
index 576c7c36a6..5b32beb830 100644
--- a/src/syscall/zsysnum_linux_amd64.go
+++ b/src/syscall/zsysnum_linux_amd64.go
@@ -309,4 +309,5 @@ const (
        SYS_FANOTIFY_INIT          = 300
        SYS_FANOTIFY_MARK          = 301
        SYS_PRLIMIT64              = 302
+       SYS_LANDLOCK_RESTRICT_SELF = 446
 )

This clearly needs more documentation and a thorough analysis of whether (1) the placement of Landlock LSM invocation is correct and whether (2) the ruleset FD won't be closed at the point of landlock_restrict_self(2) invocation.

But besides that, the system call constant values defined in src/syscall/zsysnum_linux_*.go seem to be wildly outdated.

To implement the support for all architectures, one only needs a single SYS_LANDLOCK_RESTRICT_SELF system call constant, but mksysnum_linux.pl seems to generate a bunch of other (unneeded) definitions too.

What is the correct approach to add new system call definitions in this case? As far as I understand it's not possible to use golang.org/x/sys/unix (which has this definition) from core Golang's repository.

ianlancetaylor commented 1 month ago

New system calls needed by the standard library are put into internal/syscall/unix.