// apparmor.d - Full set of apparmor profiles // Copyright (C) 2023-2024 Alexandre Pujol // SPDX-License-Identifier: GPL-2.0-only package prebuild import ( "fmt" "regexp" "strings" "github.com/arduino/go-paths-helper" "github.com/roddhjav/apparmor.d/pkg/aa" "github.com/roddhjav/apparmor.d/pkg/util" ) // Build the profiles with the following directive applied var ( Directives = []DirectiveFunc{ DirectiveDbus, DirectiveStack, } DirectiveMsg = map[string]string{ "DirectiveDbus": "DBus directive applied", "DirectiveStack": "Stack directive applied", } ) var ( regDbus = regexp.MustCompile(`(?m) # dbus: (.*)$`) regStack = regexp.MustCompile(`(?m) # stack: (.*)$`) regRules = regexp.MustCompile(`(?m)^profile.*{$((.|\n)*)}`) regEndOfRules = regexp.MustCompile(`(?m)([\t ]*include if exists <.*>\n)+}`) regCleanStakedRules = util.ToRegexRepl([]string{ `(?m)^.*include .*$`, ``, `(?m)^.*@{exec_path}.*$`, ``, `(?m)^.*(|P|p)(|U|u)(|i)x,.*$`, ``, `(?m)^(?:[\t ]*(?:\r?\n))+`, ``, }) ) type DirectiveFunc func(*paths.Path, string) string // Apply dbus directive // // Example of supported dbus directive: // # dbus: own bus=session name=org.freedesktop.FileManager1 // # dbus: talk name=org.freedesktop.login1 label=systemd-logind func DirectiveDbus(file *paths.Path, profile string) string { var p *aa.AppArmorProfile for _, match := range regDbus.FindAllStringSubmatch(profile, -1) { origin := match[0] rules := map[string]string{} fields := strings.Fields(match[1]) action, fields := fields[0], fields[1:] for _, t := range fields { tmp := strings.Split(t, "=") rules[tmp[0]] = tmp[1] } rules = sanitizeDbusRule(file, action, rules) switch action { case "own": p = dbusOwn(rules) case "talk": p = dbusTalk(rules) default: panic(fmt.Sprintf("Unknown dbus directive: %s in %s", action, file)) } generatedDbus := p.String() lenDbus := len(generatedDbus) generatedDbus = generatedDbus[:lenDbus-1] profile = strings.Replace(profile, origin, generatedDbus, -1) } return profile } func sanitizeDbusRule(file *paths.Path, action string, rules map[string]string) map[string]string { // Sanity check if _, present := rules["name"]; !present { panic(fmt.Sprintf("Missing name for 'dbus: %s' in %s", action, file)) } if _, present := rules["bus"]; !present { panic(fmt.Sprintf("Missing bus for '%s' in %s", rules["name"], file)) } if _, present := rules["label"]; !present && action == "talk" { panic(fmt.Sprintf("Missing label for '%s' in %s", rules["name"], file)) } // Set default values if _, present := rules["path"]; !present { rules["path"] = "/" + strings.Replace(rules["name"], ".", "/", -1) + "{,/**}" } if _, present := rules["interface"]; !present { rules["interface"] = "org.freedesktop.DBus.{Properties,ObjectManager}" } rules["name"] += "{,.*}" return rules } func dbusOwn(rules map[string]string) *aa.AppArmorProfile { interfaces := []string{rules["name"], rules["interface"]} p := &aa.AppArmorProfile{} p.Rules = append(p.Rules, &aa.Dbus{ Access: "bind", Bus: rules["bus"], Name: rules["name"], }) for _, iface := range interfaces { p.Rules = append(p.Rules, &aa.Dbus{ Access: "receive", Bus: rules["bus"], Path: rules["path"], Interface: iface, Name: `":1.@{int}"`, }) } for _, iface := range interfaces { p.Rules = append(p.Rules, &aa.Dbus{ Access: "send", Bus: rules["bus"], Path: rules["path"], Interface: iface, Name: `"{:1.@{int},org.freedesktop.DBus}"`, }) } p.Rules = append(p.Rules, &aa.Dbus{ Access: "receive", Bus: rules["bus"], Path: rules["path"], Interface: "org.freedesktop.DBus.Introspectable", Member: "Introspect", Name: `":1.@{int}"`, }) return p } func dbusTalk(rules map[string]string) *aa.AppArmorProfile { interfaces := []string{rules["name"], rules["interface"]} p := &aa.AppArmorProfile{} for _, iface := range interfaces { p.Rules = append(p.Rules, &aa.Dbus{ Access: "send", Bus: rules["bus"], Path: rules["path"], Interface: iface, Name: `"{:1.@{int},` + rules["name"] + `}"`, Label: rules["label"], }) } for _, iface := range interfaces { p.Rules = append(p.Rules, &aa.Dbus{ Access: "receive", Bus: rules["bus"], Path: rules["path"], Interface: iface, Name: `"{:1.@{int},` + rules["name"] + `}"`, Label: rules["label"], }) } return p } // Apply stack directive // // The stacked profiles are appended at the end of the current profile. // Arguments: list of profiles to stack on the current profile // Example of supported dbus directive: // # stack: pipewire pipewire-media-session func DirectiveStack(file *paths.Path, profile string) string { for _, match := range regStack.FindAllStringSubmatch(profile, -1) { res := "" origin := match[0] profilesToStack := strings.Fields(match[1]) for _, name := range profilesToStack { 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", file)) } profile = strings.Replace(profile, m[0], res+m[0], -1) profile = strings.Replace(profile, origin, "", -1) } return profile }