From ab4feda5baa4d7de82f9ca65dd4df30ca65e309a Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Sun, 14 Apr 2024 23:58:34 +0100 Subject: [PATCH] feat(aa): improve apparmor struct. --- pkg/aa/capability.go | 12 ++-- pkg/aa/change_profile.go | 22 +++--- pkg/aa/data_test.go | 33 ++++----- pkg/aa/dbus.go | 65 ++++++++++------- pkg/aa/file.go | 44 ++++++++---- pkg/aa/include.go | 28 -------- pkg/aa/io_uring.go | 18 +++-- pkg/aa/mount.go | 62 ++++++++-------- pkg/aa/mqueue.go | 27 ++++--- pkg/aa/network.go | 65 +++++++++-------- pkg/aa/pivot_root.go | 20 +++--- pkg/aa/preamble.go | 87 ++++++++++++++++++++++ pkg/aa/profile.go | 50 ++++++------- pkg/aa/profile_test.go | 30 ++++---- pkg/aa/ptrace.go | 14 ++-- pkg/aa/rlimit.go | 19 +++-- pkg/aa/rules.go | 108 +++++++++------------------- pkg/aa/rules_test.go | 128 +++++++++++++++++++-------------- pkg/aa/signal.go | 20 +++--- pkg/aa/templates/include.j2 | 1 + pkg/aa/templates/profile.j2 | 32 ++++++--- pkg/aa/templates/qualifier.j2 | 3 - pkg/aa/unix.go | 81 +++++++++++---------- pkg/aa/userns.go | 8 ++- pkg/aa/variables.go | 35 +++------ pkg/aa/variables_test.go | 88 +++++++++++++---------- pkg/logs/logs_test.go | 20 +++--- pkg/prebuild/directive/dbus.go | 14 ++-- 28 files changed, 638 insertions(+), 496 deletions(-) delete mode 100644 pkg/aa/include.go create mode 100644 pkg/aa/preamble.go diff --git a/pkg/aa/capability.go b/pkg/aa/capability.go index b65e8bd2d..292e3814b 100644 --- a/pkg/aa/capability.go +++ b/pkg/aa/capability.go @@ -5,23 +5,25 @@ package aa type Capability struct { + Rule Qualifier Name string } -func CapabilityFromLog(log map[string]string) ApparmorRule { +func newCapabilityFromLog(log map[string]string) *Capability { return &Capability{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), Name: log["capname"], } } func (r *Capability) Less(other any) bool { o, _ := other.(*Capability) - if r.Name == o.Name { - return r.Qualifier.Less(o.Qualifier) + if r.Name != o.Name { + return r.Name < o.Name } - return r.Name < o.Name + return r.Qualifier.Less(o.Qualifier) } func (r *Capability) Equals(other any) bool { diff --git a/pkg/aa/change_profile.go b/pkg/aa/change_profile.go index 610376eb4..eeb5f9734 100644 --- a/pkg/aa/change_profile.go +++ b/pkg/aa/change_profile.go @@ -5,15 +5,17 @@ package aa type ChangeProfile struct { + Rule Qualifier ExecMode string Exec string ProfileName string } -func ChangeProfileFromLog(log map[string]string) ApparmorRule { +func newChangeProfileFromLog(log map[string]string) *ChangeProfile { return &ChangeProfile{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), ExecMode: log["mode"], Exec: log["exec"], ProfileName: log["target"], @@ -22,16 +24,20 @@ func ChangeProfileFromLog(log map[string]string) ApparmorRule { func (r *ChangeProfile) Less(other any) bool { o, _ := other.(*ChangeProfile) - if r.ExecMode == o.ExecMode { - if r.Exec == o.Exec { - return r.ProfileName < o.ProfileName - } + if r.ExecMode != o.ExecMode { + return r.ExecMode < o.ExecMode + } + if r.Exec != o.Exec { return r.Exec < o.Exec } - return r.ExecMode < o.ExecMode + if r.ProfileName != o.ProfileName { + return r.ProfileName < o.ProfileName + } + return r.Qualifier.Less(o.Qualifier) } func (r *ChangeProfile) Equals(other any) bool { o, _ := other.(*ChangeProfile) - return r.ExecMode == o.ExecMode && r.Exec == o.Exec && r.ProfileName == o.ProfileName + return r.ExecMode == o.ExecMode && r.Exec == o.Exec && + r.ProfileName == o.ProfileName && r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/data_test.go b/pkg/aa/data_test.go index b3d01a70f..63aef6bb3 100644 --- a/pkg/aa/data_test.go +++ b/pkg/aa/data_test.go @@ -71,13 +71,13 @@ var ( "flags": "rw, rbind", } mount1 = &Mount{ - Qualifier: Qualifier{Comment: "failed perms check"}, + Rule: Rule{Comment: "failed perms check"}, MountConditions: MountConditions{FsType: "overlay"}, Source: "overlay", MountPoint: "/var/lib/docker/overlay2/opaque-bug-check1209538631/merged/", } mount2 = &Mount{ - Qualifier: Qualifier{Comment: "failed perms check"}, + Rule: Rule{Comment: "failed perms check"}, MountConditions: MountConditions{Options: []string{"rw", "rbind"}}, Source: "/oldroot/dev/tty", MountPoint: "/newroot/dev/tty", @@ -197,17 +197,17 @@ var ( "protocol": "0", } unix1 = &Unix{ - Access: "send receive", - Type: "stream", - Protocol: "0", - Address: "none", - Peer: "dbus-daemon", - PeerAddr: "@/tmp/dbus-AaKMpxzC4k", + Access: "send receive", + Type: "stream", + Protocol: "0", + Address: "none", + PeerAddr: "@/tmp/dbus-AaKMpxzC4k", + PeerLabel: "dbus-daemon", } unix2 = &Unix{ - Qualifier: Qualifier{FileInherit: true}, - Access: "receive", - Type: "stream", + Rule: Rule{FileInherit: true}, + Access: "receive", + Type: "stream", } // Dbus @@ -236,11 +236,11 @@ var ( dbus1 = &Dbus{ Access: "receive", Bus: "session", - Name: ":1.15", Path: "/org/gtk/vfs/metadata", Interface: "org.gtk.vfs.Metadata", Member: "Remove", - Label: "tracker-extract", + PeerName: ":1.15", + PeerLabel: "tracker-extract", } dbus2 = &Dbus{ Access: "bind", @@ -285,8 +285,9 @@ var ( } file1 = &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: "r"} file2 = &File{ - Qualifier: Qualifier{Owner: true, NoNewPrivs: true}, - Path: "@{PROC}/4163/cgroup", - Access: "r", + Rule: Rule{NoNewPrivs: true}, + Owner: true, + Path: "@{PROC}/4163/cgroup", + Access: "r", } ) diff --git a/pkg/aa/dbus.go b/pkg/aa/dbus.go index b2e32ba2b..1c43df88b 100644 --- a/pkg/aa/dbus.go +++ b/pkg/aa/dbus.go @@ -5,6 +5,7 @@ package aa type Dbus struct { + Rule Qualifier Access string Bus string @@ -12,45 +13,58 @@ type Dbus struct { Path string Interface string Member string - Label string + PeerName string + PeerLabel string } -func DbusFromLog(log map[string]string) ApparmorRule { +func newDbusFromLog(log map[string]string) *Dbus { + name := "" + peerName := "" + if log["mask"] == "bind" { + name = log["name"] + } else { + peerName = log["name"] + } return &Dbus{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), Access: log["mask"], Bus: log["bus"], - Name: log["name"], + Name: name, Path: log["path"], Interface: log["interface"], Member: log["member"], - Label: log["peer_label"], + PeerName: peerName, + PeerLabel: log["peer_label"], } } func (r *Dbus) Less(other any) bool { o, _ := other.(*Dbus) - if r.Qualifier.Equals(o.Qualifier) { - if r.Access == o.Access { - if r.Bus == o.Bus { - if r.Name == o.Name { - if r.Path == o.Path { - if r.Interface == o.Interface { - if r.Member == o.Member { - return r.Label < o.Label - } - return r.Member < o.Member - } - return r.Interface < o.Interface - } - return r.Path < o.Path - } - return r.Name < o.Name - } - return r.Bus < o.Bus - } + if r.Access != o.Access { return r.Access < o.Access } + if r.Bus != o.Bus { + return r.Bus < o.Bus + } + if r.Name != o.Name { + return r.Name < o.Name + } + if r.Path != o.Path { + return r.Path < o.Path + } + if r.Interface != o.Interface { + return r.Interface < o.Interface + } + if r.Member != o.Member { + return r.Member < o.Member + } + if r.PeerName != o.PeerName { + return r.PeerName < o.PeerName + } + if r.PeerLabel != o.PeerLabel { + return r.PeerLabel < o.PeerLabel + } return r.Qualifier.Less(o.Qualifier) } @@ -58,5 +72,6 @@ func (r *Dbus) Equals(other any) bool { o, _ := other.(*Dbus) return r.Access == o.Access && r.Bus == o.Bus && r.Name == o.Name && r.Path == o.Path && r.Interface == o.Interface && - r.Member == o.Member && r.Label == o.Label && r.Qualifier.Equals(o.Qualifier) + r.Member == o.Member && r.PeerName == o.PeerName && + r.PeerLabel == o.PeerLabel && r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/file.go b/pkg/aa/file.go index c83322e86..ec16e54cb 100644 --- a/pkg/aa/file.go +++ b/pkg/aa/file.go @@ -5,15 +5,26 @@ package aa type File struct { + Rule Qualifier + Owner bool Path string Access string Target string } -func FileFromLog(log map[string]string) ApparmorRule { +func newFileFromLog(log map[string]string) *File { + owner := false + fsuid, hasFsUID := log["fsuid"] + ouid, hasOuUID := log["ouid"] + isDbus := strings.Contains(log["operation"], "dbus") + if hasFsUID && hasOuUID && fsuid == ouid && ouid != "0" && !isDbus { + owner = true + } return &File{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), + Owner: owner, Path: log["name"], Access: toAccess(log["requested_mask"]), Target: log["target"], @@ -24,23 +35,26 @@ func (r *File) Less(other any) bool { o, _ := other.(*File) letterR := getLetterIn(fileAlphabet, r.Path) letterO := getLetterIn(fileAlphabet, o.Path) - if fileWeights[letterR] == fileWeights[letterO] || letterR == "" || letterO == "" { - if r.Qualifier.Equals(o.Qualifier) { - if r.Path == o.Path { - if r.Access == o.Access { - return r.Target < o.Target - } - return r.Access < o.Access - } - return r.Path < o.Path - } - return r.Qualifier.Less(o.Qualifier) + if fileWeights[letterR] != fileWeights[letterO] && letterR != "" && letterO != "" { + return fileWeights[letterR] < fileWeights[letterO] } - return fileWeights[letterR] < fileWeights[letterO] + if r.Path != o.Path { + return r.Path < o.Path + } + if r.Access != o.Access { + return r.Access < o.Access + } + if r.Target != o.Target { + return r.Target < o.Target + } + if o.Owner != r.Owner { + return r.Owner + } + return r.Qualifier.Less(o.Qualifier) } func (r *File) Equals(other any) bool { o, _ := other.(*File) - return r.Path == o.Path && r.Access == o.Access && + return r.Path == o.Path && r.Access == o.Access && r.Owner == o.Owner && r.Target == o.Target && r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/include.go b/pkg/aa/include.go deleted file mode 100644 index b3aec7a9a..000000000 --- a/pkg/aa/include.go +++ /dev/null @@ -1,28 +0,0 @@ -// apparmor.d - Full set of apparmor profiles -// Copyright (C) 2021-2024 Alexandre Pujol -// SPDX-License-Identifier: GPL-2.0-only - -package aa - -type Include struct { - IfExists bool - Path string - IsMagic bool -} - -func (r *Include) Less(other any) bool { - o, _ := other.(*Include) - if r.Path == o.Path { - if r.IsMagic == o.IsMagic { - return r.IfExists - } - return r.IsMagic - } - return r.Path < o.Path -} - -func (r *Include) Equals(other any) bool { - o, _ := other.(*Include) - return r.Path == o.Path && r.IsMagic == o.IsMagic && - r.IfExists == o.IfExists -} diff --git a/pkg/aa/io_uring.go b/pkg/aa/io_uring.go index 62507bf73..083705641 100644 --- a/pkg/aa/io_uring.go +++ b/pkg/aa/io_uring.go @@ -5,19 +5,29 @@ package aa type IOUring struct { + Rule Qualifier Access string Label string } +func newIOUringFromLog(log map[string]string) *IOUring { + return &IOUring{ + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), + Access: toAccess(log["requested"]), + Label: log["label"], + } +} + func (r *IOUring) Less(other any) bool { o, _ := other.(*IOUring) - if r.Qualifier.Equals(o.Qualifier) { - if r.Access == o.Access { - return r.Label < o.Label - } + if r.Access != o.Access { return r.Access < o.Access } + if r.Label != o.Label { + return r.Label < o.Label + } return r.Qualifier.Less(o.Qualifier) } diff --git a/pkg/aa/mount.go b/pkg/aa/mount.go index 35875c4a3..81938097b 100644 --- a/pkg/aa/mount.go +++ b/pkg/aa/mount.go @@ -15,7 +15,7 @@ type MountConditions struct { Options []string } -func MountConditionsFromLog(log map[string]string) MountConditions { +func newMountConditionsFromLog(log map[string]string) MountConditions { if _, present := log["flags"]; present { return MountConditions{ FsType: log["fstype"], @@ -26,10 +26,10 @@ func MountConditionsFromLog(log map[string]string) MountConditions { } func (m MountConditions) Less(other MountConditions) bool { - if m.FsType == other.FsType { - return len(m.Options) < len(other.Options) + if m.FsType != other.FsType { + return m.FsType < other.FsType } - return m.FsType < other.FsType + return len(m.Options) < len(other.Options) } func (m MountConditions) Equals(other MountConditions) bool { @@ -37,16 +37,18 @@ func (m MountConditions) Equals(other MountConditions) bool { } type Mount struct { + Rule Qualifier MountConditions Source string MountPoint string } -func MountFromLog(log map[string]string) ApparmorRule { +func newMountFromLog(log map[string]string) *Mount { return &Mount{ - Qualifier: NewQualifierFromLog(log), - MountConditions: MountConditionsFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), + MountConditions: newMountConditionsFromLog(log), Source: log["srcname"], MountPoint: log["name"], } @@ -54,15 +56,15 @@ func MountFromLog(log map[string]string) ApparmorRule { func (r *Mount) Less(other any) bool { o, _ := other.(*Mount) - if r.Qualifier.Equals(o.Qualifier) { - if r.Source == o.Source { - if r.MountPoint == o.MountPoint { - return r.MountConditions.Less(o.MountConditions) - } - return r.MountPoint < o.MountPoint - } + if r.Source != o.Source { return r.Source < o.Source } + if r.MountPoint != o.MountPoint { + return r.MountPoint < o.MountPoint + } + if r.MountConditions.Equals(o.MountConditions) { + return r.MountConditions.Less(o.MountConditions) + } return r.Qualifier.Less(o.Qualifier) } @@ -74,27 +76,29 @@ func (r *Mount) Equals(other any) bool { } type Umount struct { + Rule Qualifier MountConditions MountPoint string } -func UmountFromLog(log map[string]string) ApparmorRule { +func newUmountFromLog(log map[string]string) *Umount { return &Umount{ - Qualifier: NewQualifierFromLog(log), - MountConditions: MountConditionsFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), + MountConditions: newMountConditionsFromLog(log), MountPoint: log["name"], } } func (r *Umount) Less(other any) bool { o, _ := other.(*Umount) - if r.Qualifier.Equals(o.Qualifier) { - if r.MountPoint == o.MountPoint { - return r.MountConditions.Less(o.MountConditions) - } + if r.MountPoint != o.MountPoint { return r.MountPoint < o.MountPoint } + if r.MountConditions.Equals(o.MountConditions) { + return r.MountConditions.Less(o.MountConditions) + } return r.Qualifier.Less(o.Qualifier) } @@ -106,27 +110,29 @@ func (r *Umount) Equals(other any) bool { } type Remount struct { + Rule Qualifier MountConditions MountPoint string } -func RemountFromLog(log map[string]string) ApparmorRule { +func newRemountFromLog(log map[string]string) *Remount { return &Remount{ - Qualifier: NewQualifierFromLog(log), - MountConditions: MountConditionsFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), + MountConditions: newMountConditionsFromLog(log), MountPoint: log["name"], } } func (r *Remount) Less(other any) bool { o, _ := other.(*Remount) - if r.Qualifier.Equals(o.Qualifier) { - if r.MountPoint == o.MountPoint { - return r.MountConditions.Less(o.MountConditions) - } + if r.MountPoint != o.MountPoint { return r.MountPoint < o.MountPoint } + if r.MountConditions.Equals(o.MountConditions) { + return r.MountConditions.Less(o.MountConditions) + } return r.Qualifier.Less(o.Qualifier) } diff --git a/pkg/aa/mqueue.go b/pkg/aa/mqueue.go index 03a52bdfb..6afba37ff 100644 --- a/pkg/aa/mqueue.go +++ b/pkg/aa/mqueue.go @@ -4,9 +4,12 @@ package aa -import "strings" +import ( + "strings" +) type Mqueue struct { + Rule Qualifier Access string Type string @@ -14,7 +17,7 @@ type Mqueue struct { Name string } -func MqueueFromLog(log map[string]string) ApparmorRule { +func newMqueueFromLog(log map[string]string) *Mqueue { mqueueType := "posix" if strings.Contains(log["class"], "posix") { mqueueType = "posix" @@ -22,7 +25,8 @@ func MqueueFromLog(log map[string]string) ApparmorRule { mqueueType = "sysv" } return &Mqueue{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), Access: toAccess(log["requested"]), Type: mqueueType, Label: log["label"], @@ -32,19 +36,20 @@ func MqueueFromLog(log map[string]string) ApparmorRule { func (r *Mqueue) Less(other any) bool { o, _ := other.(*Mqueue) - if r.Qualifier.Equals(o.Qualifier) { - if r.Access == o.Access { - if r.Type == o.Type { - return r.Label < o.Label - } - return r.Type < o.Type - } + if r.Access != o.Access { return r.Access < o.Access } + if r.Type != o.Type { + return r.Type < o.Type + } + if r.Label != o.Label { + return r.Label < o.Label + } return r.Qualifier.Less(o.Qualifier) } func (r *Mqueue) Equals(other any) bool { o, _ := other.(*Mqueue) - return r.Access == o.Access && r.Type == o.Type && r.Label == o.Label && r.Qualifier.Equals(o.Qualifier) + return r.Access == o.Access && r.Type == o.Type && r.Label == o.Label && + r.Name == o.Name && r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/network.go b/pkg/aa/network.go index 74b0c7633..b23bdf719 100644 --- a/pkg/aa/network.go +++ b/pkg/aa/network.go @@ -10,54 +10,63 @@ type AddressExpr struct { Port string } +func newAddressExprFromLog(log map[string]string) AddressExpr { + return AddressExpr{ + Source: log["laddr"], + Destination: log["faddr"], + Port: log["lport"], + } +} + +func (r AddressExpr) Less(other AddressExpr) bool { + if r.Source != other.Source { + return r.Source < other.Source + } + if r.Destination != other.Destination { + return r.Destination < other.Destination + } + return r.Port < other.Port +} + func (r AddressExpr) Equals(other AddressExpr) bool { return r.Source == other.Source && r.Destination == other.Destination && r.Port == other.Port } -func (r AddressExpr) Less(other AddressExpr) bool { - if r.Source == other.Source { - if r.Destination == other.Destination { - return r.Port < other.Port - } - return r.Destination < other.Destination - } - return r.Source < other.Source -} - type Network struct { + Rule Qualifier + AddressExpr Domain string Type string Protocol string - AddressExpr } -func NetworkFromLog(log map[string]string) ApparmorRule { +func newNetworkFromLog(log map[string]string) *Network { return &Network{ - Qualifier: NewQualifierFromLog(log), - AddressExpr: AddressExpr{ - Source: log["laddr"], - Destination: log["faddr"], - Port: log["lport"], - }, - Domain: log["family"], - Type: log["sock_type"], - Protocol: log["protocol"], + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), + AddressExpr: newAddressExprFromLog(log), + Domain: log["family"], + Type: log["sock_type"], + Protocol: log["protocol"], } } func (r *Network) Less(other any) bool { o, _ := other.(*Network) - if r.Qualifier.Equals(o.Qualifier) { - if r.Domain == o.Domain { - if r.Type == o.Type { - return r.Protocol < o.Protocol - } - return r.Type < o.Type - } + if r.Domain != o.Domain { return r.Domain < o.Domain } + if r.Type != o.Type { + return r.Type < o.Type + } + if r.Protocol != o.Protocol { + return r.Protocol < o.Protocol + } + if r.AddressExpr.Less(o.AddressExpr) { + return r.AddressExpr.Less(o.AddressExpr) + } return r.Qualifier.Less(o.Qualifier) } diff --git a/pkg/aa/pivot_root.go b/pkg/aa/pivot_root.go index 05e3d0a53..13979ca36 100644 --- a/pkg/aa/pivot_root.go +++ b/pkg/aa/pivot_root.go @@ -5,15 +5,17 @@ package aa type PivotRoot struct { + Rule Qualifier OldRoot string NewRoot string TargetProfile string } -func PivotRootFromLog(log map[string]string) ApparmorRule { +func newPivotRootFromLog(log map[string]string) *PivotRoot { return &PivotRoot{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), OldRoot: log["srcname"], NewRoot: log["name"], TargetProfile: "", @@ -22,15 +24,15 @@ func PivotRootFromLog(log map[string]string) ApparmorRule { func (r *PivotRoot) Less(other any) bool { o, _ := other.(*PivotRoot) - if r.Qualifier.Equals(o.Qualifier) { - if r.OldRoot == o.OldRoot { - if r.NewRoot == o.NewRoot { - return r.TargetProfile < o.TargetProfile - } - return r.NewRoot < o.NewRoot - } + if r.OldRoot != o.OldRoot { return r.OldRoot < o.OldRoot } + if r.NewRoot != o.NewRoot { + return r.NewRoot < o.NewRoot + } + if r.TargetProfile != o.TargetProfile { + return r.TargetProfile < o.TargetProfile + } return r.Qualifier.Less(o.Qualifier) } diff --git a/pkg/aa/preamble.go b/pkg/aa/preamble.go new file mode 100644 index 000000000..00f5042f1 --- /dev/null +++ b/pkg/aa/preamble.go @@ -0,0 +1,87 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package aa + +import ( + "slices" +) + +type Abi struct { + Rule + Path string + IsMagic bool +} + +func (r *Abi) Less(other any) bool { + o, _ := other.(*Abi) + if r.Path != o.Path { + return r.Path < o.Path + } + return r.IsMagic == o.IsMagic +} + +func (r *Abi) Equals(other any) bool { + o, _ := other.(*Abi) + return r.Path == o.Path && r.IsMagic == o.IsMagic +} + +type Alias struct { + Rule + Path string + RewrittenPath string +} + +func (r Alias) Less(other any) bool { + o, _ := other.(*Alias) + if r.Path != o.Path { + return r.Path < o.Path + } + return r.RewrittenPath < o.RewrittenPath +} + +func (r Alias) Equals(other any) bool { + o, _ := other.(*Alias) + return r.Path == o.Path && r.RewrittenPath == o.RewrittenPath +} + +type Include struct { + Rule + IfExists bool + Path string + IsMagic bool +} + +func (r *Include) Less(other any) bool { + o, _ := other.(*Include) + if r.Path == o.Path { + return r.Path < o.Path + } + if r.IsMagic != o.IsMagic { + return r.IsMagic + } + return r.IfExists +} + +func (r *Include) Equals(other any) bool { + o, _ := other.(*Include) + return r.Path == o.Path && r.IsMagic == o.IsMagic && r.IfExists == o.IfExists +} + +type Variable struct { + Rule + Name string + Values []string +} + +func (r *Variable) Less(other Variable) bool { + if r.Name != other.Name { + return r.Name < other.Name + } + return len(r.Values) < len(other.Values) +} + +func (r *Variable) Equals(other Variable) bool { + return r.Name == other.Name && slices.Equal(r.Values, other.Values) +} diff --git a/pkg/aa/profile.go b/pkg/aa/profile.go index 2e97b9d44..48596aca4 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -31,10 +31,10 @@ type AppArmorProfile struct { // Preamble section of a profile type Preamble struct { - Abi []Abi - Includes []Include - Aliases []Alias - Variables []Variable + Abi []*Abi + Includes []*Include + Aliases []*Alias + Variables []*Variable } // Profile section of a profile @@ -78,7 +78,7 @@ func (p *AppArmorProfile) AddRule(log map[string]string) { } case "-13": if strings.Contains(log["info"], "namespace creation restricted") { - p.Rules = append(p.Rules, UsernsFromLog(log)) + p.Rules = append(p.Rules, newUsernsFromLog(log)) } else if strings.Contains(log["info"], "disconnected path") && !slices.Contains(p.Flags, "attach_disconnected") { p.Flags = append(p.Flags, "attach_disconnected") } @@ -87,49 +87,51 @@ func (p *AppArmorProfile) AddRule(log map[string]string) { switch log["class"] { case "cap": - p.Rules = append(p.Rules, CapabilityFromLog(log)) + p.Rules = append(p.Rules, newCapabilityFromLog(log)) case "net": if log["family"] == "unix" { - p.Rules = append(p.Rules, UnixFromLog(log)) + p.Rules = append(p.Rules, newUnixFromLog(log)) } else { - p.Rules = append(p.Rules, NetworkFromLog(log)) + p.Rules = append(p.Rules, newNetworkFromLog(log)) } case "mount": if strings.Contains(log["flags"], "remount") { - p.Rules = append(p.Rules, RemountFromLog(log)) + p.Rules = append(p.Rules, newRemountFromLog(log)) } else { switch log["operation"] { case "mount": - p.Rules = append(p.Rules, MountFromLog(log)) + p.Rules = append(p.Rules, newMountFromLog(log)) case "umount": - p.Rules = append(p.Rules, UmountFromLog(log)) + p.Rules = append(p.Rules, newUmountFromLog(log)) case "remount": - p.Rules = append(p.Rules, RemountFromLog(log)) + p.Rules = append(p.Rules, newRemountFromLog(log)) case "pivotroot": - p.Rules = append(p.Rules, PivotRootFromLog(log)) + p.Rules = append(p.Rules, newPivotRootFromLog(log)) } } case "posix_mqueue", "sysv_mqueue": - p.Rules = append(p.Rules, MqueueFromLog(log)) + p.Rules = append(p.Rules, newMqueueFromLog(log)) case "signal": - p.Rules = append(p.Rules, SignalFromLog(log)) + p.Rules = append(p.Rules, newSignalFromLog(log)) case "ptrace": - p.Rules = append(p.Rules, PtraceFromLog(log)) + p.Rules = append(p.Rules, newPtraceFromLog(log)) case "namespace": - p.Rules = append(p.Rules, UsernsFromLog(log)) + p.Rules = append(p.Rules, newUsernsFromLog(log)) case "unix": - p.Rules = append(p.Rules, UnixFromLog(log)) + p.Rules = append(p.Rules, newUnixFromLog(log)) + case "dbus": + p.Rules = append(p.Rules, newDbusFromLog(log)) case "file": if log["operation"] == "change_onexec" { - p.Rules = append(p.Rules, ChangeProfileFromLog(log)) + p.Rules = append(p.Rules, newChangeProfileFromLog(log)) } else { - p.Rules = append(p.Rules, FileFromLog(log)) + p.Rules = append(p.Rules, newFileFromLog(log)) } default: if strings.Contains(log["operation"], "dbus") { - p.Rules = append(p.Rules, DbusFromLog(log)) + p.Rules = append(p.Rules, newDbusFromLog(log)) } else if log["family"] == "unix" { - p.Rules = append(p.Rules, UnixFromLog(log)) + p.Rules = append(p.Rules, newUnixFromLog(log)) } } } @@ -155,7 +157,7 @@ func (p *AppArmorProfile) Sort() { }) } -// MergeRules merge similar rules together +// MergeRules merge similar rules together. // Steps: // - Remove identical rules // - Merge rule access. Eg: for same path, 'r' and 'w' becomes 'rw' @@ -179,7 +181,7 @@ func (p *AppArmorProfile) MergeRules() { } } -// Format the profile for better readability before printing it +// Format the profile for better readability before printing it. // Follow: https://apparmor.pujol.io/development/guidelines/#the-file-block func (p *AppArmorProfile) Format() { const prefixOwner = " " diff --git a/pkg/aa/profile_test.go b/pkg/aa/profile_test.go index 78206b26f..14403e473 100644 --- a/pkg/aa/profile_test.go +++ b/pkg/aa/profile_test.go @@ -43,10 +43,10 @@ func TestAppArmorProfile_String(t *testing.T) { name: "foo", p: &AppArmorProfile{ Preamble: Preamble{ - Abi: []Abi{{IsMagic: true, Path: "abi/4.0"}}, - Includes: []Include{{IsMagic: true, Path: "tunables/global"}}, - Aliases: []Alias{{Path: "/mnt/usr", RewrittenPath: "/usr"}}, - Variables: []Variable{{ + Abi: []*Abi{{IsMagic: true, Path: "abi/4.0"}}, + Includes: []*Include{{IsMagic: true, Path: "tunables/global"}}, + Aliases: []*Alias{{Path: "/mnt/usr", RewrittenPath: "/usr"}}, + Variables: []*Variable{{ Name: "exec_path", Values: []string{"@{bin}/foo", "@{lib}/foo"}, }}, @@ -83,11 +83,11 @@ func TestAppArmorProfile_String(t *testing.T) { }, &Ptrace{Access: "read", Peer: "nautilus"}, &Unix{ - Access: "send receive", - Type: "stream", - Address: "@/tmp/.ICE-unix/1995", - Peer: "gnome-shell", - PeerAddr: "none", + Access: "send receive", + Type: "stream", + Address: "@/tmp/.ICE-unix/1995", + PeerLabel: "gnome-shell", + PeerAddr: "none", }, &Dbus{ Access: "bind", @@ -97,11 +97,11 @@ func TestAppArmorProfile_String(t *testing.T) { &Dbus{ Access: "receive", Bus: "system", - Name: ":1.3", Path: "/org/freedesktop/DBus", Interface: "org.freedesktop.DBus", Member: "AddMatch", - Label: "power-profiles-daemon", + PeerName: ":1.3", + PeerLabel: "power-profiles-daemon", }, &File{Path: "/opt/intel/oneapi/compiler/*/linux/lib/*.so./*", Access: "rm"}, &File{Path: "@{PROC}/@{pid}/task/@{tid}/comm", Access: "rw"}, @@ -290,9 +290,9 @@ func TestAppArmorProfile_Integration(t *testing.T) { name: "aa-status", p: &AppArmorProfile{ Preamble: Preamble{ - Abi: []Abi{{IsMagic: true, Path: "abi/3.0"}}, - Includes: []Include{{IsMagic: true, Path: "tunables/global"}}, - Variables: []Variable{{ + Abi: []*Abi{{IsMagic: true, Path: "abi/3.0"}}, + Includes: []*Include{{IsMagic: true, Path: "tunables/global"}}, + Variables: []*Variable{{ Name: "exec_path", Values: []string{"@{bin}/aa-status", "@{bin}/apparmor_status"}, }}, @@ -310,7 +310,7 @@ func TestAppArmorProfile_Integration(t *testing.T) { &File{Path: "@{sys}/kernel/security/apparmor/profiles", Access: "r"}, &File{Path: "@{PROC}/@{pids}/attr/current", Access: "r"}, &Include{IsMagic: true, Path: "abstractions/consoles"}, - &File{Qualifier: Qualifier{Owner: true}, Path: "@{PROC}/@{pid}/mounts", Access: "r"}, + &File{Owner: true, Path: "@{PROC}/@{pid}/mounts", Access: "r"}, &Include{IsMagic: true, Path: "abstractions/base"}, &File{Path: "/dev/tty@{int}", Access: "rw"}, &Capability{Name: "sys_ptrace"}, diff --git a/pkg/aa/ptrace.go b/pkg/aa/ptrace.go index 5603a24b6..6c444e228 100644 --- a/pkg/aa/ptrace.go +++ b/pkg/aa/ptrace.go @@ -5,14 +5,16 @@ package aa type Ptrace struct { + Rule Qualifier Access string Peer string } -func PtraceFromLog(log map[string]string) ApparmorRule { +func newPtraceFromLog(log map[string]string) *Ptrace { return &Ptrace{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), Access: toAccess(log["requested_mask"]), Peer: log["peer"], } @@ -20,12 +22,12 @@ func PtraceFromLog(log map[string]string) ApparmorRule { func (r *Ptrace) Less(other any) bool { o, _ := other.(*Ptrace) - if r.Qualifier.Equals(o.Qualifier) { - if r.Access == o.Access { - return r.Peer == o.Peer - } + if r.Access != o.Access { return r.Access < o.Access } + if r.Peer != o.Peer { + return r.Peer == o.Peer + } return r.Qualifier.Less(o.Qualifier) } diff --git a/pkg/aa/rlimit.go b/pkg/aa/rlimit.go index 7c2d1231b..b3d0e782f 100644 --- a/pkg/aa/rlimit.go +++ b/pkg/aa/rlimit.go @@ -10,15 +10,24 @@ type Rlimit struct { Value string } +func newRlimitFromLog(log map[string]string) *Rlimit { + return &Rlimit{ + Rule: newRuleFromLog(log), + Key: log["key"], + Op: log["op"], + Value: log["value"], + } +} + func (r *Rlimit) Less(other any) bool { o, _ := other.(*Rlimit) - if r.Key == o.Key { - if r.Op == o.Op { - return r.Value < o.Value - } + if r.Key != o.Key { + return r.Key < o.Key + } + if r.Op != o.Op { return r.Op < o.Op } - return r.Key < o.Key + return r.Value < o.Value } func (r *Rlimit) Equals(other any) bool { diff --git a/pkg/aa/rules.go b/pkg/aa/rules.go index c2702148d..a09aac402 100644 --- a/pkg/aa/rules.go +++ b/pkg/aa/rules.go @@ -5,6 +5,7 @@ package aa import ( + "fmt" "strings" ) @@ -12,43 +13,12 @@ type Rule struct { Comment string NoNewPrivs bool FileInherit bool -} - -func (r *Rule) Less(other any) bool { - return false -} - -func (r *Rule) Equals(other any) bool { - return false -} - -// Qualifier to apply extra settings to a rule -type Qualifier struct { - Audit bool - AccessType string - Owner bool - NoNewPrivs bool - FileInherit bool - Optional bool - Comment string Prefix string Padding string + Optional bool } -func NewQualifierFromLog(log map[string]string) Qualifier { - owner := false - fsuid, hasFsUID := log["fsuid"] - ouid, hasOuUID := log["ouid"] - isDbus := strings.Contains(log["operation"], "dbus") - if hasFsUID && hasOuUID && fsuid == ouid && ouid != "0" && !isDbus { - owner = true - } - - audit := false - if log["apparmor"] == "AUDIT" { - audit = true - } - +func newRuleFromLog(log map[string]string) Rule { fileInherit := false if log["operation"] == "file_inherit" { fileInherit = true @@ -76,62 +46,54 @@ func NewQualifierFromLog(log map[string]string) Qualifier { default: } - return Qualifier{ - Audit: audit, - Owner: owner, + return Rule{ + Comment: msg, NoNewPrivs: noNewPrivs, FileInherit: fileInherit, Optional: optional, - Comment: msg, } } +func (r Rule) Less(other any) bool { + return false +} + +func (r Rule) Equals(other any) bool { + return false +} + +type Qualifier struct { + Audit bool + AccessType string +} + +func newQualifierFromLog(log map[string]string) Qualifier { + audit := false + if log["apparmor"] == "AUDIT" { + audit = true + } + return Qualifier{Audit: audit} +} + func (r Qualifier) Less(other Qualifier) bool { - if r.Owner == other.Owner { - if r.Audit == other.Audit { - return r.AccessType < other.AccessType - } + if r.Audit != other.Audit { return r.Audit } - return other.Owner + return r.AccessType < other.AccessType } func (r Qualifier) Equals(other Qualifier) bool { - return r.Audit == other.Audit && r.AccessType == other.AccessType && - r.Owner == other.Owner && r.NoNewPrivs == other.NoNewPrivs && - r.FileInherit == other.FileInherit + return r.Audit == other.Audit && r.AccessType == other.AccessType } -// Preamble specific rules - -type Abi struct { - Path string - IsMagic bool +type All struct { + Rule } -func (r Abi) Less(other Abi) bool { - if r.Path == other.Path { - return r.IsMagic == other.IsMagic - } - return r.Path < other.Path +func (r *All) Less(other any) bool { + return false } -func (r Abi) Equals(other Abi) bool { - return r.Path == other.Path && r.IsMagic == other.IsMagic -} - -type Alias struct { - Path string - RewrittenPath string -} - -func (r Alias) Less(other Alias) bool { - if r.Path == other.Path { - return r.RewrittenPath < other.RewrittenPath - } - return r.Path < other.Path -} - -func (r Alias) Equals(other Alias) bool { - return r.Path == other.Path && r.RewrittenPath == other.RewrittenPath +func (r *All) Equals(other any) bool { + return false } diff --git a/pkg/aa/rules_test.go b/pkg/aa/rules_test.go index 0699f123e..48f107267 100644 --- a/pkg/aa/rules_test.go +++ b/pkg/aa/rules_test.go @@ -17,76 +17,100 @@ func TestRule_FromLog(t *testing.T) { want ApparmorRule }{ { - name: "capbability", - fromLog: CapabilityFromLog, - log: capability1Log, - want: capability1, + name: "capbability", + fromLog: func(m map[string]string) ApparmorRule { + return newCapabilityFromLog(m) + }, + log: capability1Log, + want: capability1, }, { - name: "network", - fromLog: NetworkFromLog, - log: network1Log, - want: network1, + name: "network", + fromLog: func(m map[string]string) ApparmorRule { + return newNetworkFromLog(m) + }, + log: network1Log, + want: network1, }, { - name: "mount", - fromLog: MountFromLog, - log: mount1Log, - want: mount1, + name: "mount", + fromLog: func(m map[string]string) ApparmorRule { + return newMountFromLog(m) + }, + log: mount1Log, + want: mount1, }, { - name: "umount", - fromLog: UmountFromLog, - log: umount1Log, - want: umount1, + name: "umount", + fromLog: func(m map[string]string) ApparmorRule { + return newUmountFromLog(m) + }, + log: umount1Log, + want: umount1, }, { - name: "pivotroot", - fromLog: PivotRootFromLog, - log: pivotroot1Log, - want: pivotroot1, + name: "pivotroot", + fromLog: func(m map[string]string) ApparmorRule { + return newPivotRootFromLog(m) + }, + log: pivotroot1Log, + want: pivotroot1, }, { - name: "changeprofile", - fromLog: ChangeProfileFromLog, - log: changeprofile1Log, - want: changeprofile1, + name: "changeprofile", + fromLog: func(m map[string]string) ApparmorRule { + return newChangeProfileFromLog(m) + }, + log: changeprofile1Log, + want: changeprofile1, }, { - name: "signal", - fromLog: SignalFromLog, - log: signal1Log, - want: signal1, + name: "signal", + fromLog: func(m map[string]string) ApparmorRule { + return newSignalFromLog(m) + }, + log: signal1Log, + want: signal1, }, { - name: "ptrace/xdg-document-portal", - fromLog: PtraceFromLog, - log: ptrace1Log, - want: ptrace1, + name: "ptrace/xdg-document-portal", + fromLog: func(m map[string]string) ApparmorRule { + return newPtraceFromLog(m) + }, + log: ptrace1Log, + want: ptrace1, }, { - name: "ptrace/snap-update-ns.firefox", - fromLog: PtraceFromLog, - log: ptrace2Log, - want: ptrace2, + name: "ptrace/snap-update-ns.firefox", + fromLog: func(m map[string]string) ApparmorRule { + return newPtraceFromLog(m) + }, + log: ptrace2Log, + want: ptrace2, }, { - name: "unix", - fromLog: UnixFromLog, - log: unix1Log, - want: unix1, + name: "unix", + fromLog: func(m map[string]string) ApparmorRule { + return newUnixFromLog(m) + }, + log: unix1Log, + want: unix1, }, { - name: "dbus", - fromLog: DbusFromLog, - log: dbus1Log, - want: dbus1, + name: "dbus", + fromLog: func(m map[string]string) ApparmorRule { + return newDbusFromLog(m) + }, + log: dbus1Log, + want: dbus1, }, { - name: "file", - fromLog: FileFromLog, - log: file1Log, - want: file1, + name: "file", + fromLog: func(m map[string]string) ApparmorRule { + return newFileFromLog(m) + }, + log: file1Log, + want: file1, }, } for _, tt := range tests { @@ -109,13 +133,13 @@ func TestRule_Less(t *testing.T) { name: "include1", rule: include1, other: includeLocal1, - want: true, + want: false, }, { name: "include2", rule: include1, other: include2, - want: true, + want: false, }, { name: "include3", @@ -245,9 +269,9 @@ func TestRule_Less(t *testing.T) { }, { name: "file/owner", - rule: &File{Path: "/usr/share/poppler/cMap/Identity-H", Qualifier: Qualifier{Owner: true}}, + rule: &File{Path: "/usr/share/poppler/cMap/Identity-H", Owner: true}, other: &File{Path: "/usr/share/poppler/cMap/Identity-H"}, - want: false, + want: true, }, { name: "file/access", diff --git a/pkg/aa/signal.go b/pkg/aa/signal.go index 3dbf9e16b..9589f508f 100644 --- a/pkg/aa/signal.go +++ b/pkg/aa/signal.go @@ -5,15 +5,17 @@ package aa type Signal struct { + Rule Qualifier Access string Set string Peer string } -func SignalFromLog(log map[string]string) ApparmorRule { +func newSignalFromLog(log map[string]string) *Signal { return &Signal{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), Access: toAccess(log["requested_mask"]), Set: log["signal"], Peer: log["peer"], @@ -22,15 +24,15 @@ func SignalFromLog(log map[string]string) ApparmorRule { func (r *Signal) Less(other any) bool { o, _ := other.(*Signal) - if r.Qualifier.Equals(o.Qualifier) { - if r.Access == o.Access { - if r.Set == o.Set { - return r.Peer < o.Peer - } - return r.Set < o.Set - } + if r.Access != o.Access { return r.Access < o.Access } + if r.Set != o.Set { + return r.Set < o.Set + } + if r.Peer != o.Peer { + return r.Peer < o.Peer + } return r.Qualifier.Less(o.Qualifier) } diff --git a/pkg/aa/templates/include.j2 b/pkg/aa/templates/include.j2 index 8a39a8c30..fad5e9ca4 100644 --- a/pkg/aa/templates/include.j2 +++ b/pkg/aa/templates/include.j2 @@ -8,4 +8,5 @@ {{- else -}} {{ " \"" }}{{ .Path }}{{ "\"" }} {{- end -}} + {{- template "comment" . -}} {{- end -}} diff --git a/pkg/aa/templates/profile.j2 b/pkg/aa/templates/profile.j2 index da406e86e..8c9587e1d 100644 --- a/pkg/aa/templates/profile.j2 +++ b/pkg/aa/templates/profile.j2 @@ -56,7 +56,7 @@ {{- end -}} {{- if eq $type "Rlimit" -}} - {{ "set rlimit " }}{{ .Key }} {{ .Op }} {{ .Value }}{{ "," }} + {{ "set rlimit " }}{{ .Key }} {{ .Op }} {{ .Value }}{{ "," }}{{ template "comment" . }} {{- end -}} {{- if eq $type "Capability" -}} @@ -191,15 +191,24 @@ {{- with .Type -}} {{ " type=" }}{{ . }} {{- end -}} + {{- with .Protocol -}} + {{ " protocol=" }}{{ . }} + {{- end -}} {{- with .Address -}} {{ " addr=" }}{{ . }} {{- end -}} - {{- if .Peer -}} - {{ " peer=(label=" }}{{ .Peer }} - {{- with .PeerAddr -}} - {{ ", addr="}}{{ . }} + {{- with .Label -}} + {{ " label=" }}{{ . }} + {{- end -}} + {{- if and .PeerLabel .PeerAddr -}} + {{ " peer=(label=" }}{{ .PeerLabel }}{{ ", addr="}}{{ .PeerAddr }}{{ ")" }} + {{- else -}} + {{- with .PeerLabel -}} + {{ overindent "peer=(label=" }}{{ . }}{{ ")" }} + {{- end -}} + {{- with .PeerAddr -}} + {{ overindent "peer=(addr=" }}{{ . }}{{ ")" }} {{- end -}} - {{- ")" -}} {{- end -}} {{- "," -}} {{- template "comment" . -}} @@ -256,13 +265,13 @@ {{- with .Member -}} {{ overindent "member=" }}{{ . }}{{ "\n" }} {{- end -}} - {{- if and .Name .Label -}} - {{ overindent "peer=(name=" }}{{ .Name }}{{ ", label="}}{{ .Label }}{{ ")" }} + {{- if and .PeerName .PeerLabel -}} + {{ overindent "peer=(name=" }}{{ .PeerName }}{{ ", label="}}{{ .PeerLabel }}{{ ")" }} {{- else -}} - {{- with .Name -}} + {{- with .PeerName -}} {{ overindent "peer=(name=" }}{{ . }}{{ ")" }} {{- end -}} - {{- with .Label -}} + {{- with .PeerLabel -}} {{ overindent "peer=(label=" }}{{ . }}{{ ")" }} {{- end -}} {{- end -}} @@ -273,6 +282,9 @@ {{- if eq $type "File" -}} {{- template "qualifier" . -}} + {{- if .Owner -}} + {{- "owner " -}} + {{- end -}} {{- .Path -}} {{- " " -}} {{- with .Padding -}} diff --git a/pkg/aa/templates/qualifier.j2 b/pkg/aa/templates/qualifier.j2 index 929cc8ede..513735499 100644 --- a/pkg/aa/templates/qualifier.j2 +++ b/pkg/aa/templates/qualifier.j2 @@ -2,9 +2,6 @@ {{- with .Prefix -}} {{ . }} {{- end -}} - {{- if .Owner -}} - {{- "owner " -}} - {{- end -}} {{- if .Audit -}} {{- "audit " -}} {{- end -}} diff --git a/pkg/aa/unix.go b/pkg/aa/unix.go index 3c353579c..0372d467e 100644 --- a/pkg/aa/unix.go +++ b/pkg/aa/unix.go @@ -5,62 +5,64 @@ package aa type Unix struct { + Rule Qualifier - Access string - Type string - Protocol string - Address string - Label string - Attr string - Opt string - Peer string - PeerAddr string + Access string + Type string + Protocol string + Address string + Label string + Attr string + Opt string + PeerLabel string + PeerAddr string } -func UnixFromLog(log map[string]string) ApparmorRule { +func newUnixFromLog(log map[string]string) *Unix { return &Unix{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), Access: toAccess(log["requested_mask"]), Type: log["sock_type"], Protocol: log["protocol"], Address: log["addr"], - Label: log["peer_label"], + Label: log["label"], Attr: log["attr"], Opt: log["opt"], - Peer: log["peer"], + PeerLabel: log["peer"], PeerAddr: log["peer_addr"], } } func (r *Unix) Less(other any) bool { o, _ := other.(*Unix) - if r.Qualifier.Equals(o.Qualifier) { - if r.Access == o.Access { - if r.Type == o.Type { - if r.Protocol == o.Protocol { - if r.Address == o.Address { - if r.Label == o.Label { - if r.Attr == o.Attr { - if r.Opt == o.Opt { - if r.Peer == o.Peer { - return r.PeerAddr < o.PeerAddr - } - return r.Peer < o.Peer - } - return r.Opt < o.Opt - } - return r.Attr < o.Attr - } - return r.Label < o.Label - } - return r.Address < o.Address - } - return r.Protocol < o.Protocol - } - return r.Type < o.Type - } + if r.Access != o.Access { return r.Access < o.Access } + if r.Type != o.Type { + return r.Type < o.Type + } + if r.Protocol != o.Protocol { + return r.Protocol < o.Protocol + } + if r.Address != o.Address { + return r.Address < o.Address + } + if r.Label != o.Label { + return r.Label < o.Label + } + if r.Attr != o.Attr { + return r.Attr < o.Attr + } + if r.Opt != o.Opt { + return r.Opt < o.Opt + } + if r.PeerLabel != o.PeerLabel { + return r.PeerLabel < o.PeerLabel + } + if r.PeerAddr != o.PeerAddr { + return r.PeerAddr < o.PeerAddr + } return r.Qualifier.Less(o.Qualifier) } @@ -69,5 +71,6 @@ func (r *Unix) Equals(other any) bool { return r.Access == o.Access && r.Type == o.Type && r.Protocol == o.Protocol && r.Address == o.Address && r.Label == o.Label && r.Attr == o.Attr && r.Opt == o.Opt && - r.Peer == o.Peer && r.PeerAddr == o.PeerAddr && r.Qualifier.Equals(o.Qualifier) + r.PeerLabel == o.PeerLabel && r.PeerAddr == o.PeerAddr && + r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/userns.go b/pkg/aa/userns.go index 446b130de..9c5824008 100644 --- a/pkg/aa/userns.go +++ b/pkg/aa/userns.go @@ -5,20 +5,22 @@ package aa type Userns struct { + Rule Qualifier Create bool } -func UsernsFromLog(log map[string]string) ApparmorRule { +func newUsernsFromLog(log map[string]string) *Userns { return &Userns{ - Qualifier: NewQualifierFromLog(log), + Rule: newRuleFromLog(log), + Qualifier: newQualifierFromLog(log), Create: true, } } func (r *Userns) Less(other any) bool { o, _ := other.(*Userns) - if r.Qualifier.Equals(o.Qualifier) { + if r.Create != o.Create { return r.Create } return r.Qualifier.Less(o.Qualifier) diff --git a/pkg/aa/variables.go b/pkg/aa/variables.go index ddd2e3d17..5081b5ba6 100644 --- a/pkg/aa/variables.go +++ b/pkg/aa/variables.go @@ -9,7 +9,6 @@ package aa import ( "regexp" - "slices" "strings" ) @@ -18,35 +17,19 @@ var ( regVariablesRef = regexp.MustCompile(`@{([^{}]+)}`) ) -type Variable struct { - Name string - Values []string -} - -func (r Variable) Less(other Variable) bool { - if r.Name == other.Name { - return len(r.Values) < len(other.Values) - } - return r.Name < other.Name -} - -func (r Variable) Equals(other Variable) bool { - return r.Name == other.Name && slices.Equal(r.Values, other.Values) -} - // DefaultTunables return a minimal working profile to build the profile // It should not be used when loading file from /etc/apparmor.d func DefaultTunables() *AppArmorProfile { return &AppArmorProfile{ Preamble: Preamble{ - Variables: []Variable{ - {"bin", []string{"/{,usr/}{,s}bin"}}, - {"lib", []string{"/{,usr/}lib{,exec,32,64}"}}, - {"multiarch", []string{"*-linux-gnu*"}}, - {"HOME", []string{"/home/*"}}, - {"user_share_dirs", []string{"/home/*/.local/share"}}, - {"etc_ro", []string{"/{,usr/}etc/"}}, - {"int", []string{"[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}"}}, + Variables: []*Variable{ + {Name: "bin", Values: []string{"/{,usr/}{,s}bin"}}, + {Name: "lib", Values: []string{"/{,usr/}lib{,exec,32,64}"}}, + {Name: "multiarch", Values: []string{"*-linux-gnu*"}}, + {Name: "HOME", Values: []string{"/home/*"}}, + {Name: "user_share_dirs", Values: []string{"/home/*/.local/share"}}, + {Name: "etc_ro", Values: []string{"/{,usr/}etc/"}}, + {Name: "int", Values: []string{"[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}"}}, }, }, } @@ -68,7 +51,7 @@ func (p *AppArmorProfile) ParseVariables(content string) { } } if !found { - variable := Variable{Name: key, Values: values} + variable := &Variable{Name: key, Values: values} p.Variables = append(p.Variables, variable) } } diff --git a/pkg/aa/variables_test.go b/pkg/aa/variables_test.go index 3d91ec7bd..2660c5b9a 100644 --- a/pkg/aa/variables_test.go +++ b/pkg/aa/variables_test.go @@ -9,6 +9,10 @@ import ( "testing" ) +// TODO: space in variable need to be tested. +// @{name} = "Mullvad VPN" +// profile mullvad-gui /{opt/"Mullvad/mullvad-gui,opt/VPN"/mullvad-gui,mullvad-gui} flags=(attach_disconnected,complain) { + func TestDefaultTunables(t *testing.T) { tests := []struct { name string @@ -18,14 +22,14 @@ func TestDefaultTunables(t *testing.T) { name: "aa", want: &AppArmorProfile{ Preamble: Preamble{ - Variables: []Variable{ - {"bin", []string{"/{,usr/}{,s}bin"}}, - {"lib", []string{"/{,usr/}lib{,exec,32,64}"}}, - {"multiarch", []string{"*-linux-gnu*"}}, - {"HOME", []string{"/home/*"}}, - {"user_share_dirs", []string{"/home/*/.local/share"}}, - {"etc_ro", []string{"/{,usr/}etc/"}}, - {"int", []string{"[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}"}}, + Variables: []*Variable{ + {Name: "bin", Values: []string{"/{,usr/}{,s}bin"}}, + {Name: "lib", Values: []string{"/{,usr/}lib{,exec,32,64}"}}, + {Name: "multiarch", Values: []string{"*-linux-gnu*"}}, + {Name: "HOME", Values: []string{"/home/*"}}, + {Name: "user_share_dirs", Values: []string{"/home/*/.local/share"}}, + {Name: "etc_ro", Values: []string{"/{,usr/}etc/"}}, + {Name: "int", Values: []string{"[0-9]{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}{[0-9],}"}}, }, }, }, @@ -44,7 +48,7 @@ func TestAppArmorProfile_ParseVariables(t *testing.T) { tests := []struct { name string content string - want []Variable + want []*Variable }{ { name: "firefox", @@ -54,12 +58,12 @@ func TestAppArmorProfile_ParseVariables(t *testing.T) { @{firefox_cache_dirs} = @{user_cache_dirs}/mozilla/ @{exec_path} = /{usr/,}bin/@{firefox_name} @{firefox_lib_dirs}/@{firefox_name} `, - want: []Variable{ - {"firefox_name", []string{"firefox{,-esr,-bin}"}}, - {"firefox_lib_dirs", []string{"/{usr/,}lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}}, - {"firefox_config_dirs", []string{"@{HOME}/.mozilla/"}}, - {"firefox_cache_dirs", []string{"@{user_cache_dirs}/mozilla/"}}, - {"exec_path", []string{"/{usr/,}bin/@{firefox_name}", "@{firefox_lib_dirs}/@{firefox_name}"}}, + want: []*Variable{ + {Name: "firefox_name", Values: []string{"firefox{,-esr,-bin}"}}, + {Name: "firefox_lib_dirs", Values: []string{"/{usr/,}lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}}, + {Name: "firefox_config_dirs", Values: []string{"@{HOME}/.mozilla/"}}, + {Name: "firefox_cache_dirs", Values: []string{"@{user_cache_dirs}/mozilla/"}}, + {Name: "exec_path", Values: []string{"/{usr/,}bin/@{firefox_name}", "@{firefox_lib_dirs}/@{firefox_name}"}}, }, }, { @@ -68,8 +72,8 @@ func TestAppArmorProfile_ParseVariables(t *testing.T) { @{exec_path} += /{usr/,}bin/Xorg{,.bin} @{exec_path} += /{usr/,}lib/Xorg{,.wrap} @{exec_path} += /{usr/,}lib/xorg/Xorg{,.wrap}`, - want: []Variable{ - {"exec_path", []string{ + want: []*Variable{ + {Name: "exec_path", Values: []string{ "/{usr/,}bin/X", "/{usr/,}bin/Xorg{,.bin}", "/{usr/,}lib/Xorg{,.wrap}", @@ -81,9 +85,9 @@ func TestAppArmorProfile_ParseVariables(t *testing.T) { name: "snapd", content: `@{lib_dirs} = @{lib}/ /snap/snapd/@{int}@{lib} @{exec_path} = @{lib_dirs}/snapd/snapd`, - want: []Variable{ - {"lib_dirs", []string{"@{lib}/", "/snap/snapd/@{int}@{lib}"}}, - {"exec_path", []string{"@{lib_dirs}/snapd/snapd"}}, + want: []*Variable{ + {Name: "lib_dirs", Values: []string{"@{lib}/", "/snap/snapd/@{int}@{lib}"}}, + {Name: "exec_path", Values: []string{"@{lib_dirs}/snapd/snapd"}}, }, }, } @@ -104,11 +108,21 @@ func TestAppArmorProfile_resolve(t *testing.T) { input string want []string }{ + { + name: "default", + input: "@{etc_ro}", + want: []string{"/{,usr/}etc/"}, + }, { name: "empty", input: "@{}", want: []string{"@{}"}, }, + { + name: "nil", + input: "@{foo}", + want: []string{}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -123,15 +137,15 @@ func TestAppArmorProfile_resolve(t *testing.T) { func TestAppArmorProfile_ResolveAttachments(t *testing.T) { tests := []struct { name string - variables []Variable + variables []*Variable want []string }{ { name: "firefox", - variables: []Variable{ - {"firefox_name", []string{"firefox{,-esr,-bin}"}}, - {"firefox_lib_dirs", []string{"/{usr/,}/lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}}, - {"exec_path", []string{"/{usr/,}bin/@{firefox_name}", "@{firefox_lib_dirs}/@{firefox_name}"}}, + variables: []*Variable{ + {Name: "firefox_name", Values: []string{"firefox{,-esr,-bin}"}}, + {Name: "firefox_lib_dirs", Values: []string{"/{usr/,}/lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}}, + {Name: "exec_path", Values: []string{"/{usr/,}bin/@{firefox_name}", "@{firefox_lib_dirs}/@{firefox_name}"}}, }, want: []string{ "/{usr/,}bin/firefox{,-esr,-bin}", @@ -141,10 +155,10 @@ func TestAppArmorProfile_ResolveAttachments(t *testing.T) { }, { name: "chromium", - variables: []Variable{ - {"name", []string{"chromium"}}, - {"lib_dirs", []string{"/{usr/,}lib/@{name}"}}, - {"exec_path", []string{"@{lib_dirs}/@{name}"}}, + variables: []*Variable{ + {Name: "name", Values: []string{"chromium"}}, + {Name: "lib_dirs", Values: []string{"/{usr/,}lib/@{name}"}}, + {Name: "exec_path", Values: []string{"@{lib_dirs}/@{name}"}}, }, want: []string{ "/{usr/,}lib/chromium/chromium", @@ -152,9 +166,9 @@ func TestAppArmorProfile_ResolveAttachments(t *testing.T) { }, { name: "geoclue", - variables: []Variable{ - {"libexec", []string{"/{usr/,}libexec"}}, - {"exec_path", []string{"@{libexec}/geoclue", "@{libexec}/geoclue-2.0/demos/agent"}}, + variables: []*Variable{ + {Name: "libexec", Values: []string{"/{usr/,}libexec"}}, + {Name: "exec_path", Values: []string{"@{libexec}/geoclue", "@{libexec}/geoclue-2.0/demos/agent"}}, }, want: []string{ "/{usr/,}libexec/geoclue", @@ -163,11 +177,11 @@ func TestAppArmorProfile_ResolveAttachments(t *testing.T) { }, { name: "opera", - variables: []Variable{ - {"multiarch", []string{"*-linux-gnu*"}}, - {"name", []string{"opera{,-beta,-developer}"}}, - {"lib_dirs", []string{"/{usr/,}lib/@{multiarch}/@{name}"}}, - {"exec_path", []string{"@{lib_dirs}/@{name}"}}, + variables: []*Variable{ + {Name: "multiarch", Values: []string{"*-linux-gnu*"}}, + {Name: "name", Values: []string{"opera{,-beta,-developer}"}}, + {Name: "lib_dirs", Values: []string{"/{usr/,}lib/@{multiarch}/@{name}"}}, + {Name: "exec_path", Values: []string{"@{lib_dirs}/@{name}"}}, }, want: []string{ "/{usr/,}lib/*-linux-gnu*/opera{,-beta,-developer}/opera{,-beta,-developer}", diff --git a/pkg/logs/logs_test.go b/pkg/logs/logs_test.go index 4bfd5d90d..d0aa0f5b1 100644 --- a/pkg/logs/logs_test.go +++ b/pkg/logs/logs_test.go @@ -303,16 +303,16 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) { Name: "kmod", Rules: aa.Rules{ &aa.Unix{ - Qualifier: aa.Qualifier{FileInherit: true}, - Access: "send receive", - Type: "stream", - Protocol: "0", + Rule: aa.Rule{FileInherit: true}, + Access: "send receive", + Type: "stream", + Protocol: "0", }, &aa.Unix{ - Qualifier: aa.Qualifier{FileInherit: true}, - Access: "send receive", - Type: "stream", - Protocol: "0", + Rule: aa.Rule{FileInherit: true}, + Access: "send receive", + Type: "stream", + Protocol: "0", }, }, }, @@ -324,11 +324,11 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) { &aa.Dbus{ Access: "send", Bus: "system", - Name: "org.freedesktop.DBus", Path: "/org/freedesktop/DBus", Interface: "org.freedesktop.DBus", Member: "AddMatch", - Label: "dbus-daemon", + PeerName: "org.freedesktop.DBus", + PeerLabel: "dbus-daemon", }, }, }, diff --git a/pkg/prebuild/directive/dbus.go b/pkg/prebuild/directive/dbus.go index 2c171624e..f0922c552 100644 --- a/pkg/prebuild/directive/dbus.go +++ b/pkg/prebuild/directive/dbus.go @@ -107,7 +107,7 @@ func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile { Bus: rules["bus"], Path: rules["path"], Interface: iface, - Name: `":1.@{int}"`, + PeerName: `":1.@{int}"`, }) } for _, iface := range interfaces { @@ -116,7 +116,7 @@ func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile { Bus: rules["bus"], Path: rules["path"], Interface: iface, - Name: `"{:1.@{int},org.freedesktop.DBus}"`, + PeerName: `"{:1.@{int},org.freedesktop.DBus}"`, }) } p.Rules = append(p.Rules, &aa.Dbus{ @@ -125,7 +125,7 @@ func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile { Path: rules["path"], Interface: "org.freedesktop.DBus.Introspectable", Member: "Introspect", - Name: `":1.@{int}"`, + PeerName: `":1.@{int}"`, }) return p } @@ -139,8 +139,8 @@ func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfile { Bus: rules["bus"], Path: rules["path"], Interface: iface, - Name: `"{:1.@{int},` + rules["name"] + `}"`, - Label: rules["label"], + PeerName: `"{:1.@{int},` + rules["name"] + `}"`, + PeerLabel: rules["label"], }) } for _, iface := range interfaces { @@ -149,8 +149,8 @@ func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfile { Bus: rules["bus"], Path: rules["path"], Interface: iface, - Name: `"{:1.@{int},` + rules["name"] + `}"`, - Label: rules["label"], + PeerName: `"{:1.@{int},` + rules["name"] + `}"`, + PeerLabel: rules["label"], }) } return p