github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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 "runtime" 36 "strings" 37 38 "github.com/snapcore/snapd/osutil" 39 ) 40 41 // ValidateNoAppArmorRegexp will check that the given string does not 42 // contain AppArmor regular expressions (AARE), double quotes or \0. 43 func ValidateNoAppArmorRegexp(s string) error { 44 const AARE = `?*[]{}^"` + "\x00" 45 46 if strings.ContainsAny(s, AARE) { 47 return fmt.Errorf("%q contains a reserved apparmor char from %s", s, AARE) 48 } 49 return nil 50 } 51 52 type aaParserFlags int 53 54 const ( 55 // skipReadCache causes apparmor_parser to be invoked with --skip-read-cache. 56 // This allows us to essentially overwrite a cache that we know is stale regardless 57 // of the time and date settings (apparmor_parser caching is based on mtime). 58 // Note that writing of the cache relies on --write-cache but we pass that 59 // command-line option unconditionally. 60 skipReadCache aaParserFlags = 1 << iota 61 62 // conserveCPU tells apparmor_parser to spare up to two CPUs on multi-core systems to 63 // reduce load when processing many profiles at once. 64 conserveCPU aaParserFlags = 1 << iota 65 66 // skipKernelLoad tells apparmor_parser not to load profiles into the kernel. The use 67 // case of this is when in pre-seeding mode. 68 skipKernelLoad aaParserFlags = 1 << iota 69 ) 70 71 var runtimeNumCPU = runtime.NumCPU 72 73 func maybeSetNumberOfJobs() string { 74 cpus := runtimeNumCPU() 75 // Do not use all CPUs as this may have negative impact when booting. 76 if cpus > 2 { 77 // otherwise spare 2 78 cpus = cpus - 2 79 } else { 80 // Systems with only two CPUs, spare 1. 81 // 82 // When there is a a single CPU, pass -j1 to allow a single 83 // compilation job only. Note, we could pass -j0 in such case 84 // for further improvement, but that has incompatible meaning 85 // between apparmor 2.x (automatic job count, equivalent to 86 // -jauto) and 3.x (compile everything in the main process). 87 cpus = 1 88 } 89 90 return fmt.Sprintf("-j%d", cpus) 91 } 92 93 // loadProfiles loads apparmor profiles from the given files. 94 // 95 // If no such profiles were previously loaded then they are simply added to the kernel. 96 // If there were some profiles with the same name before, those profiles are replaced. 97 func loadProfiles(fnames []string, cacheDir string, flags aaParserFlags) error { 98 if len(fnames) == 0 { 99 return nil 100 } 101 102 // Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858) 103 args := []string{"--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s", cacheDir)} 104 if flags&conserveCPU != 0 { 105 if jobArg := maybeSetNumberOfJobs(); jobArg != "" { 106 args = append(args, jobArg) 107 } 108 } 109 110 if flags&skipKernelLoad != 0 { 111 args = append(args, "--skip-kernel-load") 112 } 113 114 if flags&skipReadCache != 0 { 115 args = append(args, "--skip-read-cache") 116 } 117 if !osutil.GetenvBool("SNAPD_DEBUG") { 118 args = append(args, "--quiet") 119 } 120 args = append(args, fnames...) 121 122 output, err := exec.Command("apparmor_parser", args...).CombinedOutput() 123 if err != nil { 124 return fmt.Errorf("cannot load apparmor profiles: %s\napparmor_parser output:\n%s", err, string(output)) 125 } 126 return nil 127 } 128 129 // unloadProfiles is meant to remove the named profiles from the running 130 // kernel and then remove any cache files. Importantly, we can only unload 131 // profiles when we are sure there are no lingering processes from the snap 132 // (ie, forcibly stop all running processes from the snap). Otherwise, any 133 // running processes will become unconfined. Since we don't have this guarantee 134 // yet, leave the profiles loaded in the kernel but remove the cache files from 135 // the system so the policy is gone on the next reboot. LP: #1818241 136 func unloadProfiles(names []string, cacheDir string) error { 137 if len(names) == 0 { 138 return nil 139 } 140 141 /* TODO: uncomment when no lingering snap processes is guaranteed 142 // By the time this function is called, all the profiles (names) have 143 // been removed from dirs.SnapAppArmorDir, so to unload the profiles 144 // from the running kernel we must instead use sysfs and write the 145 // profile names one at a time to 146 // /sys/kernel/security/apparmor/.remove (with no trailing \n). 147 apparmorSysFsRemove := "/sys/kernel/security/apparmor/.remove" 148 if !osutil.IsWritable(appArmorSysFsRemove) { 149 return fmt.Errorf("cannot unload apparmor profile: %s does not exist\n", appArmorSysFsRemove) 150 } 151 for _, n := range names { 152 // ignore errors since it is ok if the profile isn't removed 153 // from the kernel 154 ioutil.WriteFile(appArmorSysFsRemove, []byte(n), 0666) 155 } 156 */ 157 158 // AppArmor 2.13 and higher has a cache forest while 2.12 and lower has 159 // a flat directory (on 2.12 and earlier, .features and the snap 160 // profiles are in the top-level directory instead of a subdirectory). 161 // With 2.13+, snap profiles are not expected to be in every 162 // subdirectory, so don't error on ENOENT but otherwise if we get an 163 // error, something weird happened so stop processing. 164 if li, err := filepath.Glob(filepath.Join(cacheDir, "*/.features")); err == nil && len(li) > 0 { // 2.13+ 165 for _, p := range li { 166 dir := path.Dir(p) 167 if err := osutil.UnlinkMany(dir, names); err != nil && !os.IsNotExist(err) { 168 return fmt.Errorf("cannot remove apparmor profile cache in %s: %s", dir, err) 169 } 170 } 171 } else if err := osutil.UnlinkMany(cacheDir, names); err != nil && !os.IsNotExist(err) { // 2.12- 172 return fmt.Errorf("cannot remove apparmor profile cache: %s", err) 173 } 174 return nil 175 } 176 177 // profilesPath contains information about the currently loaded apparmor profiles. 178 const realProfilesPath = "/sys/kernel/security/apparmor/profiles" 179 180 var profilesPath = realProfilesPath 181 182 // LoadedProfiles interrogates the kernel and returns a list of loaded apparmor profiles. 183 // 184 // Snappy manages apparmor profiles named "snap.*". Other profiles might exist on 185 // the system (via snappy dimension) and those are filtered-out. 186 func LoadedProfiles() ([]string, error) { 187 file, err := os.Open(profilesPath) 188 if err != nil { 189 return nil, err 190 } 191 defer file.Close() 192 var profiles []string 193 for { 194 var name, mode string 195 n, err := fmt.Fscanf(file, "%s %s\n", &name, &mode) 196 if n > 0 && n != 2 { 197 return nil, fmt.Errorf("syntax error, expected: name (mode)") 198 } 199 if err == io.EOF { 200 break 201 } 202 if err != nil { 203 return nil, err 204 } 205 if strings.HasPrefix(name, "snap.") { 206 profiles = append(profiles, name) 207 } 208 } 209 return profiles, nil 210 }