gitee.com/mysnapcore/mysnapd@v0.1.0/sandbox/apparmor/profile.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2022 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 21 22 import ( 23 "fmt" 24 "io" 25 "os" 26 "os/exec" 27 "path" 28 "path/filepath" 29 "runtime" 30 "strings" 31 32 "gitee.com/mysnapcore/mysnapd/dirs" 33 "gitee.com/mysnapcore/mysnapd/osutil" 34 ) 35 36 type AaParserFlags int 37 38 const ( 39 // SkipReadCache causes apparmor_parser to be invoked with --skip-read-cache. 40 // This allows us to essentially overwrite a cache that we know is stale regardless 41 // of the time and date settings (apparmor_parser caching is based on mtime). 42 // Note that writing of the cache relies on --write-cache but we pass that 43 // command-line option unconditionally. 44 SkipReadCache AaParserFlags = 1 << iota 45 46 // ConserveCPU tells apparmor_parser to spare up to two CPUs on multi-core systems to 47 // reduce load when processing many profiles at once. 48 ConserveCPU AaParserFlags = 1 << iota 49 50 // SkipKernelLoad tells apparmor_parser not to load profiles into the kernel. The use 51 // case of this is when in pre-seeding mode. 52 SkipKernelLoad AaParserFlags = 1 << iota 53 ) 54 55 var runtimeNumCPU = runtime.NumCPU 56 57 func numberOfJobsParam() string { 58 cpus := runtimeNumCPU() 59 // Do not use all CPUs as this may have negative impact when booting. 60 if cpus > 2 { 61 // otherwise spare 2 62 cpus = cpus - 2 63 } else { 64 // Systems with only two CPUs, spare 1. 65 // 66 // When there is a a single CPU, pass -j1 to allow a single 67 // compilation job only. Note, we could pass -j0 in such case 68 // for further improvement, but that has incompatible meaning 69 // between apparmor 2.x (automatic job count, equivalent to 70 // -jauto) and 3.x (compile everything in the main process). 71 cpus = 1 72 } 73 74 return fmt.Sprintf("-j%d", cpus) 75 } 76 77 // LoadProfiles loads apparmor profiles from the given files. 78 // 79 // If no such profiles were previously loaded then they are simply added to the kernel. 80 // If there were some profiles with the same name before, those profiles are replaced. 81 var LoadProfiles = func(fnames []string, cacheDir string, flags AaParserFlags) error { 82 if len(fnames) == 0 { 83 return nil 84 } 85 86 // Use no-expr-simplify since expr-simplify is actually slower on armhf (LP: #1383858) 87 args := []string{"--replace", "--write-cache", "-O", "no-expr-simplify", fmt.Sprintf("--cache-loc=%s", cacheDir)} 88 if flags&ConserveCPU != 0 { 89 args = append(args, numberOfJobsParam()) 90 } 91 92 if flags&SkipKernelLoad != 0 { 93 args = append(args, "--skip-kernel-load") 94 } 95 96 if flags&SkipReadCache != 0 { 97 args = append(args, "--skip-read-cache") 98 } 99 if !osutil.GetenvBool("SNAPD_DEBUG") { 100 args = append(args, "--quiet") 101 } 102 args = append(args, fnames...) 103 104 output, err := exec.Command("apparmor_parser", args...).CombinedOutput() 105 if err != nil { 106 return fmt.Errorf("cannot load apparmor profiles: %s\napparmor_parser output:\n%s", err, string(output)) 107 } 108 return nil 109 } 110 111 // UnloadProfiles is meant to remove the named profiles from the running 112 // kernel and then remove any cache files. Importantly, we can only unload 113 // profiles when we are sure there are no lingering processes from the snap 114 // (ie, forcibly stop all running processes from the snap). Otherwise, any 115 // running processes will become unconfined. Since we don't have this guarantee 116 // yet, leave the profiles loaded in the kernel but remove the cache files from 117 // the system so the policy is gone on the next reboot. LP: #1818241 118 func UnloadProfiles(names []string, cacheDir string) error { 119 if len(names) == 0 { 120 return nil 121 } 122 123 /* TODO: uncomment when no lingering snap processes is guaranteed 124 // By the time this function is called, all the profiles (names) have 125 // been removed from dirs.SnapAppArmorDir, so to unload the profiles 126 // from the running kernel we must instead use sysfs and write the 127 // profile names one at a time to 128 // /sys/kernel/security/apparmor/.remove (with no trailing \n). 129 apparmorSysFsRemove := "/sys/kernel/security/apparmor/.remove" 130 if !osutil.IsWritable(appArmorSysFsRemove) { 131 return fmt.Errorf("cannot unload apparmor profile: %s does not exist\n", appArmorSysFsRemove) 132 } 133 for _, n := range names { 134 // ignore errors since it is ok if the profile isn't removed 135 // from the kernel 136 ioutil.WriteFile(appArmorSysFsRemove, []byte(n), 0666) 137 } 138 */ 139 140 // AppArmor 2.13 and higher has a cache forest while 2.12 and lower has 141 // a flat directory (on 2.12 and earlier, .features and the snap 142 // profiles are in the top-level directory instead of a subdirectory). 143 // With 2.13+, snap profiles are not expected to be in every 144 // subdirectory, so don't error on ENOENT but otherwise if we get an 145 // error, something weird happened so stop processing. 146 if li, err := filepath.Glob(filepath.Join(cacheDir, "*/.features")); err == nil && len(li) > 0 { // 2.13+ 147 for _, p := range li { 148 dir := path.Dir(p) 149 if err := osutil.UnlinkMany(dir, names); err != nil && !os.IsNotExist(err) { 150 return fmt.Errorf("cannot remove apparmor profile cache in %s: %s", dir, err) 151 } 152 } 153 } else if err := osutil.UnlinkMany(cacheDir, names); err != nil && !os.IsNotExist(err) { // 2.12- 154 return fmt.Errorf("cannot remove apparmor profile cache: %s", err) 155 } 156 return nil 157 } 158 159 // ReloadAllSnapProfiles reload the AppArmor profiles of all installed snaps, 160 // as well as that of snap-confine. 161 // This method is meant to be called when some rules have been changed in 162 // AppArmor include files (like in the tunable files for HOMEDIRS or other 163 // variables) which are bound to affect most snaps. 164 func ReloadAllSnapProfiles() error { 165 profiles, err := filepath.Glob(filepath.Join(dirs.SnapAppArmorDir, "*")) 166 if err != nil { 167 // This only happens if the pattern is malformed 168 return err 169 } 170 171 // We also need to reload the profile of snap-confine; it could come from 172 // the core snap, in which case the glob above will already include it, or 173 // from the distribution package, in which case it's under 174 // /etc/apparmor.d/. 175 if snapConfineProfile := SnapConfineDistroProfilePath(); snapConfineProfile != "" { 176 profiles = append(profiles, snapConfineProfile) 177 } 178 179 // We want to reload the profiles no matter what, so don't even bother 180 // checking if the cached profile is newer 181 aaFlags := SkipReadCache 182 if err := LoadProfiles(profiles, SystemCacheDir, aaFlags); err != nil { 183 return err 184 } 185 186 return nil 187 } 188 189 // profilesPath contains information about the currently loaded apparmor profiles. 190 const realProfilesPath = "/sys/kernel/security/apparmor/profiles" 191 192 var profilesPath = realProfilesPath 193 194 // LoadedProfiles interrogates the kernel and returns a list of loaded apparmor profiles. 195 // 196 // Snappy manages apparmor profiles named "snap.*". Other profiles might exist on 197 // the system (via snappy dimension) and those are filtered-out. 198 func LoadedProfiles() ([]string, error) { 199 file, err := os.Open(profilesPath) 200 if err != nil { 201 return nil, err 202 } 203 defer file.Close() 204 var profiles []string 205 for { 206 var name, mode string 207 n, err := fmt.Fscanf(file, "%s %s\n", &name, &mode) 208 if n > 0 && n != 2 { 209 return nil, fmt.Errorf("syntax error, expected: name (mode)") 210 } 211 if err == io.EOF { 212 break 213 } 214 if err != nil { 215 return nil, err 216 } 217 if strings.HasPrefix(name, "snap.") { 218 profiles = append(profiles, name) 219 } 220 } 221 return profiles, nil 222 } 223 224 // SnapConfineDistroProfilePath returns the path to the AppArmor profile of the 225 // snap-confine binary shipped by the distribution package. 226 // If such a profile is not found (for instance, because we are running Ubuntu 227 // Core) return an empty string 228 var SnapConfineDistroProfilePath = func() string { 229 // For historical reasons we may have a filename that ends with .real or 230 // not. If we do then we prefer the file ending with the name .real as 231 // that is the more recent name we use. 232 for _, profileName := range []string{ 233 "usr.lib.snapd.snap-confine.real", 234 "usr.lib.snapd.snap-confine", 235 "usr.libexec.snapd.snap-confine", 236 } { 237 maybeProfilePath := filepath.Join(ConfDir, profileName) 238 if osutil.FileExists(maybeProfilePath) { 239 return maybeProfilePath 240 } 241 } 242 243 return "" 244 }