github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/interfaces/apparmor/backend.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2020 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 implements integration between snappy and 21 // ubuntu-core-launcher around apparmor. 22 // 23 // Snappy creates apparmor profiles for each application (for each snap) 24 // present in the system. Upon each execution of ubuntu-core-launcher 25 // application process is launched under the profile. Prior to that the profile 26 // must be parsed, compiled and loaded into the kernel using the support tool 27 // "apparmor_parser". 28 // 29 // Each apparmor profile contains a simple <header><content><footer> structure. 30 // The header specifies the profile name that the launcher will use to launch a 31 // process under this profile. Snappy uses "abstract identifiers" as profile 32 // names. 33 // 34 // The actual profiles are stored in /var/lib/snappy/apparmor/profiles. 35 // 36 // NOTE: A systemd job (apparmor.service) loads all snappy-specific apparmor 37 // profiles into the kernel during the boot process. 38 package apparmor 39 40 import ( 41 "bytes" 42 "fmt" 43 "io/ioutil" 44 "os" 45 "path" 46 "path/filepath" 47 "regexp" 48 "sort" 49 "strings" 50 51 "github.com/snapcore/snapd/dirs" 52 "github.com/snapcore/snapd/interfaces" 53 "github.com/snapcore/snapd/logger" 54 "github.com/snapcore/snapd/osutil" 55 "github.com/snapcore/snapd/release" 56 apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor" 57 "github.com/snapcore/snapd/snap" 58 "github.com/snapcore/snapd/timings" 59 ) 60 61 var ( 62 procSelfExe = "/proc/self/exe" 63 isHomeUsingNFS = osutil.IsHomeUsingNFS 64 isRootWritableOverlay = osutil.IsRootWritableOverlay 65 kernelFeatures = apparmor_sandbox.KernelFeatures 66 parserFeatures = apparmor_sandbox.ParserFeatures 67 68 // make sure that apparmor profile fulfills the late discarding backend 69 // interface 70 _ interfaces.SecurityBackendDiscardingLate = (*Backend)(nil) 71 ) 72 73 // Backend is responsible for maintaining apparmor profiles for snaps and parts of snapd. 74 type Backend struct { 75 preseed bool 76 } 77 78 // Name returns the name of the backend. 79 func (b *Backend) Name() interfaces.SecuritySystem { 80 return interfaces.SecurityAppArmor 81 } 82 83 // Initialize prepares customized apparmor policy for snap-confine. 84 func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error { 85 if opts != nil && opts.Preseed { 86 b.preseed = true 87 } 88 // NOTE: It would be nice if we could also generate the profile for 89 // snap-confine executing from the core snap, right here, and not have to 90 // do this in the Setup function below. I sadly don't think this is 91 // possible because snapd must be able to install a new core and only at 92 // that moment generate it. 93 94 // Inspect the system and sets up local apparmor policy for snap-confine. 95 // Local policy is included by the system-wide policy. If the local policy 96 // has changed then the apparmor profile for snap-confine is reloaded. 97 98 // Create the local policy directory if it is not there. 99 if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil { 100 return fmt.Errorf("cannot create snap-confine policy directory: %s", err) 101 } 102 103 // Check the /proc/self/exe symlink, this is needed below but we want to 104 // fail early if this fails for whatever reason. 105 exe, err := os.Readlink(procSelfExe) 106 if err != nil { 107 return fmt.Errorf("cannot read %s: %s", procSelfExe, err) 108 } 109 110 // Location of the generated policy. 111 glob := "*" 112 policy := make(map[string]osutil.FileState) 113 114 // Check if NFS is mounted at or under $HOME. Because NFS is not 115 // transparent to apparmor we must alter our profile to counter that and 116 // allow snap-confine to work. 117 if nfs, err := isHomeUsingNFS(); err != nil { 118 logger.Noticef("cannot determine if NFS is in use: %v", err) 119 } else if nfs { 120 policy["nfs-support"] = &osutil.MemoryFileState{ 121 Content: []byte(nfsSnippet), 122 Mode: 0644, 123 } 124 logger.Noticef("snapd enabled NFS support, additional implicit network permissions granted") 125 } 126 127 // Check if '/' is on overlayfs. If so, add the necessary rules for 128 // upperdir and allow snap-confine to work. 129 if overlayRoot, err := isRootWritableOverlay(); err != nil { 130 logger.Noticef("cannot determine if root filesystem on overlay: %v", err) 131 } else if overlayRoot != "" { 132 snippet := strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1) 133 policy["overlay-root"] = &osutil.MemoryFileState{ 134 Content: []byte(snippet), 135 Mode: 0644, 136 } 137 logger.Noticef("snapd enabled root filesystem on overlay support, additional upperdir permissions granted") 138 } 139 140 // Ensure that generated policy is what we computed above. 141 created, removed, err := osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, policy) 142 if err != nil { 143 return fmt.Errorf("cannot synchronize snap-confine policy: %s", err) 144 } 145 if len(created) == 0 && len(removed) == 0 { 146 // If the generated policy didn't change, we're all done. 147 return nil 148 } 149 150 // If snapd is executing from the core snap the it means it has 151 // re-executed. In that case we are no longer using the copy of 152 // snap-confine from the host distribution but our own copy. We don't have 153 // to re-compile and load the updated profile as that is performed by 154 // setupSnapConfineReexec below. 155 if strings.HasPrefix(exe, dirs.SnapMountDir) { 156 return nil 157 } 158 159 // Reload the apparmor profile of snap-confine. This points to the main 160 // file in /etc/apparmor.d/ as that file contains include statements that 161 // load any of the files placed in /var/lib/snapd/apparmor/snap-confine/. 162 // For historical reasons we may have a filename that ends with .real or 163 // not. If we do then we prefer the file ending with the name .real as 164 // that is the more recent name we use. 165 var profilePath string 166 for _, profileFname := range []string{"usr.lib.snapd.snap-confine.real", "usr.lib.snapd.snap-confine"} { 167 profilePath = filepath.Join(apparmor_sandbox.ConfDir, profileFname) 168 if _, err := os.Stat(profilePath); err != nil { 169 if os.IsNotExist(err) { 170 continue 171 } 172 return err 173 } 174 break 175 } 176 if profilePath == "" { 177 return fmt.Errorf("cannot find system apparmor profile for snap-confine") 178 } 179 180 aaFlags := skipReadCache 181 if b.preseed { 182 aaFlags |= skipKernelLoad 183 } 184 185 // We are not using apparmor.LoadProfiles() because it uses other cache. 186 if err := loadProfiles([]string{profilePath}, apparmor_sandbox.SystemCacheDir, aaFlags); err != nil { 187 // When we cannot reload the profile then let's remove the generated 188 // policy. Maybe we have caused the problem so it's better to let other 189 // things work. 190 osutil.EnsureDirState(dirs.SnapConfineAppArmorDir, glob, nil) 191 return fmt.Errorf("cannot reload snap-confine apparmor profile: %v", err) 192 } 193 return nil 194 } 195 196 // snapConfineFromSnapProfile returns the apparmor profile for 197 // snap-confine in the given core/snapd snap. 198 func snapConfineFromSnapProfile(info *snap.Info) (dir, glob string, content map[string]osutil.FileState, err error) { 199 // Find the vanilla apparmor profile for snap-confine as present in the given core snap. 200 201 // We must test the ".real" suffix first, this is a workaround for 202 // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=858004 203 vanillaProfilePath := filepath.Join(info.MountDir(), "/etc/apparmor.d/usr.lib.snapd.snap-confine.real") 204 vanillaProfileText, err := ioutil.ReadFile(vanillaProfilePath) 205 if os.IsNotExist(err) { 206 vanillaProfilePath = filepath.Join(info.MountDir(), "/etc/apparmor.d/usr.lib.snapd.snap-confine") 207 vanillaProfileText, err = ioutil.ReadFile(vanillaProfilePath) 208 } 209 if err != nil { 210 return "", "", nil, fmt.Errorf("cannot open apparmor profile for vanilla snap-confine: %s", err) 211 } 212 213 // Replace the path to vanilla snap-confine with the path to the mounted snap-confine from core. 214 snapConfineInCore := filepath.Join(info.MountDir(), "usr/lib/snapd/snap-confine") 215 patchedProfileText := bytes.Replace( 216 vanillaProfileText, []byte("/usr/lib/snapd/snap-confine"), []byte(snapConfineInCore), -1) 217 218 // We need to add a uniqe prefix that can never collide with a 219 // snap on the system. Using "snap-confine.*" is similar to 220 // "snap-update-ns.*" that is already used there 221 // 222 // So 223 // /snap/core/111/usr/lib/snapd/snap-confine 224 // becomes 225 // snap-confine.core.111 226 patchedProfileName := snapConfineProfileName(info.InstanceName(), info.Revision) 227 // remove other generated profiles, which is only relevant for the 228 // 'core' snap on classic system where we reexec, on core systems the 229 // profile is already a part of the rootfs snap 230 patchedProfileGlob := fmt.Sprintf("snap-confine.%s.*", info.InstanceName()) 231 232 if info.Type() == snap.TypeSnapd { 233 // with the snapd snap, things are a little different, the 234 // profile is discarded only late for the revisions that are 235 // being removed, also on core devices the rootfs snap and the 236 // snapd snap are updated separately, so the profile needs to be 237 // around for as long as the given revision of the snapd snap is 238 // active, so we use the exact match such that we only replace 239 // our own profile, which can happen if system was rebooted 240 // before task calling the backend was finished 241 patchedProfileGlob = patchedProfileName 242 } 243 244 // Return information for EnsureDirState that describes the re-exec profile for snap-confine. 245 content = map[string]osutil.FileState{ 246 patchedProfileName: &osutil.MemoryFileState{ 247 Content: []byte(patchedProfileText), 248 Mode: 0644, 249 }, 250 } 251 252 return dirs.SnapAppArmorDir, patchedProfileGlob, content, nil 253 } 254 255 func snapConfineProfileName(snapName string, rev snap.Revision) string { 256 return fmt.Sprintf("snap-confine.%s.%s", snapName, rev) 257 } 258 259 // setupSnapConfineReexec will setup apparmor profiles inside the host's 260 // /var/lib/snapd/apparmor/profiles directory. This is needed for 261 // running snap-confine from the core or snapd snap. 262 // 263 // Additionally it will cleanup stale apparmor profiles it created. 264 func (b *Backend) setupSnapConfineReexec(info *snap.Info) error { 265 if err := os.MkdirAll(dirs.SnapConfineAppArmorDir, 0755); err != nil { 266 return fmt.Errorf("cannot create snap-confine policy directory: %s", err) 267 } 268 dir, glob, content, err := snapConfineFromSnapProfile(info) 269 cache := apparmor_sandbox.CacheDir 270 if err != nil { 271 return fmt.Errorf("cannot compute snap-confine profile: %s", err) 272 } 273 if err := os.MkdirAll(dir, 0755); err != nil { 274 return fmt.Errorf("cannot create snap-confine directory %q: %s", dir, err) 275 } 276 277 changed, removed, errEnsure := osutil.EnsureDirState(dir, glob, content) 278 if len(changed) == 0 { 279 // XXX: because NFS workaround is handled separately the same correct 280 // snap-confine profile may need to be re-loaded. This is because the 281 // profile contains include directives and those load a second file 282 // that has changed outside of the scope of EnsureDirState. 283 // 284 // To counter that, always reload the profile by pretending it had 285 // changed. 286 for fname := range content { 287 changed = append(changed, fname) 288 } 289 } 290 pathnames := make([]string, len(changed)) 291 for i, profile := range changed { 292 pathnames[i] = filepath.Join(dir, profile) 293 } 294 295 var aaFlags aaParserFlags 296 if b.preseed { 297 aaFlags = skipKernelLoad 298 } 299 errReload := loadProfiles(pathnames, cache, aaFlags) 300 errUnload := unloadProfiles(removed, cache) 301 if errEnsure != nil { 302 return fmt.Errorf("cannot synchronize snap-confine apparmor profile: %s", errEnsure) 303 } 304 if errReload != nil { 305 return fmt.Errorf("cannot reload snap-confine apparmor profile: %s", errReload) 306 } 307 if errUnload != nil { 308 return fmt.Errorf("cannot unload snap-confine apparmor profile: %s", errReload) 309 } 310 return nil 311 } 312 313 // nsProfile returns name of the apparmor profile for snap-update-ns for a given snap. 314 func nsProfile(snapName string) string { 315 return fmt.Sprintf("snap-update-ns.%s", snapName) 316 } 317 318 // profileGlobs returns a list of globs that describe the apparmor profiles of 319 // a given snap. 320 // 321 // Currently the list is just a pair. The first glob describes profiles for all 322 // apps and hooks while the second profile describes the snap-update-ns profile 323 // for the whole snap. 324 func profileGlobs(snapName string) []string { 325 return []string{interfaces.SecurityTagGlob(snapName), nsProfile(snapName)} 326 } 327 328 // Determine if a profile filename is removable during core refresh/rollback. 329 // This is needed because core devices are also special, the apparmor cache 330 // gets confused too easy, especially at rollbacks, so we delete the cache. See 331 // Setup(), below. Some systems employ a unified cache directory where all 332 // apparmor cache files are stored under one location so ensure we don't remove 333 // the snap profiles since snapd manages them elsewhere and instead only remove 334 // snap-confine and system profiles (eg, as shipped by distro package manager 335 // or created by the administrator). snap-confine profiles are like the 336 // following: 337 // - usr.lib.snapd.snap-confine.real 338 // - usr.lib.snapd.snap-confine (historic) 339 // - snap.core.NNNN.usr.lib.snapd.snap-confine (historic) 340 // - var.lib.snapd.snap.core.NNNN.usr.lib.snapd.snap-confine (historic) 341 // - snap-confine.core.NNNN 342 // - snap-confine.snapd.NNNN 343 func profileIsRemovableOnCoreSetup(fn string) bool { 344 bn := path.Base(fn) 345 if strings.HasPrefix(bn, ".") { 346 return false 347 } else if strings.HasPrefix(bn, "snap") && !strings.HasPrefix(bn, "snap-confine.core.") && !strings.HasPrefix(bn, "snap-confine.snapd.") && !strings.Contains(bn, "usr.lib.snapd.snap-confine") { 348 return false 349 } 350 return true 351 } 352 353 type profilePathsResults struct { 354 changed []string 355 unchanged []string 356 removed []string 357 } 358 359 func (b *Backend) prepareProfiles(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) (prof *profilePathsResults, err error) { 360 snapName := snapInfo.InstanceName() 361 spec, err := repo.SnapSpecification(b.Name(), snapName) 362 if err != nil { 363 return nil, fmt.Errorf("cannot obtain apparmor specification for snap %q: %s", snapName, err) 364 } 365 366 // Add snippets for parallel snap installation mapping 367 spec.(*Specification).AddOvername(snapInfo) 368 369 // Add snippets derived from the layout definition. 370 spec.(*Specification).AddLayout(snapInfo) 371 372 // core on classic is special 373 if snapName == "core" && release.OnClassic && apparmor_sandbox.ProbedLevel() != apparmor_sandbox.Unsupported { 374 if err := b.setupSnapConfineReexec(snapInfo); err != nil { 375 return nil, fmt.Errorf("cannot create host snap-confine apparmor configuration: %s", err) 376 } 377 } 378 379 // Deal with the "snapd" snap - we do the setup slightly differently 380 // here because this will run both on classic and on Ubuntu Core 18 381 // systems but /etc/apparmor.d is not writable on core18 systems 382 if snapInfo.Type() == snap.TypeSnapd && apparmor_sandbox.ProbedLevel() != apparmor_sandbox.Unsupported { 383 if err := b.setupSnapConfineReexec(snapInfo); err != nil { 384 return nil, fmt.Errorf("cannot create host snap-confine apparmor configuration: %s", err) 385 } 386 } 387 388 // core on core devices is also special, the apparmor cache gets 389 // confused too easy, especially at rollbacks, so we delete the cache. 390 // See LP:#1460152 and 391 // https://forum.snapcraft.io/t/core-snap-revert-issues-on-core-devices/ 392 // 393 if (snapInfo.Type() == snap.TypeOS || snapInfo.Type() == snap.TypeSnapd) && !release.OnClassic { 394 if li, err := filepath.Glob(filepath.Join(apparmor_sandbox.SystemCacheDir, "*")); err == nil { 395 for _, p := range li { 396 if st, err := os.Stat(p); err == nil && st.Mode().IsRegular() && profileIsRemovableOnCoreSetup(p) { 397 if err := os.Remove(p); err != nil { 398 logger.Noticef("cannot remove %q: %s", p, err) 399 } 400 } 401 } 402 } 403 } 404 405 // Get the files that this snap should have 406 content, err := b.deriveContent(spec.(*Specification), snapInfo, opts) 407 if err != nil { 408 return nil, fmt.Errorf("cannot obtain expected security files for snap %q: %s", snapName, err) 409 } 410 dir := dirs.SnapAppArmorDir 411 globs := profileGlobs(snapInfo.InstanceName()) 412 if err := os.MkdirAll(dir, 0755); err != nil { 413 return nil, fmt.Errorf("cannot create directory for apparmor profiles %q: %s", dir, err) 414 } 415 changed, removedPaths, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, content) 416 // XXX: in the old code this error was reported late, after doing load/unload. 417 if errEnsure != nil { 418 return nil, fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure) 419 } 420 421 // Find the set of unchanged profiles. 422 unchanged := make([]string, 0, len(content)-len(changed)) 423 for name := range content { 424 // changed is pre-sorted by EnsureDirStateGlobs 425 x := sort.SearchStrings(changed, name) 426 if x < len(changed) && changed[x] == name { 427 continue 428 } 429 unchanged = append(unchanged, name) 430 } 431 sort.Strings(unchanged) 432 433 changedPaths := make([]string, len(changed)) 434 for i, profile := range changed { 435 changedPaths[i] = filepath.Join(dir, profile) 436 } 437 438 unchangedPaths := make([]string, len(unchanged)) 439 for i, profile := range unchanged { 440 unchangedPaths[i] = filepath.Join(dir, profile) 441 } 442 443 return &profilePathsResults{changed: changedPaths, removed: removedPaths, unchanged: unchangedPaths}, nil 444 } 445 446 // Setup creates and loads apparmor profiles specific to a given snap. 447 // The snap can be in developer mode to make security violations non-fatal to 448 // the offending application process. 449 // 450 // This method should be called after changing plug, slots, connections between 451 // them or application present in the snap. 452 func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error { 453 prof, err := b.prepareProfiles(snapInfo, opts, repo) 454 if err != nil { 455 return err 456 } 457 458 // Load all changed profiles with a flag that asks apparmor to skip reading 459 // the cache (since we know those changed for sure). This allows us to 460 // work despite time being wrong (e.g. in the past). For more details see 461 // https://forum.snapcraft.io/t/apparmor-profile-caching/1268/18 462 var errReloadChanged error 463 aaFlags := skipReadCache 464 if b.preseed { 465 aaFlags |= skipKernelLoad 466 } 467 timings.Run(tm, "load-profiles[changed]", fmt.Sprintf("load changed security profiles of snap %q", snapInfo.InstanceName()), func(nesttm timings.Measurer) { 468 errReloadChanged = loadProfiles(prof.changed, apparmor_sandbox.CacheDir, aaFlags) 469 }) 470 471 // Load all unchanged profiles anyway. This ensures those are correct in 472 // the kernel even if the files on disk were not changed. We rely on 473 // apparmor cache to make this performant. 474 var errReloadOther error 475 aaFlags = 0 476 if b.preseed { 477 aaFlags |= skipKernelLoad 478 } 479 timings.Run(tm, "load-profiles[unchanged]", fmt.Sprintf("load unchanged security profiles of snap %q", snapInfo.InstanceName()), func(nesttm timings.Measurer) { 480 errReloadOther = loadProfiles(prof.unchanged, apparmor_sandbox.CacheDir, aaFlags) 481 }) 482 errUnload := unloadProfiles(prof.removed, apparmor_sandbox.CacheDir) 483 if errReloadChanged != nil { 484 return errReloadChanged 485 } 486 if errReloadOther != nil { 487 return errReloadOther 488 } 489 return errUnload 490 } 491 492 // SetupMany creates and loads apparmor profiles for multiple snaps. 493 // The snaps can be in developer mode to make security violations non-fatal to 494 // the offending application process. 495 // SetupMany tries to recreate all profiles without interrupting on errors, but 496 // collects and returns them all. 497 // 498 // This method is useful mainly for regenerating profiles. 499 func (b *Backend) SetupMany(snaps []*snap.Info, confinement func(snapName string) interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) []error { 500 var allChangedPaths, allUnchangedPaths, allRemovedPaths []string 501 var fallback bool 502 for _, snapInfo := range snaps { 503 opts := confinement(snapInfo.InstanceName()) 504 prof, err := b.prepareProfiles(snapInfo, opts, repo) 505 if err != nil { 506 fallback = true 507 break 508 } 509 allChangedPaths = append(allChangedPaths, prof.changed...) 510 allUnchangedPaths = append(allUnchangedPaths, prof.unchanged...) 511 allRemovedPaths = append(allRemovedPaths, prof.removed...) 512 } 513 514 if !fallback { 515 aaFlags := skipReadCache | conserveCPU 516 if b.preseed { 517 aaFlags |= skipKernelLoad 518 } 519 var errReloadChanged error 520 timings.Run(tm, "load-profiles[changed-many]", fmt.Sprintf("load changed security profiles of %d snaps", len(snaps)), func(nesttm timings.Measurer) { 521 errReloadChanged = loadProfiles(allChangedPaths, apparmor_sandbox.CacheDir, aaFlags) 522 }) 523 524 aaFlags = conserveCPU 525 if b.preseed { 526 aaFlags |= skipKernelLoad 527 } 528 var errReloadOther error 529 timings.Run(tm, "load-profiles[unchanged-many]", fmt.Sprintf("load unchanged security profiles %d snaps", len(snaps)), func(nesttm timings.Measurer) { 530 errReloadOther = loadProfiles(allUnchangedPaths, apparmor_sandbox.CacheDir, aaFlags) 531 }) 532 533 errUnload := unloadProfiles(allRemovedPaths, apparmor_sandbox.CacheDir) 534 if errReloadChanged != nil { 535 logger.Noticef("failed to batch-reload changed profiles: %s", errReloadChanged) 536 fallback = true 537 } 538 if errReloadOther != nil { 539 logger.Noticef("failed to batch-reload unchanged profiles: %s", errReloadOther) 540 fallback = true 541 } 542 if errUnload != nil { 543 logger.Noticef("failed to batch-unload profiles: %s", errUnload) 544 fallback = true 545 } 546 } 547 548 var errors []error 549 // if an error was encountered when processing all profiles at once, re-try them one by one 550 if fallback { 551 for _, snapInfo := range snaps { 552 opts := confinement(snapInfo.InstanceName()) 553 if err := b.Setup(snapInfo, opts, repo, tm); err != nil { 554 errors = append(errors, fmt.Errorf("cannot setup profiles for snap %q: %s", snapInfo.InstanceName(), err)) 555 } 556 } 557 } 558 return errors 559 } 560 561 // Remove removes and unloads apparmor profiles of a given snap. 562 func (b *Backend) Remove(snapName string) error { 563 dir := dirs.SnapAppArmorDir 564 globs := profileGlobs(snapName) 565 cache := apparmor_sandbox.CacheDir 566 _, removed, errEnsure := osutil.EnsureDirStateGlobs(dir, globs, nil) 567 // always try to unload affected profiles 568 errUnload := unloadProfiles(removed, cache) 569 if errEnsure != nil { 570 return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, errEnsure) 571 } 572 return errUnload 573 } 574 575 func (b *Backend) RemoveLate(snapName string, rev snap.Revision, typ snap.Type) error { 576 logger.Debugf("remove late for snap %v (%s) type %v", snapName, rev, typ) 577 if typ != snap.TypeSnapd { 578 // late remove is relevant only for snap confine profiles 579 return nil 580 } 581 582 globs := []string{snapConfineProfileName(snapName, rev)} 583 _, removed, errEnsure := osutil.EnsureDirStateGlobs(dirs.SnapAppArmorDir, globs, nil) 584 // XXX: unloadProfiles() does not unload profiles from the kernel, but 585 // only removes profiles from the cache 586 // always try to unload the affected profile 587 errUnload := unloadProfiles(removed, apparmor_sandbox.CacheDir) 588 if errEnsure != nil { 589 return fmt.Errorf("cannot remove security profiles for snap %q (%s): %s", snapName, rev, errEnsure) 590 } 591 return errUnload 592 } 593 594 var ( 595 templatePattern = regexp.MustCompile("(###[A-Z_]+###)") 596 coreRuntimePattern = regexp.MustCompile("^core([0-9][0-9])?$") 597 ) 598 599 const ( 600 attachPattern = "(attach_disconnected,mediate_deleted)" 601 attachComplain = "(attach_disconnected,mediate_deleted,complain)" 602 ) 603 604 func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info, opts interfaces.ConfinementOptions) (content map[string]osutil.FileState, err error) { 605 content = make(map[string]osutil.FileState, len(snapInfo.Apps)+len(snapInfo.Hooks)+1) 606 607 // Add profile for each app. 608 for _, appInfo := range snapInfo.Apps { 609 securityTag := appInfo.SecurityTag() 610 addContent(securityTag, snapInfo, appInfo.Name, opts, spec.SnippetForTag(securityTag), content, spec) 611 } 612 // Add profile for each hook. 613 for _, hookInfo := range snapInfo.Hooks { 614 securityTag := hookInfo.SecurityTag() 615 addContent(securityTag, snapInfo, "hook."+hookInfo.Name, opts, spec.SnippetForTag(securityTag), content, spec) 616 } 617 // Add profile for snap-update-ns if we have any apps or hooks. 618 // If we have neither then we don't have any need to create an executing environment. 619 // This applies to, for example, kernel snaps or gadget snaps (unless they have hooks). 620 if len(content) > 0 { 621 snippets := strings.Join(spec.UpdateNS(), "\n") 622 addUpdateNSProfile(snapInfo, opts, snippets, content) 623 } 624 625 return content, nil 626 } 627 628 // addUpdateNSProfile adds an apparmor profile for snap-update-ns, tailored to a specific snap. 629 // 630 // This profile exists so that snap-update-ns doens't need to carry very wide, open permissions 631 // that are suitable for poking holes (and writing) in nearly arbitrary places. Instead the profile 632 // contains just the permissions needed to poke a hole and write to the layout-specific paths. 633 func addUpdateNSProfile(snapInfo *snap.Info, opts interfaces.ConfinementOptions, snippets string, content map[string]osutil.FileState) { 634 // Compute the template by injecting special updateNS snippets. 635 policy := templatePattern.ReplaceAllStringFunc(updateNSTemplate, func(placeholder string) string { 636 switch placeholder { 637 case "###SNAP_INSTANCE_NAME###": 638 return snapInfo.InstanceName() 639 case "###SNIPPETS###": 640 if overlayRoot, _ := isRootWritableOverlay(); overlayRoot != "" { 641 snippets += strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1) 642 } 643 return snippets 644 } 645 return "" 646 }) 647 648 // Ensure that the snap-update-ns profile is on disk. 649 profileName := nsProfile(snapInfo.InstanceName()) 650 content[profileName] = &osutil.MemoryFileState{ 651 Content: []byte(policy), 652 Mode: 0644, 653 } 654 } 655 656 func addContent(securityTag string, snapInfo *snap.Info, cmdName string, opts interfaces.ConfinementOptions, snippetForTag string, content map[string]osutil.FileState, spec *Specification) { 657 // If base is specified and it doesn't match the core snaps (not 658 // specifying a base should use the default core policy since in this 659 // case, the 'core' snap is used for the runtime), use the base 660 // apparmor template, otherwise use the default template. 661 var policy string 662 if snapInfo.Base != "" && !coreRuntimePattern.MatchString(snapInfo.Base) { 663 policy = defaultOtherBaseTemplate 664 } else { 665 policy = defaultCoreRuntimeTemplate 666 } 667 668 ignoreSnippets := false 669 // Classic confinement (unless overridden by JailMode) has a dedicated 670 // permissive template that applies a strict, but very open, policy. 671 if opts.Classic && !opts.JailMode { 672 policy = classicTemplate 673 ignoreSnippets = true 674 } 675 // If a snap is in devmode (or is using classic confinement) then make the 676 // profile non-enforcing where violations are logged but not denied. 677 // This is also done for classic so that no confinement applies. Just in 678 // case the profile we start with is not permissive enough. 679 if (opts.DevMode || opts.Classic) && !opts.JailMode { 680 policy = strings.Replace(policy, attachPattern, attachComplain, -1) 681 } 682 policy = templatePattern.ReplaceAllStringFunc(policy, func(placeholder string) string { 683 switch placeholder { 684 case "###VAR###": 685 return templateVariables(snapInfo, securityTag, cmdName) 686 case "###PROFILEATTACH###": 687 return fmt.Sprintf("profile \"%s\"", securityTag) 688 case "###CHANGEPROFILE_RULE###": 689 features, _ := parserFeatures() 690 for _, f := range features { 691 if f == "unsafe" { 692 return "change_profile unsafe /**," 693 } 694 } 695 return "change_profile," 696 case "###SNIPPETS###": 697 var tagSnippets string 698 if opts.Classic && opts.JailMode { 699 // Add a special internal snippet for snaps using classic confinement 700 // and jailmode together. This snippet provides access to the core snap 701 // so that the dynamic linker and shared libraries can be used. 702 tagSnippets = classicJailmodeSnippet + "\n" + snippetForTag 703 } else if ignoreSnippets { 704 // When classic confinement template is in effect we are 705 // ignoring all apparmor snippets as they may conflict with the 706 // super-broad template we are starting with. 707 } else { 708 // Check if NFS is mounted at or under $HOME. Because NFS is not 709 // transparent to apparmor we must alter the profile to counter that and 710 // allow access to SNAP_USER_* files. 711 tagSnippets = snippetForTag 712 if nfs, _ := isHomeUsingNFS(); nfs { 713 tagSnippets += nfsSnippet 714 } 715 716 if overlayRoot, _ := isRootWritableOverlay(); overlayRoot != "" { 717 snippet := strings.Replace(overlayRootSnippet, "###UPPERDIR###", overlayRoot, -1) 718 tagSnippets += snippet 719 } 720 } 721 722 if !ignoreSnippets { 723 // For policy with snippets that request 724 // suppression of 'ptrace (trace)' denials, add 725 // the suppression rule unless another 726 // interface said it uses them. 727 if spec.SuppressPtraceTrace() && !spec.UsesPtraceTrace() { 728 tagSnippets += ptraceTraceDenySnippet 729 } 730 731 // Use 'ix' rules in the home interface unless an 732 // interface asked to suppress them 733 repl := "ix" 734 if spec.SuppressHomeIx() { 735 repl = "" 736 } 737 tagSnippets = strings.Replace(tagSnippets, "###HOME_IX###", repl, -1) 738 739 // Conditionally add privilege dropping policy 740 if len(snapInfo.SystemUsernames) > 0 { 741 tagSnippets += privDropAndChownRules 742 } 743 } 744 745 return tagSnippets 746 } 747 return "" 748 }) 749 750 content[securityTag] = &osutil.MemoryFileState{ 751 Content: []byte(policy), 752 Mode: 0644, 753 } 754 } 755 756 // NewSpecification returns a new, empty apparmor specification. 757 func (b *Backend) NewSpecification() interfaces.Specification { 758 return &Specification{} 759 } 760 761 // SandboxFeatures returns the list of apparmor features supported by the kernel. 762 func (b *Backend) SandboxFeatures() []string { 763 if apparmor_sandbox.ProbedLevel() == apparmor_sandbox.Unsupported { 764 return nil 765 } 766 767 kFeatures, _ := kernelFeatures() 768 pFeatures, _ := parserFeatures() 769 tags := make([]string, 0, len(kFeatures)+len(pFeatures)) 770 for _, feature := range kFeatures { 771 // Prepend "kernel:" to apparmor kernel features to namespace them and 772 // allow us to introduce our own tags later. 773 tags = append(tags, "kernel:"+feature) 774 } 775 776 for _, feature := range pFeatures { 777 // Prepend "parser:" to apparmor kernel features to namespace 778 // them and allow us to introduce our own tags later. 779 tags = append(tags, "parser:"+feature) 780 } 781 782 level := "full" 783 policy := "default" 784 if apparmor_sandbox.ProbedLevel() == apparmor_sandbox.Partial { 785 level = "partial" 786 } 787 tags = append(tags, fmt.Sprintf("support-level:%s", level)) 788 tags = append(tags, fmt.Sprintf("policy:%s", policy)) 789 790 return tags 791 } 792 793 // MockIsHomeUsingNFS mocks the real implementation of osutil.IsHomeUsingNFS. 794 // This is exported so that other packages that indirectly interact with AppArmor backend 795 // can mock isHomeUsingNFS. 796 func MockIsHomeUsingNFS(new func() (bool, error)) (restore func()) { 797 old := isHomeUsingNFS 798 isHomeUsingNFS = new 799 return func() { 800 isHomeUsingNFS = old 801 } 802 }