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