diff --git a/pkg/aa/all.go b/pkg/aa/all.go index 3e29505a..21368d32 100644 --- a/pkg/aa/all.go +++ b/pkg/aa/all.go @@ -39,5 +39,11 @@ func (r *All) Compare(other Rule) int { func (r *All) Merge(other Rule) bool { o, _ := other.(*All) b := &r.Base - return b.merge(o.Base) + return b.merge(o.Base) // Always merge all rules } + +func (r *All) Lengths() []int { + return []int{} // No len for all +} + +func (r *All) setPaddings(max []int) {} // No paddings for all diff --git a/pkg/aa/blocks.go b/pkg/aa/blocks.go index 6aa10c94..901fdaae 100644 --- a/pkg/aa/blocks.go +++ b/pkg/aa/blocks.go @@ -39,3 +39,9 @@ func (r *Hat) Compare(other Rule) int { func (r *Hat) Merge(other Rule) bool { return false // Never merge hat blocks } + +func (r *Hat) Lengths() []int { + return []int{} // No len for hat +} + +func (r *Hat) setPaddings(max []int) {} // No paddings for hat diff --git a/pkg/aa/capability.go b/pkg/aa/capability.go index e438a761..b1ba27c6 100644 --- a/pkg/aa/capability.go +++ b/pkg/aa/capability.go @@ -81,3 +81,17 @@ func (r *Capability) Compare(other Rule) int { func (r *Capability) Merge(other Rule) bool { return false // Never merge capabilities } + +func (r *Capability) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.Names), + } +} + +func (r *Capability) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{""}, []any{r.Names})..., + ) +} diff --git a/pkg/aa/change_profile.go b/pkg/aa/change_profile.go index 15e357d9..76942702 100644 --- a/pkg/aa/change_profile.go +++ b/pkg/aa/change_profile.go @@ -103,3 +103,20 @@ func (r *ChangeProfile) Compare(other Rule) int { func (r *ChangeProfile) Merge(other Rule) bool { return false // Never merge change_profile } + +func (r *ChangeProfile) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.ExecMode), + length("", r.Exec), + length("", r.ProfileName), + } +} + +func (r *ChangeProfile) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"", "", ""}, + []any{r.ExecMode, r.Exec, r.ProfileName})..., + ) +} diff --git a/pkg/aa/dbus.go b/pkg/aa/dbus.go index 23c517f6..79072925 100644 --- a/pkg/aa/dbus.go +++ b/pkg/aa/dbus.go @@ -137,3 +137,9 @@ func (r *Dbus) Merge(other Rule) bool { } return false } + +func (r *Dbus) Lengths() []int { + return []int{} // No len for dbus +} + +func (r *Dbus) setPaddings(max []int) {} // No paddings for dbus diff --git a/pkg/aa/file.go b/pkg/aa/file.go index 31ebf3fd..88b08c08 100644 --- a/pkg/aa/file.go +++ b/pkg/aa/file.go @@ -8,6 +8,8 @@ import ( "fmt" "slices" "strings" + + "github.com/roddhjav/apparmor.d/pkg/util" ) const ( @@ -156,6 +158,42 @@ func (r *File) Merge(other Rule) bool { return false } +func (r *File) Lengths() []int { + // Add padding to align with other transition rule + lenPath := 0 + isTransition := util.Intersect( + append(requirements[FILE]["transition"], "m"), r.Access, + ) + if len(isTransition) > 0 { + lenPath = length("", r.Path) + } + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("owner", r.Owner), + lenPath, + } +} + +func (r *File) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"owner", ""}, + []any{r.Owner, r.Path})..., + ) +} + +func (r *File) addLine(other Rule) bool { + if other.Kind() != r.Kind() { + return false + } + + letterI := getLetterIn(fileAlphabet, r.Path) + letterJ := getLetterIn(fileAlphabet, other.(*File).Path) + groupI, ok1 := fileAlphabetGroups[letterI] + groupJ, ok2 := fileAlphabetGroups[letterJ] + return letterI != letterJ && !(ok1 && ok2 && groupI == groupJ) +} + type Link struct { Base Qualifier diff --git a/pkg/aa/io_uring.go b/pkg/aa/io_uring.go index ceda00c7..3346ed4c 100644 --- a/pkg/aa/io_uring.go +++ b/pkg/aa/io_uring.go @@ -88,3 +88,19 @@ func (r *IOUring) Merge(other Rule) bool { } return false } + +func (r *IOUring) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.Access), + length("label=", r.Label), + } +} + +func (r *IOUring) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"", "label="}, + []any{r.Access, r.Label})..., + ) +} diff --git a/pkg/aa/mount.go b/pkg/aa/mount.go index 822d1193..914efc2f 100644 --- a/pkg/aa/mount.go +++ b/pkg/aa/mount.go @@ -73,6 +73,21 @@ func (m *MountConditions) Merge(other MountConditions) bool { return false } +func (m MountConditions) getLenFsType() int { + return length("fstype=", m.FsType) +} + +func (m MountConditions) getLenOptions() int { + return length("options=", m.Options) +} + +func (m MountConditions) setPaddings(max []int) []string { + return setPaddings(max, + []string{"fstype=", "options="}, + []any{m.FsType, m.Options}, + ) +} + type Mount struct { Base Qualifier @@ -168,6 +183,24 @@ func (r *Mount) Merge(other Rule) bool { return false } +func (r *Mount) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + r.MountConditions.getLenFsType(), + r.MountConditions.getLenOptions(), + length("", r.Source), + length("", r.MountPoint), + } +} + +func (r *Mount) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), r.MountConditions.setPaddings(max[2:4])...) + r.Paddings = append(r.Paddings, + setPaddings(max[4:], []string{"", ""}, []any{r.Source, r.MountPoint})..., + ) +} + type Umount struct { Base Qualifier @@ -246,6 +279,23 @@ func (r *Umount) Merge(other Rule) bool { return false } +func (r *Umount) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + r.MountConditions.getLenFsType(), + r.MountConditions.getLenOptions(), + length("", r.MountPoint), + } +} + +func (r *Umount) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), r.MountConditions.setPaddings(max[2:4])...) + r.Paddings = append(r.Paddings, + setPaddings(max[4:], []string{""}, []any{r.MountPoint})..., + ) +} + type Remount struct { Base Qualifier @@ -324,3 +374,20 @@ func (r *Remount) Merge(other Rule) bool { } return false } + +func (r *Remount) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + r.MountConditions.getLenFsType(), + r.MountConditions.getLenOptions(), + length("", r.MountPoint), + } +} + +func (r *Remount) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), r.MountConditions.setPaddings(max[2:4])...) + r.Paddings = append(r.Paddings, + setPaddings(max[4:], []string{""}, []any{r.MountPoint})..., + ) +} diff --git a/pkg/aa/mqueue.go b/pkg/aa/mqueue.go index 927606c9..82106ec7 100644 --- a/pkg/aa/mqueue.go +++ b/pkg/aa/mqueue.go @@ -122,3 +122,21 @@ func (r *Mqueue) Merge(other Rule) bool { } return false } + +func (r *Mqueue) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.Access), + length("type=", r.Type), + length("label=", r.Label), + length("", r.Name), + } +} + +func (r *Mqueue) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"", "type=", "label=", ""}, + []any{r.Access, r.Type, r.Label, r.Name})..., + ) +} diff --git a/pkg/aa/network.go b/pkg/aa/network.go index aa7d89da..69bd01c8 100644 --- a/pkg/aa/network.go +++ b/pkg/aa/network.go @@ -144,3 +144,20 @@ func (r *Network) Compare(other Rule) int { func (r *Network) Merge(other Rule) bool { return false // Never merge network } + +func (r *Network) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.Domain), + length("", r.Type), + length("", r.Protocol), + } +} + +func (r *Network) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"", "", ""}, + []any{r.Domain, r.Type, r.Protocol})..., + ) +} diff --git a/pkg/aa/pivot_root.go b/pkg/aa/pivot_root.go index 7366be18..2341f445 100644 --- a/pkg/aa/pivot_root.go +++ b/pkg/aa/pivot_root.go @@ -83,3 +83,20 @@ func (r *PivotRoot) Compare(other Rule) int { func (r *PivotRoot) Merge(other Rule) bool { return false // Never merge pivot root } + +func (r *PivotRoot) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("oldroot=", r.OldRoot), + length("", r.NewRoot), + length("", r.TargetProfile), + } +} + +func (r *PivotRoot) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"oldroot=", "", ""}, + []any{r.OldRoot, r.NewRoot, r.TargetProfile})..., + ) +} diff --git a/pkg/aa/preamble.go b/pkg/aa/preamble.go index eeae1a5c..4b54954a 100644 --- a/pkg/aa/preamble.go +++ b/pkg/aa/preamble.go @@ -53,6 +53,12 @@ func (r *Comment) Merge(other Rule) bool { return false // Never merge comments } +func (r *Comment) Lengths() []int { + return []int{} // No len for comments +} + +func (r *Comment) setPaddings(max []int) {} // No paddings for comments + type Abi struct { Base Path string @@ -109,6 +115,12 @@ func (r *Abi) Merge(other Rule) bool { return false // Never merge abi } +func (r *Abi) Lengths() []int { + return []int{} // No len for abi +} + +func (r *Abi) setPaddings(max []int) {} // No paddings for abi + type Alias struct { Base Path string @@ -157,6 +169,12 @@ func (r *Alias) Merge(other Rule) bool { return false // Never merge alias } +func (r *Alias) Lengths() []int { + return []int{} // No len for alias +} + +func (r *Alias) setPaddings(max []int) {} // No paddings for alias + type Include struct { Base IfExists bool @@ -234,6 +252,12 @@ func (r *Include) Merge(other Rule) bool { return false // Never merge include } +func (r *Include) Lengths() []int { + return []int{} // No len for include +} + +func (r *Include) setPaddings(max []int) {} // No paddings for include + type Variable struct { Base Name string @@ -305,3 +329,9 @@ func (r *Variable) Merge(other Rule) bool { } return false } + +func (r *Variable) Lengths() []int { + return []int{} // No len for variable +} + +func (r *Variable) setPaddings(max []int) {} // No paddings for variable diff --git a/pkg/aa/profile.go b/pkg/aa/profile.go index ee359bee..ec506897 100644 --- a/pkg/aa/profile.go +++ b/pkg/aa/profile.go @@ -103,6 +103,12 @@ func (p *Profile) Merge(other Rule) bool { return false } +func (r *Profile) Lengths() []int { + return []int{} // No len for profile +} + +func (r *Profile) setPaddings(max []int) {} // No paddings for profile + func (p *Profile) Sort() { p.Rules = p.Rules.Sort() } diff --git a/pkg/aa/ptrace.go b/pkg/aa/ptrace.go index 8f0dc1c6..91547087 100644 --- a/pkg/aa/ptrace.go +++ b/pkg/aa/ptrace.go @@ -90,3 +90,19 @@ func (r *Ptrace) Merge(other Rule) bool { } return false } + +func (r *Ptrace) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.Access), + length("peer=", r.Peer), + } +} + +func (r *Ptrace) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"", "peer="}, + []any{r.Access, r.Peer})..., + ) +} diff --git a/pkg/aa/rlimit.go b/pkg/aa/rlimit.go index 9ad1dd30..d7b9a051 100644 --- a/pkg/aa/rlimit.go +++ b/pkg/aa/rlimit.go @@ -84,3 +84,18 @@ func (r *Rlimit) Compare(other Rule) int { func (r *Rlimit) Merge(other Rule) bool { return false // Never merge rlimit } + +func (r *Rlimit) Lengths() []int { + return []int{ + length("", r.Key), + length("", r.Op), + length("", r.Value), + } +} + +func (r *Rlimit) setPaddings(max []int) { + r.Paddings = setPaddings( + max, []string{"", "", ""}, + []any{r.Key, r.Op, r.Value}, + ) +} diff --git a/pkg/aa/signal.go b/pkg/aa/signal.go index 62c41f0a..c0fa4e1b 100644 --- a/pkg/aa/signal.go +++ b/pkg/aa/signal.go @@ -121,3 +121,20 @@ func (r *Signal) Merge(other Rule) bool { } return false } + +func (r *Signal) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.Access), + length("set=", r.Set), + length("peer=", r.Peer), + } +} + +func (r *Signal) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"", "set=", "peer="}, + []any{r.Access, r.Set, r.Peer})..., + ) +} diff --git a/pkg/aa/unix.go b/pkg/aa/unix.go index a14c5816..3b14c298 100644 --- a/pkg/aa/unix.go +++ b/pkg/aa/unix.go @@ -136,3 +136,22 @@ func (r *Unix) Merge(other Rule) bool { } return false } + +func (r *Unix) Lengths() []int { + return []int{ + r.Qualifier.getLenAudit(), + r.Qualifier.getLenAccess(), + length("", r.Access), + length("type=", r.Type), + length("protocol=", r.Protocol), + length("addr=", r.Address), + length("label=", r.Label), + } +} + +func (r *Unix) setPaddings(max []int) { + r.Paddings = append(r.Qualifier.setPaddings(max[:2]), setPaddings( + max[2:], []string{"", "type=", "protocol=", "addr=", "label="}, + []any{r.Access, r.Type, r.Protocol, r.Address, r.Label})..., + ) +} diff --git a/pkg/aa/userns.go b/pkg/aa/userns.go index a594bd48..f4a9815c 100644 --- a/pkg/aa/userns.go +++ b/pkg/aa/userns.go @@ -69,5 +69,11 @@ func (r *Userns) Compare(other Rule) int { func (r *Userns) Merge(other Rule) bool { o, _ := other.(*Userns) b := &r.Base - return b.merge(o.Base) + return b.merge(o.Base) // Always merge userns rules } + +func (r *Userns) Lengths() []int { + return []int{} // No len for userns +} + +func (r *Userns) setPaddings(max []int) {} // No paddings for userns diff --git a/pkg/aa/util.go b/pkg/aa/util.go index e0889360..485478fe 100644 --- a/pkg/aa/util.go +++ b/pkg/aa/util.go @@ -43,6 +43,53 @@ func merge(kind Kind, key string, a, b []string) []string { return slices.Compact(a) } +func length(prefix string, value any) int { + var res int + switch value := value.(type) { + case bool: + if value { + return len(prefix) + 1 + } + return 0 + case string: + if value != "" { + res = len(value) + len(prefix) + 1 + } + return res + case []string: + for _, v := range value { + lenV := len(v) + if lenV > 0 { + res += lenV + 1 // Space between values + } + } + if len(value) > 1 { + res += 2 // Brackets on slices + } + return res + default: + panic("length: unsupported type") + } +} + +func setPaddings(max []int, prefixes []string, values []any) []string { + if len(max) != len(values) || len(max) != len(prefixes) { + panic("setPaddings: max, prefix, and values must have the same length") + } + res := make([]string, len(max)) + for i, v := range values { + if max[i] == 0 { + res[i] = "" + continue + } + count := max[i] - length(prefixes[i], v) + if count > 0 { + res[i] = strings.Repeat(" ", count) + } + } + return res +} + func compare(a, b any) int { switch a := a.(type) { case int: