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