diff --git a/pkg/prebuild/directive/stack.go b/pkg/prebuild/directive/stack.go new file mode 100644 index 00000000..9c79fc80 --- /dev/null +++ b/pkg/prebuild/directive/stack.go @@ -0,0 +1,68 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package directive + +import ( + "fmt" + "regexp" + "strings" + + "github.com/arduino/go-paths-helper" + "github.com/roddhjav/apparmor.d/pkg/util" +) + +var rootApparmord = paths.New(".build/apparmor.d") + +var ( + regRules = regexp.MustCompile(`(?m)^profile.*{$((.|\n)*)}`) + regEndOfRules = regexp.MustCompile(`(?m)([\t ]*include if exists <.*>\n)+}`) + regCleanStakedRules = util.ToRegexRepl([]string{ + `(?m)^.*include .*$`, ``, // Remove mandatory base abstraction + `(?m)^.*@{exec_path}.*$`, ``, // Remove entry point + `(?m)^.*(|P|p)(|U|u)(|i)x,.*$`, ``, // Remove transition rules + `(?m)^(?:[\t ]*(?:\r?\n))+`, ``, // Remove empty lines + }) +) + +type Stack struct { + DirectiveBase +} + +func init() { + Directives["stack"] = &Stack{ + DirectiveBase: DirectiveBase{ + message: "Stack directive applied", + usage: `#aa:stack profiles_name...`, + }, + } +} + +func (s Stack) Apply(opt *Option, profile string) string { + res := "" + for name := range opt.Args { + tmp, err := rootApparmord.Join(name).ReadFile() + if err != nil { + panic(err) + } + stackedProfile := string(tmp) + + m := regRules.FindStringSubmatch(stackedProfile) + if len(m) < 2 { + panic(fmt.Sprintf("No profile found in %s", name)) + } + stackedRules := m[1] + stackedRules = regCleanStakedRules.Replace(stackedRules) + res += " # Stacked profile: " + name + "\n" + stackedRules + "\n" + } + + // Insert the stacked profile at the end of the current profile, remove the stack directive + m := regEndOfRules.FindStringSubmatch(profile) + if len(m) <= 1 { + panic(fmt.Sprintf("No end of rules found in %s", opt.File)) + } + profile = strings.Replace(profile, m[0], res+m[0], -1) + profile = strings.Replace(profile, opt.Raw, "", -1) + return profile +} diff --git a/pkg/prebuild/directive/stack_test.go b/pkg/prebuild/directive/stack_test.go new file mode 100644 index 00000000..6b6880ae --- /dev/null +++ b/pkg/prebuild/directive/stack_test.go @@ -0,0 +1,74 @@ +// apparmor.d - Full set of apparmor profiles +// Copyright (C) 2021-2024 Alexandre Pujol +// SPDX-License-Identifier: GPL-2.0-only + +package directive + +import ( + "testing" + + "github.com/arduino/go-paths-helper" +) + +func TestStack_Apply(t *testing.T) { + tests := []struct { + name string + rootApparmord *paths.Path + opt *Option + profile string + want string + }{ + { + name: "stack", + rootApparmord: paths.New("../../../apparmor.d/groups/freedesktop/"), + opt: &Option{ + Name: "stack", + Args: map[string]string{"plymouth": ""}, + File: nil, + Raw: " #aa:stack plymouth", + }, + profile: ` +profile parent @{exec_path} { + include + + @{exec_path} mr, + + #aa:stack plymouth + @{bin}/plymouth rPx -> parent//&plymouth, + + @{PROC}/cmdline r, + + include if exists +}`, + want: ` +profile parent @{exec_path} { + include + + @{exec_path} mr, + + + @{bin}/plymouth rPx -> parent//&plymouth, + + @{PROC}/cmdline r, + + # Stacked profile: plymouth + include + include + include + unix (send, receive, connect) type=stream peer=(addr="@/org/freedesktop/plymouthd"), + @{PROC}/cmdline r, + include if exists + + include if exists +}`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rootApparmord = tt.rootApparmord + if got := Directives["stack"].Apply(tt.opt, tt.profile); got != tt.want { + t.Errorf("Stack.Apply() = %v, want %v", got, tt.want) + } + }) + } +}