diff --git a/landlock/llrules/experimental.go b/landlock/llrules/experimental.go index 96ca7cb..b273853 100644 --- a/landlock/llrules/experimental.go +++ b/landlock/llrules/experimental.go @@ -1,17 +1,14 @@ -// Package llrules experimentally implements commonly used groups of -// Landlock rules. +// Package llrules implements commonly used groups of Landlock rules. +// +// This package is *experimental*. package llrules import "github.com/landlock-lsm/go-landlock/landlock" -func DNSOverTCP() landlock.Rule { - return landlock.CompositeRule(landlock.ConnectTCP(53), dnsFiles()) -} - -func DNSOverUDP() landlock.Rule { +func DNS() landlock.Rule { // UDP is not restrictable yet, but it can be added here once // Landlock can do that. - return dnsFiles() + return landlock.CompositeRule(landlock.ConnectTCP(53), dnsFiles()) } func dnsFiles() landlock.Rule { 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) +}