feat(aa): improve apparmor struct.

This commit is contained in:
Alexandre Pujol 2024-04-14 23:58:34 +01:00
parent ea1736083a
commit ab4feda5ba
No known key found for this signature in database
GPG key ID: C5469996F0DF68EC
28 changed files with 638 additions and 496 deletions

View file

@ -5,23 +5,25 @@
package aa package aa
type Capability struct { type Capability struct {
Rule
Qualifier Qualifier
Name string Name string
} }
func CapabilityFromLog(log map[string]string) ApparmorRule { func newCapabilityFromLog(log map[string]string) *Capability {
return &Capability{ return &Capability{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Name: log["capname"], Name: log["capname"],
} }
} }
func (r *Capability) Less(other any) bool { func (r *Capability) Less(other any) bool {
o, _ := other.(*Capability) o, _ := other.(*Capability)
if r.Name == o.Name { if r.Name != o.Name {
return r.Qualifier.Less(o.Qualifier) return r.Name < o.Name
} }
return r.Name < o.Name return r.Qualifier.Less(o.Qualifier)
} }
func (r *Capability) Equals(other any) bool { func (r *Capability) Equals(other any) bool {

View file

@ -5,15 +5,17 @@
package aa package aa
type ChangeProfile struct { type ChangeProfile struct {
Rule
Qualifier Qualifier
ExecMode string ExecMode string
Exec string Exec string
ProfileName string ProfileName string
} }
func ChangeProfileFromLog(log map[string]string) ApparmorRule { func newChangeProfileFromLog(log map[string]string) *ChangeProfile {
return &ChangeProfile{ return &ChangeProfile{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
ExecMode: log["mode"], ExecMode: log["mode"],
Exec: log["exec"], Exec: log["exec"],
ProfileName: log["target"], ProfileName: log["target"],
@ -22,16 +24,20 @@ func ChangeProfileFromLog(log map[string]string) ApparmorRule {
func (r *ChangeProfile) Less(other any) bool { func (r *ChangeProfile) Less(other any) bool {
o, _ := other.(*ChangeProfile) o, _ := other.(*ChangeProfile)
if r.ExecMode == o.ExecMode { if r.ExecMode != o.ExecMode {
if r.Exec == o.Exec { return r.ExecMode < o.ExecMode
return r.ProfileName < o.ProfileName }
} if r.Exec != o.Exec {
return 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 { func (r *ChangeProfile) Equals(other any) bool {
o, _ := other.(*ChangeProfile) 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)
} }

View file

@ -71,13 +71,13 @@ var (
"flags": "rw, rbind", "flags": "rw, rbind",
} }
mount1 = &Mount{ mount1 = &Mount{
Qualifier: Qualifier{Comment: "failed perms check"}, Rule: Rule{Comment: "failed perms check"},
MountConditions: MountConditions{FsType: "overlay"}, MountConditions: MountConditions{FsType: "overlay"},
Source: "overlay", Source: "overlay",
MountPoint: "/var/lib/docker/overlay2/opaque-bug-check1209538631/merged/", MountPoint: "/var/lib/docker/overlay2/opaque-bug-check1209538631/merged/",
} }
mount2 = &Mount{ mount2 = &Mount{
Qualifier: Qualifier{Comment: "failed perms check"}, Rule: Rule{Comment: "failed perms check"},
MountConditions: MountConditions{Options: []string{"rw", "rbind"}}, MountConditions: MountConditions{Options: []string{"rw", "rbind"}},
Source: "/oldroot/dev/tty", Source: "/oldroot/dev/tty",
MountPoint: "/newroot/dev/tty", MountPoint: "/newroot/dev/tty",
@ -197,17 +197,17 @@ var (
"protocol": "0", "protocol": "0",
} }
unix1 = &Unix{ unix1 = &Unix{
Access: "send receive", Access: "send receive",
Type: "stream", Type: "stream",
Protocol: "0", Protocol: "0",
Address: "none", Address: "none",
Peer: "dbus-daemon", PeerAddr: "@/tmp/dbus-AaKMpxzC4k",
PeerAddr: "@/tmp/dbus-AaKMpxzC4k", PeerLabel: "dbus-daemon",
} }
unix2 = &Unix{ unix2 = &Unix{
Qualifier: Qualifier{FileInherit: true}, Rule: Rule{FileInherit: true},
Access: "receive", Access: "receive",
Type: "stream", Type: "stream",
} }
// Dbus // Dbus
@ -236,11 +236,11 @@ var (
dbus1 = &Dbus{ dbus1 = &Dbus{
Access: "receive", Access: "receive",
Bus: "session", Bus: "session",
Name: ":1.15",
Path: "/org/gtk/vfs/metadata", Path: "/org/gtk/vfs/metadata",
Interface: "org.gtk.vfs.Metadata", Interface: "org.gtk.vfs.Metadata",
Member: "Remove", Member: "Remove",
Label: "tracker-extract", PeerName: ":1.15",
PeerLabel: "tracker-extract",
} }
dbus2 = &Dbus{ dbus2 = &Dbus{
Access: "bind", Access: "bind",
@ -285,8 +285,9 @@ var (
} }
file1 = &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: "r"} file1 = &File{Path: "/usr/share/poppler/cMap/Identity-H", Access: "r"}
file2 = &File{ file2 = &File{
Qualifier: Qualifier{Owner: true, NoNewPrivs: true}, Rule: Rule{NoNewPrivs: true},
Path: "@{PROC}/4163/cgroup", Owner: true,
Access: "r", Path: "@{PROC}/4163/cgroup",
Access: "r",
} }
) )

View file

@ -5,6 +5,7 @@
package aa package aa
type Dbus struct { type Dbus struct {
Rule
Qualifier Qualifier
Access string Access string
Bus string Bus string
@ -12,45 +13,58 @@ type Dbus struct {
Path string Path string
Interface string Interface string
Member 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{ return &Dbus{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Access: log["mask"], Access: log["mask"],
Bus: log["bus"], Bus: log["bus"],
Name: log["name"], Name: name,
Path: log["path"], Path: log["path"],
Interface: log["interface"], Interface: log["interface"],
Member: log["member"], Member: log["member"],
Label: log["peer_label"], PeerName: peerName,
PeerLabel: log["peer_label"],
} }
} }
func (r *Dbus) Less(other any) bool { func (r *Dbus) Less(other any) bool {
o, _ := other.(*Dbus) o, _ := other.(*Dbus)
if r.Qualifier.Equals(o.Qualifier) { if r.Access != o.Access {
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
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }
@ -58,5 +72,6 @@ func (r *Dbus) Equals(other any) bool {
o, _ := other.(*Dbus) o, _ := other.(*Dbus)
return r.Access == o.Access && r.Bus == o.Bus && r.Name == o.Name && return r.Access == o.Access && r.Bus == o.Bus && r.Name == o.Name &&
r.Path == o.Path && r.Interface == o.Interface && 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)
} }

View file

@ -5,15 +5,26 @@
package aa package aa
type File struct { type File struct {
Rule
Qualifier Qualifier
Owner bool
Path string Path string
Access string Access string
Target 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{ return &File{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Owner: owner,
Path: log["name"], Path: log["name"],
Access: toAccess(log["requested_mask"]), Access: toAccess(log["requested_mask"]),
Target: log["target"], Target: log["target"],
@ -24,23 +35,26 @@ func (r *File) Less(other any) bool {
o, _ := other.(*File) o, _ := other.(*File)
letterR := getLetterIn(fileAlphabet, r.Path) letterR := getLetterIn(fileAlphabet, r.Path)
letterO := getLetterIn(fileAlphabet, o.Path) letterO := getLetterIn(fileAlphabet, o.Path)
if fileWeights[letterR] == fileWeights[letterO] || letterR == "" || letterO == "" { if fileWeights[letterR] != fileWeights[letterO] && letterR != "" && letterO != "" {
if r.Qualifier.Equals(o.Qualifier) { return fileWeights[letterR] < fileWeights[letterO]
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)
} }
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 { func (r *File) Equals(other any) bool {
o, _ := other.(*File) 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) r.Target == o.Target && r.Qualifier.Equals(o.Qualifier)
} }

View file

@ -1,28 +0,0 @@
// apparmor.d - Full set of apparmor profiles
// Copyright (C) 2021-2024 Alexandre Pujol <alexandre@pujol.io>
// 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
}

View file

@ -5,19 +5,29 @@
package aa package aa
type IOUring struct { type IOUring struct {
Rule
Qualifier Qualifier
Access string Access string
Label 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 { func (r *IOUring) Less(other any) bool {
o, _ := other.(*IOUring) o, _ := other.(*IOUring)
if r.Qualifier.Equals(o.Qualifier) { if r.Access != o.Access {
if r.Access == o.Access {
return r.Label < o.Label
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }

View file

@ -15,7 +15,7 @@ type MountConditions struct {
Options []string Options []string
} }
func MountConditionsFromLog(log map[string]string) MountConditions { func newMountConditionsFromLog(log map[string]string) MountConditions {
if _, present := log["flags"]; present { if _, present := log["flags"]; present {
return MountConditions{ return MountConditions{
FsType: log["fstype"], FsType: log["fstype"],
@ -26,10 +26,10 @@ func MountConditionsFromLog(log map[string]string) MountConditions {
} }
func (m MountConditions) Less(other MountConditions) bool { func (m MountConditions) Less(other MountConditions) bool {
if m.FsType == other.FsType { if m.FsType != other.FsType {
return len(m.Options) < len(other.Options) return m.FsType < other.FsType
} }
return m.FsType < other.FsType return len(m.Options) < len(other.Options)
} }
func (m MountConditions) Equals(other MountConditions) bool { func (m MountConditions) Equals(other MountConditions) bool {
@ -37,16 +37,18 @@ func (m MountConditions) Equals(other MountConditions) bool {
} }
type Mount struct { type Mount struct {
Rule
Qualifier Qualifier
MountConditions MountConditions
Source string Source string
MountPoint string MountPoint string
} }
func MountFromLog(log map[string]string) ApparmorRule { func newMountFromLog(log map[string]string) *Mount {
return &Mount{ return &Mount{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
MountConditions: MountConditionsFromLog(log), Qualifier: newQualifierFromLog(log),
MountConditions: newMountConditionsFromLog(log),
Source: log["srcname"], Source: log["srcname"],
MountPoint: log["name"], MountPoint: log["name"],
} }
@ -54,15 +56,15 @@ func MountFromLog(log map[string]string) ApparmorRule {
func (r *Mount) Less(other any) bool { func (r *Mount) Less(other any) bool {
o, _ := other.(*Mount) o, _ := other.(*Mount)
if r.Qualifier.Equals(o.Qualifier) { if r.Source != o.Source {
if r.Source == o.Source {
if r.MountPoint == o.MountPoint {
return r.MountConditions.Less(o.MountConditions)
}
return r.MountPoint < o.MountPoint
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }
@ -74,27 +76,29 @@ func (r *Mount) Equals(other any) bool {
} }
type Umount struct { type Umount struct {
Rule
Qualifier Qualifier
MountConditions MountConditions
MountPoint string MountPoint string
} }
func UmountFromLog(log map[string]string) ApparmorRule { func newUmountFromLog(log map[string]string) *Umount {
return &Umount{ return &Umount{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
MountConditions: MountConditionsFromLog(log), Qualifier: newQualifierFromLog(log),
MountConditions: newMountConditionsFromLog(log),
MountPoint: log["name"], MountPoint: log["name"],
} }
} }
func (r *Umount) Less(other any) bool { func (r *Umount) Less(other any) bool {
o, _ := other.(*Umount) o, _ := other.(*Umount)
if r.Qualifier.Equals(o.Qualifier) { if r.MountPoint != o.MountPoint {
if r.MountPoint == o.MountPoint {
return r.MountConditions.Less(o.MountConditions)
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }
@ -106,27 +110,29 @@ func (r *Umount) Equals(other any) bool {
} }
type Remount struct { type Remount struct {
Rule
Qualifier Qualifier
MountConditions MountConditions
MountPoint string MountPoint string
} }
func RemountFromLog(log map[string]string) ApparmorRule { func newRemountFromLog(log map[string]string) *Remount {
return &Remount{ return &Remount{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
MountConditions: MountConditionsFromLog(log), Qualifier: newQualifierFromLog(log),
MountConditions: newMountConditionsFromLog(log),
MountPoint: log["name"], MountPoint: log["name"],
} }
} }
func (r *Remount) Less(other any) bool { func (r *Remount) Less(other any) bool {
o, _ := other.(*Remount) o, _ := other.(*Remount)
if r.Qualifier.Equals(o.Qualifier) { if r.MountPoint != o.MountPoint {
if r.MountPoint == o.MountPoint {
return r.MountConditions.Less(o.MountConditions)
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }

View file

@ -4,9 +4,12 @@
package aa package aa
import "strings" import (
"strings"
)
type Mqueue struct { type Mqueue struct {
Rule
Qualifier Qualifier
Access string Access string
Type string Type string
@ -14,7 +17,7 @@ type Mqueue struct {
Name string Name string
} }
func MqueueFromLog(log map[string]string) ApparmorRule { func newMqueueFromLog(log map[string]string) *Mqueue {
mqueueType := "posix" mqueueType := "posix"
if strings.Contains(log["class"], "posix") { if strings.Contains(log["class"], "posix") {
mqueueType = "posix" mqueueType = "posix"
@ -22,7 +25,8 @@ func MqueueFromLog(log map[string]string) ApparmorRule {
mqueueType = "sysv" mqueueType = "sysv"
} }
return &Mqueue{ return &Mqueue{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Access: toAccess(log["requested"]), Access: toAccess(log["requested"]),
Type: mqueueType, Type: mqueueType,
Label: log["label"], Label: log["label"],
@ -32,19 +36,20 @@ func MqueueFromLog(log map[string]string) ApparmorRule {
func (r *Mqueue) Less(other any) bool { func (r *Mqueue) Less(other any) bool {
o, _ := other.(*Mqueue) o, _ := other.(*Mqueue)
if r.Qualifier.Equals(o.Qualifier) { if r.Access != o.Access {
if r.Access == o.Access {
if r.Type == o.Type {
return r.Label < o.Label
}
return r.Type < o.Type
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }
func (r *Mqueue) Equals(other any) bool { func (r *Mqueue) Equals(other any) bool {
o, _ := other.(*Mqueue) 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)
} }

View file

@ -10,54 +10,63 @@ type AddressExpr struct {
Port string 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 { func (r AddressExpr) Equals(other AddressExpr) bool {
return r.Source == other.Source && r.Destination == other.Destination && return r.Source == other.Source && r.Destination == other.Destination &&
r.Port == other.Port 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 { type Network struct {
Rule
Qualifier Qualifier
AddressExpr
Domain string Domain string
Type string Type string
Protocol string Protocol string
AddressExpr
} }
func NetworkFromLog(log map[string]string) ApparmorRule { func newNetworkFromLog(log map[string]string) *Network {
return &Network{ return &Network{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
AddressExpr: AddressExpr{ Qualifier: newQualifierFromLog(log),
Source: log["laddr"], AddressExpr: newAddressExprFromLog(log),
Destination: log["faddr"], Domain: log["family"],
Port: log["lport"], Type: log["sock_type"],
}, Protocol: log["protocol"],
Domain: log["family"],
Type: log["sock_type"],
Protocol: log["protocol"],
} }
} }
func (r *Network) Less(other any) bool { func (r *Network) Less(other any) bool {
o, _ := other.(*Network) o, _ := other.(*Network)
if r.Qualifier.Equals(o.Qualifier) { if r.Domain != o.Domain {
if r.Domain == o.Domain {
if r.Type == o.Type {
return r.Protocol < o.Protocol
}
return r.Type < o.Type
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }

View file

@ -5,15 +5,17 @@
package aa package aa
type PivotRoot struct { type PivotRoot struct {
Rule
Qualifier Qualifier
OldRoot string OldRoot string
NewRoot string NewRoot string
TargetProfile string TargetProfile string
} }
func PivotRootFromLog(log map[string]string) ApparmorRule { func newPivotRootFromLog(log map[string]string) *PivotRoot {
return &PivotRoot{ return &PivotRoot{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
OldRoot: log["srcname"], OldRoot: log["srcname"],
NewRoot: log["name"], NewRoot: log["name"],
TargetProfile: "", TargetProfile: "",
@ -22,15 +24,15 @@ func PivotRootFromLog(log map[string]string) ApparmorRule {
func (r *PivotRoot) Less(other any) bool { func (r *PivotRoot) Less(other any) bool {
o, _ := other.(*PivotRoot) o, _ := other.(*PivotRoot)
if r.Qualifier.Equals(o.Qualifier) { if r.OldRoot != o.OldRoot {
if r.OldRoot == o.OldRoot {
if r.NewRoot == o.NewRoot {
return r.TargetProfile < o.TargetProfile
}
return r.NewRoot < o.NewRoot
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }

87
pkg/aa/preamble.go Normal file
View file

@ -0,0 +1,87 @@
// apparmor.d - Full set of apparmor profiles
// Copyright (C) 2021-2024 Alexandre Pujol <alexandre@pujol.io>
// 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)
}

View file

@ -31,10 +31,10 @@ type AppArmorProfile struct {
// Preamble section of a profile // Preamble section of a profile
type Preamble struct { type Preamble struct {
Abi []Abi Abi []*Abi
Includes []Include Includes []*Include
Aliases []Alias Aliases []*Alias
Variables []Variable Variables []*Variable
} }
// Profile section of a profile // Profile section of a profile
@ -78,7 +78,7 @@ func (p *AppArmorProfile) AddRule(log map[string]string) {
} }
case "-13": case "-13":
if strings.Contains(log["info"], "namespace creation restricted") { 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") { } else if strings.Contains(log["info"], "disconnected path") && !slices.Contains(p.Flags, "attach_disconnected") {
p.Flags = append(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"] { switch log["class"] {
case "cap": case "cap":
p.Rules = append(p.Rules, CapabilityFromLog(log)) p.Rules = append(p.Rules, newCapabilityFromLog(log))
case "net": case "net":
if log["family"] == "unix" { if log["family"] == "unix" {
p.Rules = append(p.Rules, UnixFromLog(log)) p.Rules = append(p.Rules, newUnixFromLog(log))
} else { } else {
p.Rules = append(p.Rules, NetworkFromLog(log)) p.Rules = append(p.Rules, newNetworkFromLog(log))
} }
case "mount": case "mount":
if strings.Contains(log["flags"], "remount") { if strings.Contains(log["flags"], "remount") {
p.Rules = append(p.Rules, RemountFromLog(log)) p.Rules = append(p.Rules, newRemountFromLog(log))
} else { } else {
switch log["operation"] { switch log["operation"] {
case "mount": case "mount":
p.Rules = append(p.Rules, MountFromLog(log)) p.Rules = append(p.Rules, newMountFromLog(log))
case "umount": case "umount":
p.Rules = append(p.Rules, UmountFromLog(log)) p.Rules = append(p.Rules, newUmountFromLog(log))
case "remount": case "remount":
p.Rules = append(p.Rules, RemountFromLog(log)) p.Rules = append(p.Rules, newRemountFromLog(log))
case "pivotroot": case "pivotroot":
p.Rules = append(p.Rules, PivotRootFromLog(log)) p.Rules = append(p.Rules, newPivotRootFromLog(log))
} }
} }
case "posix_mqueue", "sysv_mqueue": case "posix_mqueue", "sysv_mqueue":
p.Rules = append(p.Rules, MqueueFromLog(log)) p.Rules = append(p.Rules, newMqueueFromLog(log))
case "signal": case "signal":
p.Rules = append(p.Rules, SignalFromLog(log)) p.Rules = append(p.Rules, newSignalFromLog(log))
case "ptrace": case "ptrace":
p.Rules = append(p.Rules, PtraceFromLog(log)) p.Rules = append(p.Rules, newPtraceFromLog(log))
case "namespace": case "namespace":
p.Rules = append(p.Rules, UsernsFromLog(log)) p.Rules = append(p.Rules, newUsernsFromLog(log))
case "unix": 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": case "file":
if log["operation"] == "change_onexec" { if log["operation"] == "change_onexec" {
p.Rules = append(p.Rules, ChangeProfileFromLog(log)) p.Rules = append(p.Rules, newChangeProfileFromLog(log))
} else { } else {
p.Rules = append(p.Rules, FileFromLog(log)) p.Rules = append(p.Rules, newFileFromLog(log))
} }
default: default:
if strings.Contains(log["operation"], "dbus") { 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" { } 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: // Steps:
// - Remove identical rules // - Remove identical rules
// - Merge rule access. Eg: for same path, 'r' and 'w' becomes 'rw' // - 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 // Follow: https://apparmor.pujol.io/development/guidelines/#the-file-block
func (p *AppArmorProfile) Format() { func (p *AppArmorProfile) Format() {
const prefixOwner = " " const prefixOwner = " "

View file

@ -43,10 +43,10 @@ func TestAppArmorProfile_String(t *testing.T) {
name: "foo", name: "foo",
p: &AppArmorProfile{ p: &AppArmorProfile{
Preamble: Preamble{ Preamble: Preamble{
Abi: []Abi{{IsMagic: true, Path: "abi/4.0"}}, Abi: []*Abi{{IsMagic: true, Path: "abi/4.0"}},
Includes: []Include{{IsMagic: true, Path: "tunables/global"}}, Includes: []*Include{{IsMagic: true, Path: "tunables/global"}},
Aliases: []Alias{{Path: "/mnt/usr", RewrittenPath: "/usr"}}, Aliases: []*Alias{{Path: "/mnt/usr", RewrittenPath: "/usr"}},
Variables: []Variable{{ Variables: []*Variable{{
Name: "exec_path", Name: "exec_path",
Values: []string{"@{bin}/foo", "@{lib}/foo"}, Values: []string{"@{bin}/foo", "@{lib}/foo"},
}}, }},
@ -83,11 +83,11 @@ func TestAppArmorProfile_String(t *testing.T) {
}, },
&Ptrace{Access: "read", Peer: "nautilus"}, &Ptrace{Access: "read", Peer: "nautilus"},
&Unix{ &Unix{
Access: "send receive", Access: "send receive",
Type: "stream", Type: "stream",
Address: "@/tmp/.ICE-unix/1995", Address: "@/tmp/.ICE-unix/1995",
Peer: "gnome-shell", PeerLabel: "gnome-shell",
PeerAddr: "none", PeerAddr: "none",
}, },
&Dbus{ &Dbus{
Access: "bind", Access: "bind",
@ -97,11 +97,11 @@ func TestAppArmorProfile_String(t *testing.T) {
&Dbus{ &Dbus{
Access: "receive", Access: "receive",
Bus: "system", Bus: "system",
Name: ":1.3",
Path: "/org/freedesktop/DBus", Path: "/org/freedesktop/DBus",
Interface: "org.freedesktop.DBus", Interface: "org.freedesktop.DBus",
Member: "AddMatch", 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: "/opt/intel/oneapi/compiler/*/linux/lib/*.so./*", Access: "rm"},
&File{Path: "@{PROC}/@{pid}/task/@{tid}/comm", Access: "rw"}, &File{Path: "@{PROC}/@{pid}/task/@{tid}/comm", Access: "rw"},
@ -290,9 +290,9 @@ func TestAppArmorProfile_Integration(t *testing.T) {
name: "aa-status", name: "aa-status",
p: &AppArmorProfile{ p: &AppArmorProfile{
Preamble: Preamble{ Preamble: Preamble{
Abi: []Abi{{IsMagic: true, Path: "abi/3.0"}}, Abi: []*Abi{{IsMagic: true, Path: "abi/3.0"}},
Includes: []Include{{IsMagic: true, Path: "tunables/global"}}, Includes: []*Include{{IsMagic: true, Path: "tunables/global"}},
Variables: []Variable{{ Variables: []*Variable{{
Name: "exec_path", Name: "exec_path",
Values: []string{"@{bin}/aa-status", "@{bin}/apparmor_status"}, 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: "@{sys}/kernel/security/apparmor/profiles", Access: "r"},
&File{Path: "@{PROC}/@{pids}/attr/current", Access: "r"}, &File{Path: "@{PROC}/@{pids}/attr/current", Access: "r"},
&Include{IsMagic: true, Path: "abstractions/consoles"}, &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"}, &Include{IsMagic: true, Path: "abstractions/base"},
&File{Path: "/dev/tty@{int}", Access: "rw"}, &File{Path: "/dev/tty@{int}", Access: "rw"},
&Capability{Name: "sys_ptrace"}, &Capability{Name: "sys_ptrace"},

View file

@ -5,14 +5,16 @@
package aa package aa
type Ptrace struct { type Ptrace struct {
Rule
Qualifier Qualifier
Access string Access string
Peer string Peer string
} }
func PtraceFromLog(log map[string]string) ApparmorRule { func newPtraceFromLog(log map[string]string) *Ptrace {
return &Ptrace{ return &Ptrace{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Access: toAccess(log["requested_mask"]), Access: toAccess(log["requested_mask"]),
Peer: log["peer"], Peer: log["peer"],
} }
@ -20,12 +22,12 @@ func PtraceFromLog(log map[string]string) ApparmorRule {
func (r *Ptrace) Less(other any) bool { func (r *Ptrace) Less(other any) bool {
o, _ := other.(*Ptrace) o, _ := other.(*Ptrace)
if r.Qualifier.Equals(o.Qualifier) { if r.Access != o.Access {
if r.Access == o.Access {
return r.Peer == o.Peer
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }

View file

@ -10,15 +10,24 @@ type Rlimit struct {
Value string 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 { func (r *Rlimit) Less(other any) bool {
o, _ := other.(*Rlimit) o, _ := other.(*Rlimit)
if r.Key == o.Key { if r.Key != o.Key {
if r.Op == o.Op { return r.Key < o.Key
return r.Value < o.Value }
} if r.Op != o.Op {
return 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 { func (r *Rlimit) Equals(other any) bool {

View file

@ -5,6 +5,7 @@
package aa package aa
import ( import (
"fmt"
"strings" "strings"
) )
@ -12,43 +13,12 @@ type Rule struct {
Comment string Comment string
NoNewPrivs bool NoNewPrivs bool
FileInherit 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 Prefix string
Padding string Padding string
Optional bool
} }
func NewQualifierFromLog(log map[string]string) Qualifier { func newRuleFromLog(log map[string]string) Rule {
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
}
fileInherit := false fileInherit := false
if log["operation"] == "file_inherit" { if log["operation"] == "file_inherit" {
fileInherit = true fileInherit = true
@ -76,62 +46,54 @@ func NewQualifierFromLog(log map[string]string) Qualifier {
default: default:
} }
return Qualifier{ return Rule{
Audit: audit, Comment: msg,
Owner: owner,
NoNewPrivs: noNewPrivs, NoNewPrivs: noNewPrivs,
FileInherit: fileInherit, FileInherit: fileInherit,
Optional: optional, 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 { func (r Qualifier) Less(other Qualifier) bool {
if r.Owner == other.Owner { if r.Audit != other.Audit {
if r.Audit == other.Audit {
return r.AccessType < other.AccessType
}
return r.Audit return r.Audit
} }
return other.Owner return r.AccessType < other.AccessType
} }
func (r Qualifier) Equals(other Qualifier) bool { func (r Qualifier) Equals(other Qualifier) bool {
return r.Audit == other.Audit && r.AccessType == other.AccessType && return r.Audit == other.Audit && r.AccessType == other.AccessType
r.Owner == other.Owner && r.NoNewPrivs == other.NoNewPrivs &&
r.FileInherit == other.FileInherit
} }
// Preamble specific rules type All struct {
Rule
type Abi struct {
Path string
IsMagic bool
} }
func (r Abi) Less(other Abi) bool { func (r *All) Less(other any) bool {
if r.Path == other.Path { return false
return r.IsMagic == other.IsMagic
}
return r.Path < other.Path
} }
func (r Abi) Equals(other Abi) bool { func (r *All) Equals(other any) bool {
return r.Path == other.Path && r.IsMagic == other.IsMagic return false
}
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
} }

View file

@ -17,76 +17,100 @@ func TestRule_FromLog(t *testing.T) {
want ApparmorRule want ApparmorRule
}{ }{
{ {
name: "capbability", name: "capbability",
fromLog: CapabilityFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: capability1Log, return newCapabilityFromLog(m)
want: capability1, },
log: capability1Log,
want: capability1,
}, },
{ {
name: "network", name: "network",
fromLog: NetworkFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: network1Log, return newNetworkFromLog(m)
want: network1, },
log: network1Log,
want: network1,
}, },
{ {
name: "mount", name: "mount",
fromLog: MountFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: mount1Log, return newMountFromLog(m)
want: mount1, },
log: mount1Log,
want: mount1,
}, },
{ {
name: "umount", name: "umount",
fromLog: UmountFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: umount1Log, return newUmountFromLog(m)
want: umount1, },
log: umount1Log,
want: umount1,
}, },
{ {
name: "pivotroot", name: "pivotroot",
fromLog: PivotRootFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: pivotroot1Log, return newPivotRootFromLog(m)
want: pivotroot1, },
log: pivotroot1Log,
want: pivotroot1,
}, },
{ {
name: "changeprofile", name: "changeprofile",
fromLog: ChangeProfileFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: changeprofile1Log, return newChangeProfileFromLog(m)
want: changeprofile1, },
log: changeprofile1Log,
want: changeprofile1,
}, },
{ {
name: "signal", name: "signal",
fromLog: SignalFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: signal1Log, return newSignalFromLog(m)
want: signal1, },
log: signal1Log,
want: signal1,
}, },
{ {
name: "ptrace/xdg-document-portal", name: "ptrace/xdg-document-portal",
fromLog: PtraceFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: ptrace1Log, return newPtraceFromLog(m)
want: ptrace1, },
log: ptrace1Log,
want: ptrace1,
}, },
{ {
name: "ptrace/snap-update-ns.firefox", name: "ptrace/snap-update-ns.firefox",
fromLog: PtraceFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: ptrace2Log, return newPtraceFromLog(m)
want: ptrace2, },
log: ptrace2Log,
want: ptrace2,
}, },
{ {
name: "unix", name: "unix",
fromLog: UnixFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: unix1Log, return newUnixFromLog(m)
want: unix1, },
log: unix1Log,
want: unix1,
}, },
{ {
name: "dbus", name: "dbus",
fromLog: DbusFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: dbus1Log, return newDbusFromLog(m)
want: dbus1, },
log: dbus1Log,
want: dbus1,
}, },
{ {
name: "file", name: "file",
fromLog: FileFromLog, fromLog: func(m map[string]string) ApparmorRule {
log: file1Log, return newFileFromLog(m)
want: file1, },
log: file1Log,
want: file1,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -109,13 +133,13 @@ func TestRule_Less(t *testing.T) {
name: "include1", name: "include1",
rule: include1, rule: include1,
other: includeLocal1, other: includeLocal1,
want: true, want: false,
}, },
{ {
name: "include2", name: "include2",
rule: include1, rule: include1,
other: include2, other: include2,
want: true, want: false,
}, },
{ {
name: "include3", name: "include3",
@ -245,9 +269,9 @@ func TestRule_Less(t *testing.T) {
}, },
{ {
name: "file/owner", 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"}, other: &File{Path: "/usr/share/poppler/cMap/Identity-H"},
want: false, want: true,
}, },
{ {
name: "file/access", name: "file/access",

View file

@ -5,15 +5,17 @@
package aa package aa
type Signal struct { type Signal struct {
Rule
Qualifier Qualifier
Access string Access string
Set string Set string
Peer string Peer string
} }
func SignalFromLog(log map[string]string) ApparmorRule { func newSignalFromLog(log map[string]string) *Signal {
return &Signal{ return &Signal{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Access: toAccess(log["requested_mask"]), Access: toAccess(log["requested_mask"]),
Set: log["signal"], Set: log["signal"],
Peer: log["peer"], Peer: log["peer"],
@ -22,15 +24,15 @@ func SignalFromLog(log map[string]string) ApparmorRule {
func (r *Signal) Less(other any) bool { func (r *Signal) Less(other any) bool {
o, _ := other.(*Signal) o, _ := other.(*Signal)
if r.Qualifier.Equals(o.Qualifier) { if r.Access != o.Access {
if r.Access == o.Access {
if r.Set == o.Set {
return r.Peer < o.Peer
}
return r.Set < o.Set
}
return 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) return r.Qualifier.Less(o.Qualifier)
} }

View file

@ -8,4 +8,5 @@
{{- else -}} {{- else -}}
{{ " \"" }}{{ .Path }}{{ "\"" }} {{ " \"" }}{{ .Path }}{{ "\"" }}
{{- end -}} {{- end -}}
{{- template "comment" . -}}
{{- end -}} {{- end -}}

View file

@ -56,7 +56,7 @@
{{- end -}} {{- end -}}
{{- if eq $type "Rlimit" -}} {{- if eq $type "Rlimit" -}}
{{ "set rlimit " }}{{ .Key }} {{ .Op }} {{ .Value }}{{ "," }} {{ "set rlimit " }}{{ .Key }} {{ .Op }} {{ .Value }}{{ "," }}{{ template "comment" . }}
{{- end -}} {{- end -}}
{{- if eq $type "Capability" -}} {{- if eq $type "Capability" -}}
@ -191,15 +191,24 @@
{{- with .Type -}} {{- with .Type -}}
{{ " type=" }}{{ . }} {{ " type=" }}{{ . }}
{{- end -}} {{- end -}}
{{- with .Protocol -}}
{{ " protocol=" }}{{ . }}
{{- end -}}
{{- with .Address -}} {{- with .Address -}}
{{ " addr=" }}{{ . }} {{ " addr=" }}{{ . }}
{{- end -}} {{- end -}}
{{- if .Peer -}} {{- with .Label -}}
{{ " peer=(label=" }}{{ .Peer }} {{ " label=" }}{{ . }}
{{- with .PeerAddr -}} {{- end -}}
{{ ", addr="}}{{ . }} {{- if and .PeerLabel .PeerAddr -}}
{{ " peer=(label=" }}{{ .PeerLabel }}{{ ", addr="}}{{ .PeerAddr }}{{ ")" }}
{{- else -}}
{{- with .PeerLabel -}}
{{ overindent "peer=(label=" }}{{ . }}{{ ")" }}
{{- end -}}
{{- with .PeerAddr -}}
{{ overindent "peer=(addr=" }}{{ . }}{{ ")" }}
{{- end -}} {{- end -}}
{{- ")" -}}
{{- end -}} {{- end -}}
{{- "," -}} {{- "," -}}
{{- template "comment" . -}} {{- template "comment" . -}}
@ -256,13 +265,13 @@
{{- with .Member -}} {{- with .Member -}}
{{ overindent "member=" }}{{ . }}{{ "\n" }} {{ overindent "member=" }}{{ . }}{{ "\n" }}
{{- end -}} {{- end -}}
{{- if and .Name .Label -}} {{- if and .PeerName .PeerLabel -}}
{{ overindent "peer=(name=" }}{{ .Name }}{{ ", label="}}{{ .Label }}{{ ")" }} {{ overindent "peer=(name=" }}{{ .PeerName }}{{ ", label="}}{{ .PeerLabel }}{{ ")" }}
{{- else -}} {{- else -}}
{{- with .Name -}} {{- with .PeerName -}}
{{ overindent "peer=(name=" }}{{ . }}{{ ")" }} {{ overindent "peer=(name=" }}{{ . }}{{ ")" }}
{{- end -}} {{- end -}}
{{- with .Label -}} {{- with .PeerLabel -}}
{{ overindent "peer=(label=" }}{{ . }}{{ ")" }} {{ overindent "peer=(label=" }}{{ . }}{{ ")" }}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
@ -273,6 +282,9 @@
{{- if eq $type "File" -}} {{- if eq $type "File" -}}
{{- template "qualifier" . -}} {{- template "qualifier" . -}}
{{- if .Owner -}}
{{- "owner " -}}
{{- end -}}
{{- .Path -}} {{- .Path -}}
{{- " " -}} {{- " " -}}
{{- with .Padding -}} {{- with .Padding -}}

View file

@ -2,9 +2,6 @@
{{- with .Prefix -}} {{- with .Prefix -}}
{{ . }} {{ . }}
{{- end -}} {{- end -}}
{{- if .Owner -}}
{{- "owner " -}}
{{- end -}}
{{- if .Audit -}} {{- if .Audit -}}
{{- "audit " -}} {{- "audit " -}}
{{- end -}} {{- end -}}

View file

@ -5,62 +5,64 @@
package aa package aa
type Unix struct { type Unix struct {
Rule
Qualifier Qualifier
Access string Access string
Type string Type string
Protocol string Protocol string
Address string Address string
Label string Label string
Attr string Attr string
Opt string Opt string
Peer string PeerLabel string
PeerAddr string PeerAddr string
} }
func UnixFromLog(log map[string]string) ApparmorRule { func newUnixFromLog(log map[string]string) *Unix {
return &Unix{ return &Unix{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Access: toAccess(log["requested_mask"]), Access: toAccess(log["requested_mask"]),
Type: log["sock_type"], Type: log["sock_type"],
Protocol: log["protocol"], Protocol: log["protocol"],
Address: log["addr"], Address: log["addr"],
Label: log["peer_label"], Label: log["label"],
Attr: log["attr"], Attr: log["attr"],
Opt: log["opt"], Opt: log["opt"],
Peer: log["peer"], PeerLabel: log["peer"],
PeerAddr: log["peer_addr"], PeerAddr: log["peer_addr"],
} }
} }
func (r *Unix) Less(other any) bool { func (r *Unix) Less(other any) bool {
o, _ := other.(*Unix) o, _ := other.(*Unix)
if r.Qualifier.Equals(o.Qualifier) { if r.Access != o.Access {
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
}
return 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) 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 && return r.Access == o.Access && r.Type == o.Type &&
r.Protocol == o.Protocol && r.Address == o.Address && r.Protocol == o.Protocol && r.Address == o.Address &&
r.Label == o.Label && r.Attr == o.Attr && r.Opt == o.Opt && 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)
} }

View file

@ -5,20 +5,22 @@
package aa package aa
type Userns struct { type Userns struct {
Rule
Qualifier Qualifier
Create bool Create bool
} }
func UsernsFromLog(log map[string]string) ApparmorRule { func newUsernsFromLog(log map[string]string) *Userns {
return &Userns{ return &Userns{
Qualifier: NewQualifierFromLog(log), Rule: newRuleFromLog(log),
Qualifier: newQualifierFromLog(log),
Create: true, Create: true,
} }
} }
func (r *Userns) Less(other any) bool { func (r *Userns) Less(other any) bool {
o, _ := other.(*Userns) o, _ := other.(*Userns)
if r.Qualifier.Equals(o.Qualifier) { if r.Create != o.Create {
return r.Create return r.Create
} }
return r.Qualifier.Less(o.Qualifier) return r.Qualifier.Less(o.Qualifier)

View file

@ -9,7 +9,6 @@ package aa
import ( import (
"regexp" "regexp"
"slices"
"strings" "strings"
) )
@ -18,35 +17,19 @@ var (
regVariablesRef = regexp.MustCompile(`@{([^{}]+)}`) 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 // DefaultTunables return a minimal working profile to build the profile
// It should not be used when loading file from /etc/apparmor.d // It should not be used when loading file from /etc/apparmor.d
func DefaultTunables() *AppArmorProfile { func DefaultTunables() *AppArmorProfile {
return &AppArmorProfile{ return &AppArmorProfile{
Preamble: Preamble{ Preamble: Preamble{
Variables: []Variable{ Variables: []*Variable{
{"bin", []string{"/{,usr/}{,s}bin"}}, {Name: "bin", Values: []string{"/{,usr/}{,s}bin"}},
{"lib", []string{"/{,usr/}lib{,exec,32,64}"}}, {Name: "lib", Values: []string{"/{,usr/}lib{,exec,32,64}"}},
{"multiarch", []string{"*-linux-gnu*"}}, {Name: "multiarch", Values: []string{"*-linux-gnu*"}},
{"HOME", []string{"/home/*"}}, {Name: "HOME", Values: []string{"/home/*"}},
{"user_share_dirs", []string{"/home/*/.local/share"}}, {Name: "user_share_dirs", Values: []string{"/home/*/.local/share"}},
{"etc_ro", []string{"/{,usr/}etc/"}}, {Name: "etc_ro", Values: []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],}"}}, {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 { if !found {
variable := Variable{Name: key, Values: values} variable := &Variable{Name: key, Values: values}
p.Variables = append(p.Variables, variable) p.Variables = append(p.Variables, variable)
} }
} }

View file

@ -9,6 +9,10 @@ import (
"testing" "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) { func TestDefaultTunables(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -18,14 +22,14 @@ func TestDefaultTunables(t *testing.T) {
name: "aa", name: "aa",
want: &AppArmorProfile{ want: &AppArmorProfile{
Preamble: Preamble{ Preamble: Preamble{
Variables: []Variable{ Variables: []*Variable{
{"bin", []string{"/{,usr/}{,s}bin"}}, {Name: "bin", Values: []string{"/{,usr/}{,s}bin"}},
{"lib", []string{"/{,usr/}lib{,exec,32,64}"}}, {Name: "lib", Values: []string{"/{,usr/}lib{,exec,32,64}"}},
{"multiarch", []string{"*-linux-gnu*"}}, {Name: "multiarch", Values: []string{"*-linux-gnu*"}},
{"HOME", []string{"/home/*"}}, {Name: "HOME", Values: []string{"/home/*"}},
{"user_share_dirs", []string{"/home/*/.local/share"}}, {Name: "user_share_dirs", Values: []string{"/home/*/.local/share"}},
{"etc_ro", []string{"/{,usr/}etc/"}}, {Name: "etc_ro", Values: []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],}"}}, {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 { tests := []struct {
name string name string
content string content string
want []Variable want []*Variable
}{ }{
{ {
name: "firefox", name: "firefox",
@ -54,12 +58,12 @@ func TestAppArmorProfile_ParseVariables(t *testing.T) {
@{firefox_cache_dirs} = @{user_cache_dirs}/mozilla/ @{firefox_cache_dirs} = @{user_cache_dirs}/mozilla/
@{exec_path} = /{usr/,}bin/@{firefox_name} @{firefox_lib_dirs}/@{firefox_name} @{exec_path} = /{usr/,}bin/@{firefox_name} @{firefox_lib_dirs}/@{firefox_name}
`, `,
want: []Variable{ want: []*Variable{
{"firefox_name", []string{"firefox{,-esr,-bin}"}}, {Name: "firefox_name", Values: []string{"firefox{,-esr,-bin}"}},
{"firefox_lib_dirs", []string{"/{usr/,}lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}}, {Name: "firefox_lib_dirs", Values: []string{"/{usr/,}lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}},
{"firefox_config_dirs", []string{"@{HOME}/.mozilla/"}}, {Name: "firefox_config_dirs", Values: []string{"@{HOME}/.mozilla/"}},
{"firefox_cache_dirs", []string{"@{user_cache_dirs}/mozilla/"}}, {Name: "firefox_cache_dirs", Values: []string{"@{user_cache_dirs}/mozilla/"}},
{"exec_path", []string{"/{usr/,}bin/@{firefox_name}", "@{firefox_lib_dirs}/@{firefox_name}"}}, {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/,}bin/Xorg{,.bin}
@{exec_path} += /{usr/,}lib/Xorg{,.wrap} @{exec_path} += /{usr/,}lib/Xorg{,.wrap}
@{exec_path} += /{usr/,}lib/xorg/Xorg{,.wrap}`, @{exec_path} += /{usr/,}lib/xorg/Xorg{,.wrap}`,
want: []Variable{ want: []*Variable{
{"exec_path", []string{ {Name: "exec_path", Values: []string{
"/{usr/,}bin/X", "/{usr/,}bin/X",
"/{usr/,}bin/Xorg{,.bin}", "/{usr/,}bin/Xorg{,.bin}",
"/{usr/,}lib/Xorg{,.wrap}", "/{usr/,}lib/Xorg{,.wrap}",
@ -81,9 +85,9 @@ func TestAppArmorProfile_ParseVariables(t *testing.T) {
name: "snapd", name: "snapd",
content: `@{lib_dirs} = @{lib}/ /snap/snapd/@{int}@{lib} content: `@{lib_dirs} = @{lib}/ /snap/snapd/@{int}@{lib}
@{exec_path} = @{lib_dirs}/snapd/snapd`, @{exec_path} = @{lib_dirs}/snapd/snapd`,
want: []Variable{ want: []*Variable{
{"lib_dirs", []string{"@{lib}/", "/snap/snapd/@{int}@{lib}"}}, {Name: "lib_dirs", Values: []string{"@{lib}/", "/snap/snapd/@{int}@{lib}"}},
{"exec_path", []string{"@{lib_dirs}/snapd/snapd"}}, {Name: "exec_path", Values: []string{"@{lib_dirs}/snapd/snapd"}},
}, },
}, },
} }
@ -104,11 +108,21 @@ func TestAppArmorProfile_resolve(t *testing.T) {
input string input string
want []string want []string
}{ }{
{
name: "default",
input: "@{etc_ro}",
want: []string{"/{,usr/}etc/"},
},
{ {
name: "empty", name: "empty",
input: "@{}", input: "@{}",
want: []string{"@{}"}, want: []string{"@{}"},
}, },
{
name: "nil",
input: "@{foo}",
want: []string{},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -123,15 +137,15 @@ func TestAppArmorProfile_resolve(t *testing.T) {
func TestAppArmorProfile_ResolveAttachments(t *testing.T) { func TestAppArmorProfile_ResolveAttachments(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
variables []Variable variables []*Variable
want []string want []string
}{ }{
{ {
name: "firefox", name: "firefox",
variables: []Variable{ variables: []*Variable{
{"firefox_name", []string{"firefox{,-esr,-bin}"}}, {Name: "firefox_name", Values: []string{"firefox{,-esr,-bin}"}},
{"firefox_lib_dirs", []string{"/{usr/,}/lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}}, {Name: "firefox_lib_dirs", Values: []string{"/{usr/,}/lib{,32,64}/@{firefox_name}", "/opt/@{firefox_name}"}},
{"exec_path", []string{"/{usr/,}bin/@{firefox_name}", "@{firefox_lib_dirs}/@{firefox_name}"}}, {Name: "exec_path", Values: []string{"/{usr/,}bin/@{firefox_name}", "@{firefox_lib_dirs}/@{firefox_name}"}},
}, },
want: []string{ want: []string{
"/{usr/,}bin/firefox{,-esr,-bin}", "/{usr/,}bin/firefox{,-esr,-bin}",
@ -141,10 +155,10 @@ func TestAppArmorProfile_ResolveAttachments(t *testing.T) {
}, },
{ {
name: "chromium", name: "chromium",
variables: []Variable{ variables: []*Variable{
{"name", []string{"chromium"}}, {Name: "name", Values: []string{"chromium"}},
{"lib_dirs", []string{"/{usr/,}lib/@{name}"}}, {Name: "lib_dirs", Values: []string{"/{usr/,}lib/@{name}"}},
{"exec_path", []string{"@{lib_dirs}/@{name}"}}, {Name: "exec_path", Values: []string{"@{lib_dirs}/@{name}"}},
}, },
want: []string{ want: []string{
"/{usr/,}lib/chromium/chromium", "/{usr/,}lib/chromium/chromium",
@ -152,9 +166,9 @@ func TestAppArmorProfile_ResolveAttachments(t *testing.T) {
}, },
{ {
name: "geoclue", name: "geoclue",
variables: []Variable{ variables: []*Variable{
{"libexec", []string{"/{usr/,}libexec"}}, {Name: "libexec", Values: []string{"/{usr/,}libexec"}},
{"exec_path", []string{"@{libexec}/geoclue", "@{libexec}/geoclue-2.0/demos/agent"}}, {Name: "exec_path", Values: []string{"@{libexec}/geoclue", "@{libexec}/geoclue-2.0/demos/agent"}},
}, },
want: []string{ want: []string{
"/{usr/,}libexec/geoclue", "/{usr/,}libexec/geoclue",
@ -163,11 +177,11 @@ func TestAppArmorProfile_ResolveAttachments(t *testing.T) {
}, },
{ {
name: "opera", name: "opera",
variables: []Variable{ variables: []*Variable{
{"multiarch", []string{"*-linux-gnu*"}}, {Name: "multiarch", Values: []string{"*-linux-gnu*"}},
{"name", []string{"opera{,-beta,-developer}"}}, {Name: "name", Values: []string{"opera{,-beta,-developer}"}},
{"lib_dirs", []string{"/{usr/,}lib/@{multiarch}/@{name}"}}, {Name: "lib_dirs", Values: []string{"/{usr/,}lib/@{multiarch}/@{name}"}},
{"exec_path", []string{"@{lib_dirs}/@{name}"}}, {Name: "exec_path", Values: []string{"@{lib_dirs}/@{name}"}},
}, },
want: []string{ want: []string{
"/{usr/,}lib/*-linux-gnu*/opera{,-beta,-developer}/opera{,-beta,-developer}", "/{usr/,}lib/*-linux-gnu*/opera{,-beta,-developer}/opera{,-beta,-developer}",

View file

@ -303,16 +303,16 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) {
Name: "kmod", Name: "kmod",
Rules: aa.Rules{ Rules: aa.Rules{
&aa.Unix{ &aa.Unix{
Qualifier: aa.Qualifier{FileInherit: true}, Rule: aa.Rule{FileInherit: true},
Access: "send receive", Access: "send receive",
Type: "stream", Type: "stream",
Protocol: "0", Protocol: "0",
}, },
&aa.Unix{ &aa.Unix{
Qualifier: aa.Qualifier{FileInherit: true}, Rule: aa.Rule{FileInherit: true},
Access: "send receive", Access: "send receive",
Type: "stream", Type: "stream",
Protocol: "0", Protocol: "0",
}, },
}, },
}, },
@ -324,11 +324,11 @@ func TestAppArmorLogs_ParseToProfiles(t *testing.T) {
&aa.Dbus{ &aa.Dbus{
Access: "send", Access: "send",
Bus: "system", Bus: "system",
Name: "org.freedesktop.DBus",
Path: "/org/freedesktop/DBus", Path: "/org/freedesktop/DBus",
Interface: "org.freedesktop.DBus", Interface: "org.freedesktop.DBus",
Member: "AddMatch", Member: "AddMatch",
Label: "dbus-daemon", PeerName: "org.freedesktop.DBus",
PeerLabel: "dbus-daemon",
}, },
}, },
}, },

View file

@ -107,7 +107,7 @@ func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile {
Bus: rules["bus"], Bus: rules["bus"],
Path: rules["path"], Path: rules["path"],
Interface: iface, Interface: iface,
Name: `":1.@{int}"`, PeerName: `":1.@{int}"`,
}) })
} }
for _, iface := range interfaces { for _, iface := range interfaces {
@ -116,7 +116,7 @@ func (d Dbus) own(rules map[string]string) *aa.AppArmorProfile {
Bus: rules["bus"], Bus: rules["bus"],
Path: rules["path"], Path: rules["path"],
Interface: iface, Interface: iface,
Name: `"{:1.@{int},org.freedesktop.DBus}"`, PeerName: `"{:1.@{int},org.freedesktop.DBus}"`,
}) })
} }
p.Rules = append(p.Rules, &aa.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"], Path: rules["path"],
Interface: "org.freedesktop.DBus.Introspectable", Interface: "org.freedesktop.DBus.Introspectable",
Member: "Introspect", Member: "Introspect",
Name: `":1.@{int}"`, PeerName: `":1.@{int}"`,
}) })
return p return p
} }
@ -139,8 +139,8 @@ func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfile {
Bus: rules["bus"], Bus: rules["bus"],
Path: rules["path"], Path: rules["path"],
Interface: iface, Interface: iface,
Name: `"{:1.@{int},` + rules["name"] + `}"`, PeerName: `"{:1.@{int},` + rules["name"] + `}"`,
Label: rules["label"], PeerLabel: rules["label"],
}) })
} }
for _, iface := range interfaces { for _, iface := range interfaces {
@ -149,8 +149,8 @@ func (d Dbus) talk(rules map[string]string) *aa.AppArmorProfile {
Bus: rules["bus"], Bus: rules["bus"],
Path: rules["path"], Path: rules["path"],
Interface: iface, Interface: iface,
Name: `"{:1.@{int},` + rules["name"] + `}"`, PeerName: `"{:1.@{int},` + rules["name"] + `}"`,
Label: rules["label"], PeerLabel: rules["label"],
}) })
} }
return p return p