diff --git a/landlock/accessfs.go b/landlock/accessfs.go index 8c2b0d9..0b38200 100644 --- a/landlock/accessfs.go +++ b/landlock/accessfs.go @@ -1,6 +1,25 @@ package landlock -import "strings" +import ( + "fmt" + "strings" +) + +var flagNames = []string{ + "Execute", + "WriteFile", + "ReadFile", + "ReadDir", + "RemoveDir", + "RemoveFile", + "MakeChar", + "MakeDir", + "MakeReg", + "MakeSock", + "MakeFifo", + "MakeBlock", + "MakeSym", +} // AccessFSSet is a set of Landlockable file system access operations. type AccessFSSet uint64 @@ -11,28 +30,18 @@ func (a AccessFSSet) String() string { } var b strings.Builder b.WriteByte('{') - for i, n := range []string{ - "Execute", - "WriteFile", - "ReadFile", - "ReadDir", - "RemoveDir", - "RemoveFile", - "MakeChar", - "MakeDir", - "MakeReg", - "MakeSock", - "MakeFifo", - "MakeBlock", - "MakeSym", - } { + for i := 0; i < 64; i++ { if a&(1< 1 { b.WriteByte(',') } - b.WriteString(n) + if i < len(flagNames) { + b.WriteString(flagNames[i]) + } else { + b.WriteString(fmt.Sprintf("1<<%v", i)) + } } b.WriteByte('}') return b.String() diff --git a/landlock/accessfs_test.go b/landlock/accessfs_test.go index 10649c1..1db51f2 100644 --- a/landlock/accessfs_test.go +++ b/landlock/accessfs_test.go @@ -46,6 +46,7 @@ func TestPrettyPrint(t *testing.T) { {a: ll.AccessFSMakeFifo, want: "{MakeFifo}"}, {a: ll.AccessFSMakeBlock, want: "{MakeBlock}"}, {a: ll.AccessFSMakeSym, want: "{MakeSym}"}, + {a: ll.AccessFSReadFile | 1<<63, want: "{ReadFile,1<<63}"}, } { got := tc.a.String() if got != tc.want { diff --git a/landlock/config.go b/landlock/config.go index 23c0d59..b94af44 100644 --- a/landlock/config.go +++ b/landlock/config.go @@ -31,14 +31,17 @@ 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. +// landlockable operations to be restricted and the constraints on it +// (e.g. best effort mode). type Config struct { - handledAccessFS AccessFSSet + // File system operations to restrict when enabling Landlock. + // Needs to stay within the bounds of what go-landlock supports. + HandledAccessFS AccessFSSet bestEffort bool } @@ -46,23 +49,23 @@ type Config struct { // go-landlock. (It may still be unsupported by your kernel though.) func (c Config) validate() error { safs := highestKnownABIVersion.supportedAccessFS - if !c.handledAccessFS.isSubset(safs) { - return errors.New("unsupported handledAccessFS value") + if !c.HandledAccessFS.isSubset(safs) { + return errors.New("unsupported HandledAccessFS value") } return nil } // String builds a human-readable representation of the Config. func (c Config) String() string { - var abi abiInfo + 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" } @@ -70,7 +73,19 @@ func (c Config) String() string { if c.bestEffort { bestEffort = " (best effort)" } - return fmt.Sprintf("{Landlock V%v; HandledAccessFS: %v%v}", abi.version, desc, bestEffort) + + var version string + if abi.version < 0 { + version = "V???" + } else { + version = fmt.Sprintf("V%v", abi.version) + } + + errStr := "" + if err := c.validate(); err != nil { + errStr = fmt.Sprintf(" (%v)", err) + } + return fmt.Sprintf("{Landlock %v; HandledAccessFS: %v%v%v}", version, desc, bestEffort, errStr) } // BestEffort returns a config that will opportunistically enforce diff --git a/landlock/config_test.go b/landlock/config_test.go index 5e39327..6cfb21d 100644 --- a/landlock/config_test.go +++ b/landlock/config_test.go @@ -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}}", }, { @@ -28,6 +28,10 @@ func TestConfigString(t *testing.T) { cfg: V1.BestEffort(), want: "{Landlock V1; HandledAccessFS: all (best effort)}", }, + { + cfg: Config{HandledAccessFS: 1<<63}, + want: "{Landlock V???; HandledAccessFS: {1<<63} (unsupported HandledAccessFS value)}", + }, } { got := tc.cfg.String() if got != tc.want { @@ -39,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 { @@ -51,8 +55,8 @@ 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 { diff --git a/landlock/restrict.go b/landlock/restrict.go index c3f7afe..d1591a2 100644 --- a/landlock/restrict.go +++ b/landlock/restrict.go @@ -15,7 +15,7 @@ func restrictPaths(c Config, opts ...PathOpt) error { if err != nil { return fmt.Errorf("unsupported Landlock config %v (upgrade go-landlock?): %v", c, err) } - handledAccessFS := c.handledAccessFS + handledAccessFS := c.HandledAccessFS abi := getSupportedABIVersion() if c.bestEffort { handledAccessFS = handledAccessFS.intersect(abi.supportedAccessFS) diff --git a/tests/failure/failure_test.go b/tests/failure/failure_test.go index 249224d..1ece352 100644 --- a/tests/failure/failure_test.go +++ b/tests/failure/failure_test.go @@ -47,3 +47,15 @@ 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()) + } +}