// 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" "slices" "strings" "github.com/roddhjav/apparmor.d/pkg/prebuild/cfg" "github.com/roddhjav/apparmor.d/pkg/util" ) 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)^(?:[\t ]*(?:\r?\n))+`, ``, // Remove empty lines }) ) type Stack struct { cfg.Base } func init() { RegisterDirective(&Stack{ Base: cfg.Base{ Keyword: "stack", Msg: "Stack directive applied", Help: Keyword + `stack [X] profiles...`, }, }) } func (s Stack) Apply(opt *Option, profile string) (string, error) { if len(opt.ArgList) == 0 { return "", fmt.Errorf("No profile to stack") } t := opt.ArgList[0] if t != "X" { regCleanStakedRules = slices.Insert(regCleanStakedRules, 0, util.ToRegexRepl([]string{ `(?m)^.*(|P|p)(|U|u)(|i)x,.*$`, ``, // Remove X transition rules })..., ) } else { delete(opt.ArgMap, t) } res := "" for name := range opt.ArgMap { stackedProfile := util.MustReadFile(cfg.RootApparmord.Join(name)) m := regRules.FindStringSubmatch(stackedProfile) if len(m) < 2 { return "", fmt.Errorf("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 { return "", fmt.Errorf("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, nil }