Skip to content

Commit

Permalink
Hide Config.handledAccessFS again, and make it settable through a con…
Browse files Browse the repository at this point in the history
…structor.

The test in `tests/customconfig/config_test.go` has a usage example.

Fixes #12.
  • Loading branch information
gnoack committed Aug 28, 2021
1 parent e80545a commit 4e4877f
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 33 deletions.
49 changes: 41 additions & 8 deletions landlock/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,57 @@ const (
var (
// Landlock V1 support (basic file operations).
V1 = Config{
HandledAccessFS: abiInfos[1].supportedAccessFS,
handledAccessFS: abiInfos[1].supportedAccessFS,
}
)

// The Landlock configuration describes the desired set of
// landlockable operations to be restricted and the constraints on it
// (e.g. best effort mode).
type Config struct {
// File system operations to restrict when enabling Landlock.
// Needs to stay within the bounds of what go-landlock supports.
HandledAccessFS AccessFSSet
handledAccessFS AccessFSSet
bestEffort bool
}

// NewConfig creates a new Landlock configuration with the given parameters.
//
// Passing an AccessFSSet will set that as the set of file system
// operations to restrict when enabling Landlock. The AccessFSSet
// needs to stay within the bounds of what go-landlock supports.
func NewConfig(args ...interface{}) (*Config, error) {
// Implementation note: This factory is written with future
// extensibility in mind. Only specific types are supported as
// input, but in the future more might be added.
var c Config
for _, arg := range args {
if afs, ok := arg.(AccessFSSet); ok {
if !c.handledAccessFS.isEmpty() {
return nil, errors.New("only one AccessFSSet may be provided")
}
if !afs.valid() {
return nil, errors.New("unsupported AccessFSSet value; upgrade go-landlock?")
}
c.handledAccessFS = afs
} else {
return nil, fmt.Errorf("unknown argument %v; only AccessFSSet-type argument is supported", arg)
}
}
return &c, nil
}

// MustConfig is like NewConfig but panics on error.
func MustConfig(args ...interface{}) Config {

This comment has been minimized.

Copy link
@l0kod

l0kod Aug 30, 2021

Member

Is there a use case outside of tests to expose MustConfig? That seems dangerous for users.

This comment has been minimized.

Copy link
@gnoack

gnoack Aug 30, 2021

Author Collaborator

It's a Go convention that functions called "Must" will panic (there are plenty cases in the standard library), I'm not so concerned that people will call it by accident.

The use case is just convenience for simple uses. It's safe to do as long as it's invoked with a set of arguments that's fixed at compile time (and the developer has tried it once ;)).

c, err := NewConfig(args...)
if err != nil {
panic(err)
}
return *c
}

// validate returns success when the given config is supported by
// go-landlock. (It may still be unsupported by your kernel though.)
func (c Config) validate() error {
if !c.HandledAccessFS.valid() {
if !c.handledAccessFS.valid() {
return errors.New("unsupported HandledAccessFS value")
}
return nil
Expand All @@ -58,13 +91,13 @@ func (c Config) validate() error {
func (c Config) String() string {
abi := abiInfo{version: -1} // invalid
for _, a := range abiInfos {
if c.HandledAccessFS.isSubset(a.supportedAccessFS) {
if c.handledAccessFS.isSubset(a.supportedAccessFS) {
abi = a
}
}

var desc = c.HandledAccessFS.String()
if abi.supportedAccessFS == c.HandledAccessFS {
var desc = c.handledAccessFS.String()
if abi.supportedAccessFS == c.handledAccessFS {
desc = "all"
}

Expand Down
43 changes: 36 additions & 7 deletions landlock/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ func TestConfigString(t *testing.T) {
want string
}{
{
cfg: Config{HandledAccessFS: 0},
cfg: Config{handledAccessFS: 0},
want: fmt.Sprintf("{Landlock V1; HandledAccessFS: %v}", AccessFSSet(0)),
},
{
cfg: Config{HandledAccessFS: ll.AccessFSWriteFile},
cfg: Config{handledAccessFS: ll.AccessFSWriteFile},
want: "{Landlock V1; HandledAccessFS: {WriteFile}}",
},
{
Expand All @@ -29,7 +29,7 @@ func TestConfigString(t *testing.T) {
want: "{Landlock V1; HandledAccessFS: all (best effort)}",
},
{
cfg: Config{HandledAccessFS: 1<<63},
cfg: Config{handledAccessFS: 1 << 63},
want: "{Landlock V???; HandledAccessFS: {1<<63} (unsupported HandledAccessFS value)}",
},
} {
Expand All @@ -43,8 +43,8 @@ func TestConfigString(t *testing.T) {
func TestValidateSuccess(t *testing.T) {
for _, c := range []Config{
V1, V1.BestEffort(),
Config{HandledAccessFS: ll.AccessFSWriteFile},
Config{HandledAccessFS: 0},
Config{handledAccessFS: ll.AccessFSWriteFile},
Config{handledAccessFS: 0},
} {
err := c.validate()
if err != nil {
Expand All @@ -55,12 +55,41 @@ func TestValidateSuccess(t *testing.T) {

func TestValidateFailure(t *testing.T) {
for _, c := range []Config{
Config{HandledAccessFS: 0xffffffffffffffff},
Config{HandledAccessFS: highestKnownABIVersion.supportedAccessFS + 1},
Config{handledAccessFS: 0xffffffffffffffff},
Config{handledAccessFS: highestKnownABIVersion.supportedAccessFS + 1},
} {
err := c.validate()
if err == nil {
t.Errorf("%v.validate(): expected error, got success", c)
}
}
}

func TestNewConfig(t *testing.T) {
c, err := NewConfig(AccessFSSet(ll.AccessFSWriteFile))
if err != nil {
t.Errorf("NewConfig(): expected success, got %v", err)
}
want := AccessFSSet(ll.AccessFSWriteFile)
if c.handledAccessFS != want {
t.Errorf("c.handledAccessFS = %v, want %v", c.handledAccessFS, want)
}
}

func TestNewConfigFailures(t *testing.T) {
for _, args := range [][]interface{}{
{ll.AccessFSWriteFile},
{123},
{"a string"},
{"foo", 42},
// May not specify two AccessFSSets
{AccessFSSet(ll.AccessFSWriteFile), AccessFSSet(ll.AccessFSReadFile)},
// May not specify an unsupported AccessFSSet value
{AccessFSSet(1 << 63)},
} {
_, err := NewConfig(args...)
if err == nil {
t.Errorf("NewConfig(%v) success, expected error", args)
}
}
}
4 changes: 2 additions & 2 deletions landlock/restrict.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import (
func restrictPaths(c Config, opts ...PathOpt) error {
err := c.validate()
if err != nil {
return fmt.Errorf("unsupported Landlock config %v (upgrade go-landlock?): %v", c, err)
return bug(fmt.Errorf("unsupported Landlock config %v: %v", c, err))
}
handledAccessFS := c.HandledAccessFS
handledAccessFS := c.handledAccessFS
abi := getSupportedABIVersion()
if c.bestEffort {
handledAccessFS = handledAccessFS.intersect(abi.supportedAccessFS)
Expand Down
43 changes: 43 additions & 0 deletions tests/customconfig/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package landlock_test

import (
"os"
"testing"

"github.com/landlock-lsm/go-landlock/landlock"
ll "github.com/landlock-lsm/go-landlock/landlock/syscall"
)

// 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
}

func TestCustomConfig(t *testing.T) {
if !canAccess("/etc/passwd") {
t.Skipf("expected normal accesses to /etc/passwd to work")
}

if !canAccess("/etc/group") {
t.Skipf("expected normal accesses to /etc/group to work")
}

readFile := landlock.AccessFSSet(ll.AccessFSReadFile)
if err := landlock.MustConfig(readFile).RestrictPaths(
landlock.PathAccess(readFile, "/etc/passwd"),
); err != nil {
t.Fatalf("Could not restrict paths: %v", err)
}

if !canAccess("/etc/passwd") {
t.Error("expected to have read access to /etc/passwd, but didn't")
}
if canAccess("/etc/group") {
t.Error("expected to have NO read access to /etc/group, but did")
}
}
12 changes: 0 additions & 12 deletions tests/failure/failure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,3 @@ func TestEmptyAccessRights(t *testing.T) {
t.Errorf("expected error message with «empty access rights», got: %v", err)
}
}

func TestSpecifiedTooManyFlags(t *testing.T) {
cfg := landlock.Config{HandledAccessFS: 1 << 13}
err := cfg.RestrictPaths()
if err == nil {
t.Errorf("%v.RestrictPaths(): expected error, got success", cfg)
}
want := "unsupported Landlock config"
if !strings.Contains(err.Error(), want) {
t.Errorf("%v.RestrictPaths(): expected error with %q, got %q", cfg, want, err.Error())
}
}
7 changes: 3 additions & 4 deletions tests/restrict/restrict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@ func canAccess(path string) bool {
// 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)
if !canAccess("/etc/passwd") {
t.Skipf("expected normal accesses to /etc/passwd to work")
}

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

0 comments on commit 4e4877f

Please sign in to comment.