From c719a0a109df521b7f551b4d470ea2a4fd13ac1a Mon Sep 17 00:00:00 2001 From: Alexandre Pujol Date: Tue, 23 Apr 2024 21:17:25 +0100 Subject: [PATCH] feat(aa): ensure accesses are slice of string. --- pkg/aa/apparmor.go | 71 --------------------------------------- pkg/aa/apparmor_test.go | 44 ++++++++++++------------- pkg/aa/capability.go | 17 +++++++--- pkg/aa/data_test.go | 30 ++++++++--------- pkg/aa/dbus.go | 12 ++++--- pkg/aa/file.go | 13 +++++--- pkg/aa/io_uring.go | 12 ++++--- pkg/aa/mqueue.go | 10 +++--- pkg/aa/profile.go | 73 +++++++++++++++++++++++++++++++++++++++++ pkg/aa/ptrace.go | 10 +++--- pkg/aa/rules_test.go | 6 ++-- pkg/aa/signal.go | 18 +++++----- pkg/aa/template.go | 54 ++++++++++++++++++++---------- pkg/aa/unix.go | 10 +++--- pkg/logs/logs.go | 8 ++--- pkg/logs/logs_test.go | 62 ++++++++++++++++------------------ 16 files changed, 240 insertions(+), 210 deletions(-) diff --git a/pkg/aa/apparmor.go b/pkg/aa/apparmor.go index 6c598e3f8..a7f6cc262 100644 --- a/pkg/aa/apparmor.go +++ b/pkg/aa/apparmor.go @@ -61,77 +61,6 @@ func (f *AppArmorProfileFile) GetDefaultProfile() *Profile { return f.Profiles[0] } -// AddRule adds a new rule to the profile from a log map -// See utils/apparmor/logparser.py for the format of the log map -func (f *AppArmorProfileFile) AddRule(log map[string]string) { - p := f.GetDefaultProfile() - - // Generate profile flags and extra rules - switch log["error"] { - case "-2": - if !slices.Contains(p.Flags, "mediate_deleted") { - p.Flags = append(p.Flags, "mediate_deleted") - } - case "-13": - if strings.Contains(log["info"], "namespace creation restricted") { - 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") - } - default: - } - - switch log["class"] { - case "cap": - p.Rules = append(p.Rules, newCapabilityFromLog(log)) - case "net": - if log["family"] == "unix" { - p.Rules = append(p.Rules, newUnixFromLog(log)) - } else { - p.Rules = append(p.Rules, newNetworkFromLog(log)) - } - case "mount": - if strings.Contains(log["flags"], "remount") { - p.Rules = append(p.Rules, newRemountFromLog(log)) - } else { - switch log["operation"] { - case "mount": - p.Rules = append(p.Rules, newMountFromLog(log)) - case "umount": - p.Rules = append(p.Rules, newUmountFromLog(log)) - case "remount": - p.Rules = append(p.Rules, newRemountFromLog(log)) - case "pivotroot": - p.Rules = append(p.Rules, newPivotRootFromLog(log)) - } - } - case "posix_mqueue", "sysv_mqueue": - p.Rules = append(p.Rules, newMqueueFromLog(log)) - case "signal": - p.Rules = append(p.Rules, newSignalFromLog(log)) - case "ptrace": - p.Rules = append(p.Rules, newPtraceFromLog(log)) - case "namespace": - p.Rules = append(p.Rules, newUsernsFromLog(log)) - case "unix": - 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, newChangeProfileFromLog(log)) - } else { - p.Rules = append(p.Rules, newFileFromLog(log)) - } - default: - if strings.Contains(log["operation"], "dbus") { - p.Rules = append(p.Rules, newDbusFromLog(log)) - } else if log["family"] == "unix" { - p.Rules = append(p.Rules, newUnixFromLog(log)) - } - } -} - // Sort the rules in the profile // Follow: https://apparmor.pujol.io/development/guidelines/#guidelines func (f *AppArmorProfileFile) Sort() { diff --git a/pkg/aa/apparmor_test.go b/pkg/aa/apparmor_test.go index fb8cd687a..3c1bcaa11 100644 --- a/pkg/aa/apparmor_test.go +++ b/pkg/aa/apparmor_test.go @@ -62,8 +62,8 @@ func TestAppArmorProfile_String(t *testing.T) { &Include{IsMagic: true, Path: "abstractions/base"}, &Include{IsMagic: true, Path: "abstractions/nameservice-strict"}, rlimit1, - &Capability{Name: "dac_read_search"}, - &Capability{Name: "dac_override"}, + &Capability{Names: []string{"dac_read_search"}}, + &Capability{Names: []string{"dac_override"}}, &Network{Domain: "inet", Type: "stream"}, &Network{Domain: "inet6", Type: "stream"}, &Mount{ @@ -79,25 +79,25 @@ func TestAppArmorProfile_String(t *testing.T) { MountPoint: "@{run}/user/@{uid}/", }, &Signal{ - Access: "receive", - Set: "term", + Access: []string{"receive"}, + Set: []string{"term"}, Peer: "at-spi-bus-launcher", }, - &Ptrace{Access: "read", Peer: "nautilus"}, + &Ptrace{Access: []string{"read"}, Peer: "nautilus"}, &Unix{ - Access: "send receive", + Access: []string{"send", "receive"}, Type: "stream", Address: "@/tmp/.ICE-unix/1995", PeerLabel: "gnome-shell", PeerAddr: "none", }, &Dbus{ - Access: "bind", + Access: []string{"bind"}, Bus: "session", Name: "org.gnome.*", }, &Dbus{ - Access: "receive", + Access: []string{"receive"}, Bus: "system", Path: "/org/freedesktop/DBus", Interface: "org.freedesktop.DBus", @@ -105,9 +105,9 @@ func TestAppArmorProfile_String(t *testing.T) { 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"}, - &File{Path: "@{sys}/devices/@{pci}/class", Access: "r"}, + &File{Path: "/opt/intel/oneapi/compiler/*/linux/lib/*.so./*", Access: []string{"r", "m"}}, + &File{Path: "@{PROC}/@{pid}/task/@{tid}/comm", Access: []string{"r", "w"}}, + &File{Path: "@{sys}/devices/@{pci}/class", Access: []string{"r"}}, includeLocal1, }, }}, @@ -306,19 +306,19 @@ func TestAppArmorProfile_Integration(t *testing.T) { }, Rules: Rules{ &Include{IfExists: true, IsMagic: true, Path: "local/aa-status"}, - &Capability{Name: "dac_read_search"}, - &File{Path: "@{exec_path}", Access: "mr"}, - &File{Path: "@{PROC}/@{pids}/attr/apparmor/current", Access: "r"}, - &File{Path: "@{PROC}/", Access: "r"}, - &File{Path: "@{sys}/module/apparmor/parameters/enabled", Access: "r"}, - &File{Path: "@{sys}/kernel/security/apparmor/profiles", Access: "r"}, - &File{Path: "@{PROC}/@{pids}/attr/current", Access: "r"}, + &Capability{Names: []string{"dac_read_search"}}, + &File{Path: "@{exec_path}", Access: []string{"m", "r"}}, + &File{Path: "@{PROC}/@{pids}/attr/apparmor/current", Access: []string{"r"}}, + &File{Path: "@{PROC}/", Access: []string{"r"}}, + &File{Path: "@{sys}/module/apparmor/parameters/enabled", Access: []string{"r"}}, + &File{Path: "@{sys}/kernel/security/apparmor/profiles", Access: []string{"r"}}, + &File{Path: "@{PROC}/@{pids}/attr/current", Access: []string{"r"}}, &Include{IsMagic: true, Path: "abstractions/consoles"}, - &File{Owner: true, Path: "@{PROC}/@{pid}/mounts", Access: "r"}, + &File{Owner: true, Path: "@{PROC}/@{pid}/mounts", Access: []string{"r"}}, &Include{IsMagic: true, Path: "abstractions/base"}, - &File{Path: "/dev/tty@{int}", Access: "rw"}, - &Capability{Name: "sys_ptrace"}, - &Ptrace{Access: "read"}, + &File{Path: "/dev/tty@{int}", Access: []string{"r", "w"}}, + &Capability{Names: []string{"sys_ptrace"}}, + &Ptrace{Access: []string{"read"}}, }, }}, }, diff --git a/pkg/aa/capability.go b/pkg/aa/capability.go index eb6c8b36b..0e4918fab 100644 --- a/pkg/aa/capability.go +++ b/pkg/aa/capability.go @@ -4,29 +4,36 @@ package aa + type Capability struct { RuleBase Qualifier - Name string + Names []string +} + } func newCapabilityFromLog(log map[string]string) Rule { return &Capability{ RuleBase: newRuleFromLog(log), Qualifier: newQualifierFromLog(log), - Name: log["capname"], + Names: []string{log["capname"]}, } } func (r *Capability) Less(other any) bool { o, _ := other.(*Capability) - if r.Name != o.Name { - return r.Name < o.Name + for i := 0; i < len(r.Names) && i < len(o.Names); i++ { + if r.Names[i] != o.Names[i] { + return r.Names[i] < o.Names[i] + } } return r.Qualifier.Less(o.Qualifier) } func (r *Capability) Equals(other any) bool { o, _ := other.(*Capability) - return r.Name == o.Name && r.Qualifier.Equals(o.Qualifier) + return slices.Equal(r.Names, o.Names) && r.Qualifier.Equals(o.Qualifier) +} + } diff --git a/pkg/aa/data_test.go b/pkg/aa/data_test.go index 49c6da545..5787154bf 100644 --- a/pkg/aa/data_test.go +++ b/pkg/aa/data_test.go @@ -26,8 +26,8 @@ var ( "profile": "pkexec", "comm": "pkexec", } - capability1 = &Capability{Name: "net_admin"} - capability2 = &Capability{Name: "sys_ptrace"} + capability1 = &Capability{Names: []string{"net_admin"}} + capability2 = &Capability{Names: []string{"sys_ptrace"}} // Network network1Log = map[string]string{ @@ -147,13 +147,13 @@ var ( "peer": "firefox//&firejail-default", } signal1 = &Signal{ - Access: "receive", - Set: "kill", + Access: []string{"receive"}, + Set: []string{"kill"}, Peer: "firefox//&firejail-default", } signal2 = &Signal{ - Access: "receive", - Set: "up", + Access: []string{"receive"}, + Set: []string{"up"}, Peer: "firefox//&firejail-default", } @@ -177,8 +177,8 @@ var ( "denied_mask": "readby", "peer": "systemd-journald", } - ptrace1 = &Ptrace{Access: "read", Peer: "nautilus"} - ptrace2 = &Ptrace{Access: "readby", Peer: "systemd-journald"} + ptrace1 = &Ptrace{Access: []string{"read"}, Peer: "nautilus"} + ptrace2 = &Ptrace{Access: []string{"readby"}, Peer: "systemd-journald"} // Unix unix1Log = map[string]string{ @@ -197,7 +197,7 @@ var ( "protocol": "0", } unix1 = &Unix{ - Access: "send receive", + Access: []string{"receive", "send"}, Type: "stream", Protocol: "0", Address: "none", @@ -206,7 +206,7 @@ var ( } unix2 = &Unix{ RuleBase: RuleBase{FileInherit: true}, - Access: "receive", + Access: []string{"receive"}, Type: "stream", } @@ -234,7 +234,7 @@ var ( "label": "evolution-source-registry", } dbus1 = &Dbus{ - Access: "receive", + Access: []string{"receive"}, Bus: "session", Path: "/org/gtk/vfs/metadata", Interface: "org.gtk.vfs.Metadata", @@ -243,12 +243,12 @@ var ( PeerLabel: "tracker-extract", } dbus2 = &Dbus{ - Access: "bind", + Access: []string{"bind"}, Bus: "session", Name: "org.gnome.evolution.dataserver.Sources5", } dbus3 = &Dbus{ - Access: "bind", + Access: []string{"bind"}, Bus: "session", Name: "org.gnome.evolution.dataserver", } @@ -283,11 +283,11 @@ var ( "OUID": "user", "error": "-1", } - file1 = &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: "r"} + file1 = &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: []string{"r"}} file2 = &File{ RuleBase: RuleBase{NoNewPrivs: true}, Owner: true, Path: "@{PROC}/4163/cgroup", - Access: "r", + Access: []string{"r"}, } ) diff --git a/pkg/aa/dbus.go b/pkg/aa/dbus.go index 6c8a0dc76..45a5dabfd 100644 --- a/pkg/aa/dbus.go +++ b/pkg/aa/dbus.go @@ -7,7 +7,7 @@ package aa type Dbus struct { RuleBase Qualifier - Access string + Access []string Bus string Name string Path string @@ -28,7 +28,7 @@ func newDbusFromLog(log map[string]string) Rule { return &Dbus{ RuleBase: newRuleFromLog(log), Qualifier: newQualifierFromLog(log), - Access: log["mask"], + Access: []string{log["mask"]}, Bus: log["bus"], Name: name, Path: log["path"], @@ -41,8 +41,10 @@ func newDbusFromLog(log map[string]string) Rule { func (r *Dbus) Less(other any) bool { o, _ := other.(*Dbus) - if r.Access != o.Access { - return r.Access < o.Access + for i := 0; i < len(r.Access) && i < len(o.Access); i++ { + if r.Access[i] != o.Access[i] { + return r.Access[i] < o.Access[i] + } } if r.Bus != o.Bus { return r.Bus < o.Bus @@ -70,7 +72,7 @@ func (r *Dbus) Less(other any) bool { func (r *Dbus) Equals(other any) bool { o, _ := other.(*Dbus) - return r.Access == o.Access && r.Bus == o.Bus && r.Name == o.Name && + return slices.Equal(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.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 cba3fbaad..266989d80 100644 --- a/pkg/aa/file.go +++ b/pkg/aa/file.go @@ -9,7 +9,7 @@ type File struct { Qualifier Owner bool Path string - Access string + Access []string Target string } @@ -26,7 +26,7 @@ func newFileFromLog(log map[string]string) Rule { Qualifier: newQualifierFromLog(log), Owner: owner, Path: log["name"], - Access: toAccess(log["requested_mask"]), + Access: toAccess("file-log", log["requested_mask"]), Target: log["target"], } } @@ -41,8 +41,8 @@ func (r *File) Less(other any) bool { if r.Path != o.Path { return r.Path < o.Path } - if r.Access != o.Access { - return r.Access < o.Access + if len(r.Access) != len(o.Access) { + return len(r.Access) < len(o.Access) } if r.Target != o.Target { return r.Target < o.Target @@ -55,6 +55,9 @@ func (r *File) Less(other any) bool { func (r *File) Equals(other any) bool { o, _ := other.(*File) - return r.Path == o.Path && r.Access == o.Access && r.Owner == o.Owner && + return r.Path == o.Path && slices.Equal(r.Access, o.Access) && r.Owner == o.Owner && + r.Target == o.Target && r.Qualifier.Equals(o.Qualifier) +} + r.Target == o.Target && r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/io_uring.go b/pkg/aa/io_uring.go index e2f91c59a..90aae2498 100644 --- a/pkg/aa/io_uring.go +++ b/pkg/aa/io_uring.go @@ -7,7 +7,7 @@ package aa type IOUring struct { RuleBase Qualifier - Access string + Access []string Label string } @@ -15,15 +15,15 @@ func newIOUringFromLog(log map[string]string) Rule { return &IOUring{ RuleBase: newRuleFromLog(log), Qualifier: newQualifierFromLog(log), - Access: toAccess(log["requested"]), + Access: toAccess(tokIOURING, log["requested"]), Label: log["label"], } } func (r *IOUring) Less(other any) bool { o, _ := other.(*IOUring) - if r.Access != o.Access { - return r.Access < o.Access + if len(r.Access) != len(o.Access) { + return len(r.Access) < len(o.Access) } if r.Label != o.Label { return r.Label < o.Label @@ -33,5 +33,7 @@ func (r *IOUring) Less(other any) bool { func (r *IOUring) Equals(other any) bool { o, _ := other.(*IOUring) - return r.Access == o.Access && r.Label == o.Label && r.Qualifier.Equals(o.Qualifier) + return slices.Equal(r.Access, o.Access) && r.Label == o.Label && r.Qualifier.Equals(o.Qualifier) +} + } diff --git a/pkg/aa/mqueue.go b/pkg/aa/mqueue.go index b7a3edb58..42b674b56 100644 --- a/pkg/aa/mqueue.go +++ b/pkg/aa/mqueue.go @@ -11,7 +11,7 @@ import ( type Mqueue struct { RuleBase Qualifier - Access string + Access []string Type string Label string Name string @@ -27,7 +27,7 @@ func newMqueueFromLog(log map[string]string) Rule { return &Mqueue{ RuleBase: newRuleFromLog(log), Qualifier: newQualifierFromLog(log), - Access: toAccess(log["requested"]), + Access: toAccess(tokMQUEUE, log["requested"]), Type: mqueueType, Label: log["label"], Name: log["name"], @@ -36,8 +36,8 @@ func newMqueueFromLog(log map[string]string) Rule { func (r *Mqueue) Less(other any) bool { o, _ := other.(*Mqueue) - if r.Access != o.Access { - return r.Access < o.Access + if len(r.Access) != len(o.Access) { + return len(r.Access) < len(o.Access) } if r.Type != o.Type { return r.Type < o.Type @@ -50,6 +50,6 @@ func (r *Mqueue) Less(other any) bool { func (r *Mqueue) Equals(other any) bool { o, _ := other.(*Mqueue) - return r.Access == o.Access && r.Type == o.Type && r.Label == o.Label && + return slices.Equal(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/profile.go b/pkg/aa/profile.go index a7d7a6fd6..24576e205 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -39,3 +39,76 @@ func (p *Profile) Equals(other any) bool { maps.Equal(p.Attributes, o.Attributes) && slices.Equal(p.Flags, o.Flags) } + +// AddRule adds a new rule to the profile from a log map. +func (p *Profile) AddRule(log map[string]string) { + + // Generate profile flags and extra rules + switch log["error"] { + case "-2": + if !slices.Contains(p.Flags, "mediate_deleted") { + p.Flags = append(p.Flags, "mediate_deleted") + } + case "-13": + if strings.Contains(log["info"], "namespace creation restricted") { + 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") + } + default: + } + + switch log["class"] { + case "rlimits": + p.Rules = append(p.Rules, newRlimitFromLog(log)) + case "cap": + p.Rules = append(p.Rules, newCapabilityFromLog(log)) + case "net": + if log["family"] == "unix" { + p.Rules = append(p.Rules, newUnixFromLog(log)) + } else { + p.Rules = append(p.Rules, newNetworkFromLog(log)) + } + case "io_uring": + p.Rules = append(p.Rules, newIOUringFromLog(log)) + case "mount": + if strings.Contains(log["flags"], "remount") { + p.Rules = append(p.Rules, newRemountFromLog(log)) + } else { + switch log["operation"] { + case "mount": + p.Rules = append(p.Rules, newMountFromLog(log)) + case "umount": + p.Rules = append(p.Rules, newUmountFromLog(log)) + case "remount": + p.Rules = append(p.Rules, newRemountFromLog(log)) + case "pivotroot": + p.Rules = append(p.Rules, newPivotRootFromLog(log)) + } + } + case "posix_mqueue", "sysv_mqueue": + p.Rules = append(p.Rules, newMqueueFromLog(log)) + case "signal": + p.Rules = append(p.Rules, newSignalFromLog(log)) + case "ptrace": + p.Rules = append(p.Rules, newPtraceFromLog(log)) + case "namespace": + p.Rules = append(p.Rules, newUsernsFromLog(log)) + case "unix": + 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, newChangeProfileFromLog(log)) + } else { + p.Rules = append(p.Rules, newFileFromLog(log)) + } + default: + if strings.Contains(log["operation"], "dbus") { + p.Rules = append(p.Rules, newDbusFromLog(log)) + } else if log["family"] == "unix" { + p.Rules = append(p.Rules, newUnixFromLog(log)) + } + } +} diff --git a/pkg/aa/ptrace.go b/pkg/aa/ptrace.go index ff6be070e..17b04cb9a 100644 --- a/pkg/aa/ptrace.go +++ b/pkg/aa/ptrace.go @@ -7,7 +7,7 @@ package aa type Ptrace struct { RuleBase Qualifier - Access string + Access []string Peer string } @@ -15,15 +15,15 @@ func newPtraceFromLog(log map[string]string) Rule { return &Ptrace{ RuleBase: newRuleFromLog(log), Qualifier: newQualifierFromLog(log), - Access: toAccess(log["requested_mask"]), + Access: toAccess(tokPTRACE, log["requested_mask"]), Peer: log["peer"], } } func (r *Ptrace) Less(other any) bool { o, _ := other.(*Ptrace) - if r.Access != o.Access { - return r.Access < o.Access + if len(r.Access) != len(o.Access) { + return len(r.Access) < len(o.Access) } if r.Peer != o.Peer { return r.Peer == o.Peer @@ -33,6 +33,6 @@ func (r *Ptrace) Less(other any) bool { func (r *Ptrace) Equals(other any) bool { o, _ := other.(*Ptrace) - return r.Access == o.Access && r.Peer == o.Peer && + return slices.Equal(r.Access, o.Access) && r.Peer == o.Peer && r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/rules_test.go b/pkg/aa/rules_test.go index 4e9f94af1..96d7a5df1 100644 --- a/pkg/aa/rules_test.go +++ b/pkg/aa/rules_test.go @@ -251,9 +251,9 @@ func TestRule_Less(t *testing.T) { }, { name: "file/access", - rule: &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: "r"}, - other: &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: "w"}, - want: true, + rule: &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: []string{"r"}}, + other: &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: []string{"w"}}, + want: false, }, { name: "file/close", diff --git a/pkg/aa/signal.go b/pkg/aa/signal.go index ca8706bcf..13deb74df 100644 --- a/pkg/aa/signal.go +++ b/pkg/aa/signal.go @@ -7,8 +7,8 @@ package aa type Signal struct { RuleBase Qualifier - Access string - Set string + Access []string + Set []string Peer string } @@ -16,19 +16,19 @@ func newSignalFromLog(log map[string]string) Rule { return &Signal{ RuleBase: newRuleFromLog(log), Qualifier: newQualifierFromLog(log), - Access: toAccess(log["requested_mask"]), - Set: log["signal"], + Access: toAccess(tokSIGNAL, log["requested_mask"]), + Set: toAccess(tokSIGNAL, log["signal"]), Peer: log["peer"], } } func (r *Signal) Less(other any) bool { o, _ := other.(*Signal) - if r.Access != o.Access { - return r.Access < o.Access + if len(r.Access) != len(o.Access) { + return len(r.Access) < len(o.Access) } - if r.Set != o.Set { - return r.Set < o.Set + if len(r.Set) != len(o.Set) { + return len(r.Set) < len(o.Set) } if r.Peer != o.Peer { return r.Peer < o.Peer @@ -38,6 +38,6 @@ func (r *Signal) Less(other any) bool { func (r *Signal) Equals(other any) bool { o, _ := other.(*Signal) - return r.Access == o.Access && r.Set == o.Set && + return slices.Equal(r.Access, o.Access) && slices.Equal(r.Set, o.Set) && r.Peer == o.Peer && r.Qualifier.Equals(o.Qualifier) } diff --git a/pkg/aa/template.go b/pkg/aa/template.go index 2a4d0e0a1..2e94480b2 100644 --- a/pkg/aa/template.go +++ b/pkg/aa/template.go @@ -33,19 +33,10 @@ var ( } // convert apparmor requested mask to apparmor access mode - requestedMaskToAccess = map[string]string{ - "a": "w", - "ac": "w", - "c": "w", - "d": "w", - "m": "rm", - "ra": "rw", - "wc": "w", - "wd": "w", - "wr": "rw", - "wrc": "rw", - "wrd": "rw", - "x": "rix", + maskToAccess = map[string]string{ + "a": "w", + "c": "w", + "d": "w", } // The order the apparmor rules should be sorted @@ -172,9 +163,38 @@ func getLetterIn(alphabet []string, in string) string { return "" } -func toAccess(mask string) string { - if requestedMaskToAccess[mask] != "" { - return requestedMaskToAccess[mask] +// Helper function to convert a access string to slice of access +func toAccess(constraint string, input string) []string { + var res []string + + switch constraint { + case "file", "file-log": + raw := strings.Split(input, "") + trans := []string{} + for _, access := range raw { + if slices.Contains(fileAccess, access) { + res = append(res, access) + } else if maskToAccess[access] != "" { + res = append(res, maskToAccess[access]) + trans = append(trans, access) + } + } + + if constraint != "file-log" { + transition := strings.Join(trans, "") + if len(transition) > 0 { + if slices.Contains(fileExecTransition, transition) { + res = append(res, transition) + } else { + panic("unrecognized pattern: " + transition) + } + } + } + return res + + default: + res = strings.Fields(input) + slices.Sort(res) + return slices.Compact(res) } - return mask } diff --git a/pkg/aa/unix.go b/pkg/aa/unix.go index ca9415be8..f734f327e 100644 --- a/pkg/aa/unix.go +++ b/pkg/aa/unix.go @@ -7,7 +7,7 @@ package aa type Unix struct { RuleBase Qualifier - Access string + Access []string Type string Protocol string Address string @@ -22,7 +22,7 @@ func newUnixFromLog(log map[string]string) Rule { return &Unix{ RuleBase: newRuleFromLog(log), Qualifier: newQualifierFromLog(log), - Access: toAccess(log["requested_mask"]), + Access: toAccess(tokUNIX, log["requested_mask"]), Type: log["sock_type"], Protocol: log["protocol"], Address: log["addr"], @@ -36,8 +36,8 @@ func newUnixFromLog(log map[string]string) Rule { func (r *Unix) Less(other any) bool { o, _ := other.(*Unix) - if r.Access != o.Access { - return r.Access < o.Access + if len(r.Access) != len(o.Access) { + return len(r.Access) < len(o.Access) } if r.Type != o.Type { return r.Type < o.Type @@ -68,7 +68,7 @@ func (r *Unix) Less(other any) bool { func (r *Unix) Equals(other any) bool { o, _ := other.(*Unix) - return r.Access == o.Access && r.Type == o.Type && + return slices.Equal(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.PeerLabel == o.PeerLabel && r.PeerAddr == o.PeerAddr && diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go index f14aefd48..bc95dd03f 100644 --- a/pkg/logs/logs.go +++ b/pkg/logs/logs.go @@ -197,8 +197,8 @@ func (aaLogs AppArmorLogs) String() string { } // ParseToProfiles convert the log data into a new AppArmorProfiles -func (aaLogs AppArmorLogs) ParseToProfiles() aa.AppArmorProfileFiles { - profiles := make(aa.AppArmorProfileFiles, 0) +func (aaLogs AppArmorLogs) ParseToProfiles() map[string]*aa.Profile { + profiles := make(map[string]*aa.Profile, 0) for _, log := range aaLogs { name := "" if strings.Contains(log["operation"], "dbus") { @@ -208,9 +208,7 @@ func (aaLogs AppArmorLogs) ParseToProfiles() aa.AppArmorProfileFiles { } if _, ok := profiles[name]; !ok { - profile := &aa.AppArmorProfileFile{ - Profiles: []*aa.Profile{{Header: aa.Header{Name: name}}}, - } + profile := &aa.Profile{Header: aa.Header{Name: name}} profile.AddRule(log) profiles[name] = profile } else { diff --git a/pkg/logs/logs_test.go b/pkg/logs/logs_test.go index cd2d2c520..eb92f4ed9 100644 --- a/pkg/logs/logs_test.go +++ b/pkg/logs/logs_test.go @@ -292,46 +292,42 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) { tests := []struct { name string aaLogs AppArmorLogs - want aa.AppArmorProfileFiles + want map[string]*aa.Profile }{ { name: "", aaLogs: append(append(refKmod, refPowerProfiles...), refKmod...), - want: aa.AppArmorProfileFiles{ - "kmod": &aa.AppArmorProfileFile{ - Profiles: []*aa.Profile{{ - Header: aa.Header{Name: "kmod"}, - Rules: aa.Rules{ - &aa.Unix{ - RuleBase: aa.RuleBase{FileInherit: true}, - Access: "send receive", - Type: "stream", - Protocol: "0", - }, - &aa.Unix{ - RuleBase: aa.RuleBase{FileInherit: true}, - Access: "send receive", - Type: "stream", - Protocol: "0", - }, + want: map[string]*aa.Profile{ + "kmod": { + Header: aa.Header{Name: "kmod"}, + Rules: aa.Rules{ + &aa.Unix{ + RuleBase: aa.RuleBase{FileInherit: true}, + Access: []string{"receive", "send"}, + Type: "stream", + Protocol: "0", }, - }}, + &aa.Unix{ + RuleBase: aa.RuleBase{FileInherit: true}, + Access: []string{"receive", "send"}, + Type: "stream", + Protocol: "0", + }, + }, }, - "power-profiles-daemon": &aa.AppArmorProfileFile{ - Profiles: []*aa.Profile{{ - Header: aa.Header{Name: "power-profiles-daemon"}, - Rules: aa.Rules{ - &aa.Dbus{ - Access: "send", - Bus: "system", - Path: "/org/freedesktop/DBus", - Interface: "org.freedesktop.DBus", - Member: "AddMatch", - PeerName: "org.freedesktop.DBus", - PeerLabel: "dbus-daemon", - }, + "power-profiles-daemon": { + Header: aa.Header{Name: "power-profiles-daemon"}, + Rules: aa.Rules{ + &aa.Dbus{ + Access: []string{"send"}, + Bus: "system", + Path: "/org/freedesktop/DBus", + Interface: "org.freedesktop.DBus", + Member: "AddMatch", + PeerName: "org.freedesktop.DBus", + PeerLabel: "dbus-daemon", }, - }}, + }, }, }, },