Skip to content

Commit

Permalink
Landlock-Restrict all OS Threads at once (plus regression test).
Browse files Browse the repository at this point in the history
This addresses golandlock issue #5. The same issue has been previously
discussed in the context of `seccomp(2)` at
golang/go#3405.

Both `prctl()` and `landlock_restrict_self()` need to be invoked with
`syscall.AllThreadsSyscall()`, so that their thread-local effects get
applied to all OS threads managed by the Go runtime.
  • Loading branch information
gnoack committed Jul 24, 2021
1 parent b826b95 commit 9c85320
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 14 deletions.
4 changes: 2 additions & 2 deletions restrict.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,11 @@ func (v ABI) RestrictPaths(opts ...pathOpt) error {
}
}

if err := unix.Prctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
if err := ll.AllThreadsPrctl(unix.PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); err != nil {
return err
}

if err := ll.LandlockRestrictSelf(fd, 0); err != nil {
if err := ll.AllThreadsLandlockRestrictSelf(fd, 0); err != nil {
return fmt.Errorf("landlock_restrict_self: %w", err)
}
return nil
Expand Down
47 changes: 38 additions & 9 deletions restrict_test.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
package golandlock_test

import (
"errors"
"os"
"syscall"
"sync"
"testing"

"github.com/gnoack/golandlock"
)

// Make sure that after landlocking, the password file can't be read any more.
// XXX: Landlocking in the test itself makes it difficult to compose.
func TestAccessingPasswordFile(t *testing.T) {
// True if the given path can be opened for reading.
func canAccess(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
return true
}

// Verify that golandlock applies to all system threads that belong to
// the current Go process. The raw landlock_restrict_self syscall only
// applies to the current system thread, but these are managed by the
// Go runtime and not easily controlled. The same issue has already
// been discussed in the context of seccomp at
// https://github.com/golang/go/issues/3405.
func TestRestrictInPresenceOfThreading(t *testing.T) {
_, err := os.ReadFile("/etc/passwd")
if err != nil {
t.Skipf("expected normal accesses to /etc/passwd to work, got error: %v", err)
}

err = golandlock.V1.RestrictPaths(golandlock.RODirs("/tmp"))
err = golandlock.V1.RestrictPaths() // No access permitted at all.
if err != nil {
t.Skipf("kernel does not support Landlock v1; tests cannot be run.")
}

_, err = os.ReadFile("/etc/passwd")
if !errors.Is(err, syscall.EACCES) {
t.Errorf("expected that bar/a can't be read, got error: %v", err)
path := "/etc/passwd" // expected to exist and be openable

var wg sync.WaitGroup
defer wg.Wait()

const (
parallelism = 3
attempts = 10
)
for g := 0; g < parallelism; g++ {
wg.Add(1)
go func(grIdx int) {
defer wg.Done()
for i := 0; i < attempts; i++ {
if canAccess(path) {
t.Errorf("os.Open(%q): expected access denied, but it worked (goroutine %d, attempt %d)", path, grIdx, i)
}
}
}(g)
}
}
15 changes: 12 additions & 3 deletions syscall/landlock.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,18 @@ func LandlockAddRule(rulesetFd int, ruleType int, ruleAttr unsafe.Pointer, flags
return
}

// LandlockRestrictSelf enforces the given ruleset on the calling thread.
func LandlockRestrictSelf(rulesetFd int, flags int) (err error) {
_, _, e1 := syscall.Syscall(SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), uintptr(flags), 0)
// AllThreadsLandlockRestrictSelf enforces the given ruleset on the calling thread.
func AllThreadsLandlockRestrictSelf(rulesetFd int, flags int) (err error) {
_, _, e1 := syscall.AllThreadsSyscall(SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), uintptr(flags), 0)
if e1 != 0 {
err = syscall.Errno(e1)
}
return
}

// AllThreadsPrctl is like unix.Prctl, but gets applied on all OS threads at the same time.
func AllThreadsPrctl(option int, arg2 uintptr, arg3 uintptr, arg4 uintptr, arg5 uintptr) (err error) {
_, _, e1 := syscall.AllThreadsSyscall6(syscall.SYS_PRCTL, uintptr(option), uintptr(arg2), uintptr(arg3), uintptr(arg4), uintptr(arg5), 0)
if e1 != 0 {
err = syscall.Errno(e1)
}
Expand Down

0 comments on commit 9c85320

Please sign in to comment.