diff --git a/landlock/composite_opt.go b/landlock/composite_opt.go new file mode 100644 index 0000000..c5b5491 --- /dev/null +++ b/landlock/composite_opt.go @@ -0,0 +1,46 @@ +package landlock + +type compositeRule struct { + rules []Rule +} + +func (c *compositeRule) compatibleWithConfig(cfg Config) bool { + for _, r := range c.rules { + if !r.compatibleWithConfig(cfg) { + return false + } + } + return true +} + +func (c *compositeRule) downgrade(cfg Config) (out Rule, ok bool) { + cr := new(compositeRule) + for _, r := range c.rules { + r, ok := r.downgrade(cfg) + if !ok { + return nil, false + } + cr.rules = append(cr.rules, r) + } + return cr, true +} + +func (c *compositeRule) addToRuleset(rulesetFD int, cfg Config) error { + for _, r := range c.rules { + err := r.addToRuleset(rulesetFD, cfg) + if err != nil { + return err + } + } + return nil +} + +// CompositeRule returns a rule composed of sub-rules. +// +// A composite rule passed to [Restrict] behaves the same as passing +// all sub-rules individually. Composite rules are not strictly +// necessary in Go-Landlock, but useful for building libraries of +// re-usable Landlock rules. +func CompositeRule(rules ...Rule) Rule { + return &compositeRule{rules: rules} +} diff --git a/landlock/llrules/experimental.go b/landlock/llrules/experimental.go new file mode 100644 index 0000000..b273853 --- /dev/null +++ b/landlock/llrules/experimental.go @@ -0,0 +1,32 @@ +// Package llrules implements commonly used groups of Landlock rules. +// +// This package is *experimental*. +package llrules + +import "github.com/landlock-lsm/go-landlock/landlock" + +func DNS() landlock.Rule { + // UDP is not restrictable yet, but it can be added here once + // Landlock can do that. + return landlock.CompositeRule(landlock.ConnectTCP(53), dnsFiles()) +} + +func dnsFiles() landlock.Rule { + return landlock.ROFiles( + "/etc/hosts", + "/etc/resolv.conf", + ).IgnoreIfMissing() +} + +func SharedLibraries() landlock.Rule { + // XXX: How does the linker look up this list of paths? + // XXX: Use more specific rulesets. + return landlock.RODirs( + "/lib", + "/lib32", + "/lib64", + "/usr/lib", + "/usr/lib32", + "/usr/lib64", + ).IgnoreIfMissing() +} diff --git a/landlock/llrules/llrules_test.go b/landlock/llrules/llrules_test.go new file mode 100644 index 0000000..873d100 --- /dev/null +++ b/landlock/llrules/llrules_test.go @@ -0,0 +1,47 @@ +package llrules_test + +import ( + "context" + "net" + "testing" + + "github.com/landlock-lsm/go-landlock/landlock" + "github.com/landlock-lsm/go-landlock/landlock/llrules" + "github.com/landlock-lsm/go-landlock/landlock/lltest" +) + +func TestDNSOverTCP(t *testing.T) { + lltest.RunInSubprocess(t, func() { + err := landlock.V5.BestEffort().Restrict(llrules.DNS()) + if err != nil { + t.Fatalf("Enabling Landlock: %v", err) + } + + r := net.Resolver{ + PreferGo: true, + } + _, err = r.LookupHost(context.Background(), "localhost") + if err != nil { + t.Errorf("Unexpected DNS error: %v", err) + } + }) +} + +func TestDNSOverTCP_fail(t *testing.T) { + lltest.RequireABI(t, 1) + + lltest.RunInSubprocess(t, func() { + err := landlock.V5.BestEffort().Restrict() + if err != nil { + t.Fatalf("Enabling Landlock: %v", err) + } + + r := net.Resolver{ + PreferGo: true, + } + _, err = r.LookupHost(context.Background(), "localhost") + if err == nil { + t.Errorf("Expected DNS error, but got success") + } + }) +} diff --git a/landlock/rule_composite.go b/landlock/rule_composite.go new file mode 100644 index 0000000..705e74f --- /dev/null +++ b/landlock/rule_composite.go @@ -0,0 +1,49 @@ +package landlock + +import "fmt" + +type RuleGroup struct { + rules []Rule +} + +// GroupRules groups the given rules into a single Rule value. +// The result behaves the same in a Landlock restriction call +// as listing all of the individual rules separately. +func GroupRules(rules ...Rule) RuleGroup { + return RuleGroup{rules: rules} +} + +func (g RuleGroup) compatibleWithConfig(c Config) bool { + for _, r := range g.rules { + if !r.compatibleWithConfig(c) { + return false + } + } + return true +} + +func (g RuleGroup) downgrade(c Config) (out Rule, ok bool) { + rs := make([]Rule, 0, len(g.rules)) + for _, r := range g.rules { + r, ok := r.downgrade(c) + if !ok { + return GroupRules(), false + } + rs = append(rs, r) + } + return GroupRules(rs...), true +} + +func (g RuleGroup) addToRuleset(rulesetFD int, c Config) error { + for _, r := range g.rules { + err := r.addToRuleset(rulesetFD, c) + if err != nil { + return err + } + } + return nil +} + +func (g RuleGroup) String() string { + return fmt.Sprintf("rules: %v", g.rules) +}