Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
edigaryev opened this issue Jul 25, 2024 · 3 comments
Open
Labels
Milestone

Comments

@edigaryev
Copy link

Proposal Details

This is a continuation of #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

@gopherbot gopherbot added this to the Proposal milestone Jul 25, 2024
@gabyhelp
Copy link

Related Issues and Documentation

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

@edigaryev
Copy link
Author

edigaryev commented Jul 25, 2024

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 ianlancetaylor moved this to Incoming in Proposals Jul 25, 2024
@ianlancetaylor
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

4 participants