github.com/rigado/snapd@v2.42.5-go-mod+incompatible/interfaces/apparmor/apparmor.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 // Package apparmor contains primitives for working with apparmor. 21 // 22 // References: 23 // - http://wiki.apparmor.net/index.php/Kernel_interfaces 24 // - http://apparmor.wiki.kernel.org/ 25 // - http://manpages.ubuntu.com/manpages/xenial/en/man7/apparmor.7.html 26 package apparmor 27 28 import ( 29 "fmt" 30 "io" 31 "os" 32 "os/exec" 33 "path" 34 "path/filepath" 35 "strings" 36 37 "github.com/snapcore/snapd/osutil" 38 ) 39 40 // ValidateNoAppArmorRegexp will check that the given string does not 41 // contain AppArmor regular expressions (AARE), double quotes or \0. 42 func ValidateNoAppArmorRegexp(s string) error { 43 const AARE = `?*[]{}^"` + "\x00" 44 45 if strings.ContainsAny(s, AARE) { 46 return fmt.Errorf("%q contains a reserved apparmor char from %s", s, AARE) 47 } 48 return nil 49 } 50 51 type aaParserFlags int 52 53 const ( 54 // skipReadCache causes apparmor_parser to be invoked with --skip-read-cache. 55 // This allows us to essentially overwrite a cache that we know is stale regardless 56 // of the time and date settings (apparmor_parser caching is based on mtime). 57 // Note that writing of the cache relies on --write-cache but we pass that 58 // command-line option unconditionally. 59 skipReadCache aaParserFlags = 1 << iota 60 ) 61 62 // loadProfiles loads apparmor profiles from the given files. 63 // 64 // If no such profiles were previously loaded then they are simply added to the kernel. 65 // If there were some profiles with the same name before, those profiles are replaced. 66 func loadProfiles(fnames []string, cacheDir string, flags aaParserFlags) error { 67 if len(fnames) == 0 { 68 return nil 69 } 70 71 // Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858) 72 args := []string{"--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s", cacheDir)} 73 if flags&skipReadCache != 0 { 74 args = append(args, "--skip-read-cache") 75 } 76 if !osutil.GetenvBool("SNAPD_DEBUG") { 77 args = append(args, "--quiet") 78 } 79 args = append(args, fnames...) 80 81 output, err := exec.Command("apparmor_parser", args...).CombinedOutput() 82 if err != nil { 83 return fmt.Errorf("cannot load apparmor profiles: %s\napparmor_parser output:\n%s", err, string(output)) 84 } 85 return nil 86 } 87 88 // unloadProfiles is meant to remove the named profiles from the running 89 // kernel and then remove any cache files. Importantly, we can only unload 90 // profiles when we are sure there are no lingering processes from the snap 91 // (ie, forcibly stop all running processes from the snap). Otherwise, any 92 // running processes will become unconfined. Since we don't have this guarantee 93 // yet, leave the profiles loaded in the kernel but remove the cache files from 94 // the system so the policy is gone on the next reboot. LP: #1818241 95 func unloadProfiles(names []string, cacheDir string) error { 96 if len(names) == 0 { 97 return nil 98 } 99 100 /* TODO: uncomment when no lingering snap processes is guaranteed 101 // By the time this function is called, all the profiles (names) have 102 // been removed from dirs.SnapAppArmorDir, so to unload the profiles 103 // from the running kernel we must instead use sysfs and write the 104 // profile names one at a time to 105 // /sys/kernel/security/apparmor/.remove (with no trailing \n). 106 apparmorSysFsRemove := "/sys/kernel/security/apparmor/.remove" 107 if !osutil.IsWritable(appArmorSysFsRemove) { 108 return fmt.Errorf("cannot unload apparmor profile: %s does not exist\n", appArmorSysFsRemove) 109 } 110 for _, n := range names { 111 // ignore errors since it is ok if the profile isn't removed 112 // from the kernel 113 ioutil.WriteFile(appArmorSysFsRemove, []byte(n), 0666) 114 } 115 */ 116 117 // AppArmor 2.13 and higher has a cache forest while 2.12 and lower has 118 // a flat directory (on 2.12 and earlier, .features and the snap 119 // profiles are in the top-level directory instead of a subdirectory). 120 // With 2.13+, snap profiles are not expected to be in every 121 // subdirectory, so don't error on ENOENT but otherwise if we get an 122 // error, something weird happened so stop processing. 123 if li, err := filepath.Glob(filepath.Join(cacheDir, "*/.features")); err == nil && len(li) > 0 { // 2.13+ 124 for _, p := range li { 125 dir := path.Dir(p) 126 if err := osutil.UnlinkMany(dir, names); err != nil && !os.IsNotExist(err) { 127 return fmt.Errorf("cannot remove apparmor profile cache in %s: %s", dir, err) 128 } 129 } 130 } else if err := osutil.UnlinkMany(cacheDir, names); err != nil && !os.IsNotExist(err) { // 2.12- 131 return fmt.Errorf("cannot remove apparmor profile cache: %s", err) 132 } 133 return nil 134 } 135 136 // profilesPath contains information about the currently loaded apparmor profiles. 137 const realProfilesPath = "/sys/kernel/security/apparmor/profiles" 138 139 var profilesPath = realProfilesPath 140 141 // LoadedProfiles interrogates the kernel and returns a list of loaded apparmor profiles. 142 // 143 // Snappy manages apparmor profiles named "snap.*". Other profiles might exist on 144 // the system (via snappy dimension) and those are filtered-out. 145 func LoadedProfiles() ([]string, error) { 146 file, err := os.Open(profilesPath) 147 if err != nil { 148 return nil, err 149 } 150 defer file.Close() 151 var profiles []string 152 for { 153 var name, mode string 154 n, err := fmt.Fscanf(file, "%s %s\n", &name, &mode) 155 if n > 0 && n != 2 { 156 return nil, fmt.Errorf("syntax error, expected: name (mode)") 157 } 158 if err == io.EOF { 159 break 160 } 161 if err != nil { 162 return nil, err 163 } 164 if strings.HasPrefix(name, "snap.") { 165 profiles = append(profiles, name) 166 } 167 } 168 return profiles, nil 169 }