github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/interfaces/seccomp/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 seccomp implements integration between snapd and snap-confine around 21 // seccomp. 22 // 23 // Snappy creates so-called seccomp profiles for each application (for each 24 // snap) present in the system. Upon each execution of snap-confine, the 25 // profile is read and "compiled" to an eBPF program and injected into the 26 // kernel for the duration of the execution of the process. 27 // 28 // There is no binary cache for seccomp, each time the launcher starts an 29 // application the profile is parsed and re-compiled. 30 // 31 // The actual profiles are stored in /var/lib/snappy/seccomp/bpf/*.{src,bin}. 32 // This directory is hard-coded in snap-confine. 33 package seccomp 34 35 import ( 36 "bytes" 37 "fmt" 38 "os" 39 "path/filepath" 40 "runtime" 41 "strings" 42 43 "github.com/snapcore/snapd/arch" 44 "github.com/snapcore/snapd/dirs" 45 "github.com/snapcore/snapd/interfaces" 46 "github.com/snapcore/snapd/osutil" 47 "github.com/snapcore/snapd/release" 48 "github.com/snapcore/snapd/sandbox/apparmor" 49 "github.com/snapcore/snapd/sandbox/seccomp" 50 "github.com/snapcore/snapd/snap" 51 "github.com/snapcore/snapd/snapdtool" 52 "github.com/snapcore/snapd/strutil" 53 "github.com/snapcore/snapd/timings" 54 ) 55 56 var ( 57 kernelFeatures = seccomp.Actions 58 dpkgKernelArchitecture = arch.DpkgKernelArchitecture 59 releaseInfoId = release.ReleaseInfo.ID 60 releaseInfoVersionId = release.ReleaseInfo.VersionID 61 requiresSocketcall = requiresSocketcallImpl 62 63 snapSeccompVersionInfo = snapSeccompVersionInfoImpl 64 seccompCompilerLookup = snapdtool.InternalToolPath 65 ) 66 67 func snapSeccompVersionInfoImpl(c Compiler) (seccomp.VersionInfo, error) { 68 return c.VersionInfo() 69 } 70 71 type Compiler interface { 72 Compile(in, out string) error 73 VersionInfo() (seccomp.VersionInfo, error) 74 } 75 76 // Backend is responsible for maintaining seccomp profiles for snap-confine. 77 type Backend struct { 78 snapSeccomp Compiler 79 versionInfo seccomp.VersionInfo 80 } 81 82 var globalProfileLE = []byte{ 83 0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x04, 0x3e, 0x00, 0x00, 0xc0, 84 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 85 0x15, 0x00, 0x00, 0x0d, 0xff, 0xff, 0xff, 0xff, 0x15, 0x00, 0x06, 0x0c, 0x10, 0x00, 0x00, 0x00, 86 0x15, 0x00, 0x00, 0x02, 0xb7, 0x00, 0x00, 0xc0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 87 0x15, 0x00, 0x03, 0x09, 0x1d, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x08, 0x15, 0x00, 0x00, 0xc0, 88 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x06, 0x36, 0x00, 0x00, 0x00, 89 0x20, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 90 0x15, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 91 0x15, 0x00, 0x00, 0x01, 0x12, 0x54, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 92 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 93 } 94 95 var globalProfileBE = []byte{ 96 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x15, 0x00, 0x08, 0x80, 0x00, 0x00, 0x16, 97 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 98 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 99 0x00, 0x15, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 100 0x00, 0x15, 0x00, 0x01, 0x00, 0x00, 0x54, 0x12, 0x00, 0x06, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 101 0x00, 0x06, 0x00, 0x00, 0x7f, 0xff, 0x00, 0x00, 102 } 103 104 func isBigEndian() bool { 105 switch runtime.GOARCH { 106 case "s390x": 107 return true 108 case "ppc": 109 return true 110 // TODO: perhaps more here? 111 } 112 return false 113 } 114 115 // Initialize ensures that the global profile is on disk and interrogates 116 // libseccomp wrapper to generate a version string that will be used to 117 // determine if we need to recompile seccomp policy due to system 118 // changes outside of snapd. 119 func (b *Backend) Initialize(*interfaces.SecurityBackendOptions) error { 120 dir := dirs.SnapSeccompDir 121 fname := "global.bin" 122 var globalProfile []byte 123 if isBigEndian() { 124 globalProfile = globalProfileBE 125 } else { 126 globalProfile = globalProfileLE 127 } 128 content := map[string]osutil.FileState{ 129 fname: &osutil.MemoryFileState{Content: globalProfile, Mode: 0644}, 130 } 131 if err := os.MkdirAll(dir, 0755); err != nil { 132 return fmt.Errorf("cannot create directory for seccomp profiles %q: %s", dir, err) 133 } 134 _, _, err := osutil.EnsureDirState(dir, fname, content) 135 if err != nil { 136 return fmt.Errorf("cannot synchronize global seccomp profile: %s", err) 137 } 138 139 b.snapSeccomp, err = seccomp.NewCompiler(seccompCompilerLookup) 140 if err != nil { 141 return fmt.Errorf("cannot initialize seccomp profile compiler: %v", err) 142 } 143 144 versionInfo, err := snapSeccompVersionInfo(b.snapSeccomp) 145 if err != nil { 146 return fmt.Errorf("cannot obtain snap-seccomp version information: %v", err) 147 } 148 b.versionInfo = versionInfo 149 return nil 150 } 151 152 // Name returns the name of the backend. 153 func (b *Backend) Name() interfaces.SecuritySystem { 154 return interfaces.SecuritySecComp 155 } 156 157 func bpfSrcPath(srcName string) string { 158 return filepath.Join(dirs.SnapSeccompDir, srcName) 159 } 160 161 func bpfBinPath(srcName string) string { 162 return filepath.Join(dirs.SnapSeccompDir, strings.TrimSuffix(srcName, ".src")+".bin") 163 } 164 165 func parallelCompile(compiler Compiler, profiles []string) error { 166 if len(profiles) == 0 { 167 // no profiles, nothing to do 168 return nil 169 } 170 171 profilesQueue := make(chan string, len(profiles)) 172 numWorkers := runtime.NumCPU() 173 if numWorkers >= 2 { 174 numWorkers -= 1 175 } 176 if numWorkers > len(profiles) { 177 numWorkers = len(profiles) 178 } 179 resultsBufferSize := numWorkers * 2 180 if resultsBufferSize > len(profiles) { 181 resultsBufferSize = len(profiles) 182 } 183 res := make(chan error, resultsBufferSize) 184 185 // launch as many workers as we have CPUs 186 for i := 0; i < numWorkers; i++ { 187 go func() { 188 for { 189 profile, ok := <-profilesQueue 190 if !ok { 191 break 192 } 193 in := bpfSrcPath(profile) 194 out := bpfBinPath(profile) 195 // remove the old profile first so that we are 196 // not loading it accidentally should the 197 // compilation fail 198 if err := os.Remove(out); err != nil && !os.IsNotExist(err) { 199 res <- err 200 continue 201 } 202 203 // snap-seccomp uses AtomicWriteFile internally, on failure the 204 // output file is unlinked 205 if err := compiler.Compile(in, out); err != nil { 206 res <- fmt.Errorf("cannot compile %s: %v", in, err) 207 } else { 208 res <- nil 209 } 210 } 211 }() 212 } 213 214 for _, p := range profiles { 215 profilesQueue <- p 216 } 217 // signal workers to exit 218 close(profilesQueue) 219 220 var firstErr error 221 for i := 0; i < len(profiles); i++ { 222 maybeErr := <-res 223 if maybeErr != nil && firstErr == nil { 224 firstErr = maybeErr 225 } 226 227 } 228 229 // not expecting any more results 230 close(res) 231 232 if firstErr != nil { 233 for _, p := range profiles { 234 out := bpfBinPath(p) 235 // unlink all profiles that could have been successfully 236 // compiled 237 os.Remove(out) 238 } 239 240 } 241 return firstErr 242 } 243 244 // Setup creates seccomp profiles specific to a given snap. 245 // The snap can be in developer mode to make security violations non-fatal to 246 // the offending application process. 247 // 248 // This method should be called after changing plug, slots, connections between 249 // them or application present in the snap. 250 func (b *Backend) Setup(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error { 251 snapName := snapInfo.InstanceName() 252 // Get the snippets that apply to this snap 253 spec, err := repo.SnapSpecification(b.Name(), snapName) 254 if err != nil { 255 return fmt.Errorf("cannot obtain seccomp specification for snap %q: %s", snapName, err) 256 } 257 258 // Get the snippets that apply to this snap 259 content, err := b.deriveContent(spec.(*Specification), opts, snapInfo) 260 if err != nil { 261 return fmt.Errorf("cannot obtain expected security files for snap %q: %s", snapName, err) 262 } 263 264 glob := interfaces.SecurityTagGlob(snapName) + ".src" 265 dir := dirs.SnapSeccompDir 266 if err := os.MkdirAll(dir, 0755); err != nil { 267 return fmt.Errorf("cannot create directory for seccomp profiles %q: %s", dir, err) 268 } 269 // There is a delicate interaction between `snap run`, `snap-confine` 270 // and compilation of profiles: 271 // - whenever profiles need to be rebuilt due to system-key change, 272 // `snap run` detects the system-key mismatch and waits for snapd 273 // (system key is only updated once all security backends have 274 // finished their job) 275 // - whenever the binary file does not exist, `snap-confine` will poll 276 // and wait for SNAP_CONFINE_MAX_PROFILE_WAIT, if the profile does not 277 // appear in that time, `snap-confine` will fail and die 278 changed, removed, err := osutil.EnsureDirState(dir, glob, content) 279 if err != nil { 280 return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, err) 281 } 282 for _, c := range removed { 283 err := os.Remove(bpfBinPath(c)) 284 if err != nil && !os.IsNotExist(err) { 285 return err 286 } 287 } 288 289 return parallelCompile(b.snapSeccomp, changed) 290 } 291 292 // Remove removes seccomp profiles of a given snap. 293 func (b *Backend) Remove(snapName string) error { 294 glob := interfaces.SecurityTagGlob(snapName) 295 _, _, err := osutil.EnsureDirState(dirs.SnapSeccompDir, glob, nil) 296 if err != nil { 297 return fmt.Errorf("cannot synchronize security files for snap %q: %s", snapName, err) 298 } 299 return nil 300 } 301 302 // Obtain the privilege dropping snippet 303 func uidGidChownSnippet(name string) (string, error) { 304 tmp := strings.Replace(privDropAndChownSyscalls, "###USERNAME###", name, -1) 305 return strings.Replace(tmp, "###GROUP###", name, -1), nil 306 } 307 308 // deriveContent combines security snippets collected from all the interfaces 309 // affecting a given snap into a content map applicable to EnsureDirState. 310 func (b *Backend) deriveContent(spec *Specification, opts interfaces.ConfinementOptions, snapInfo *snap.Info) (content map[string]osutil.FileState, err error) { 311 // Some base snaps and systems require the socketcall() in the default 312 // template 313 addSocketcall := requiresSocketcall(snapInfo.Base) 314 315 var uidGidChownSyscalls bytes.Buffer 316 if len(snapInfo.SystemUsernames) == 0 { 317 uidGidChownSyscalls.WriteString(barePrivDropSyscalls) 318 } else { 319 for _, id := range snapInfo.SystemUsernames { 320 syscalls, err := uidGidChownSnippet(id.Name) 321 if err != nil { 322 return nil, fmt.Errorf(`cannot calculate syscalls for "%s": %s`, id, err) 323 } 324 uidGidChownSyscalls.WriteString(syscalls) 325 } 326 uidGidChownSyscalls.WriteString(rootSetUidGidSyscalls) 327 } 328 329 for _, hookInfo := range snapInfo.Hooks { 330 if content == nil { 331 content = make(map[string]osutil.FileState) 332 } 333 securityTag := hookInfo.SecurityTag() 334 335 path := securityTag + ".src" 336 content[path] = &osutil.MemoryFileState{ 337 Content: generateContent(opts, spec.SnippetForTag(securityTag), addSocketcall, b.versionInfo, uidGidChownSyscalls.String()), 338 Mode: 0644, 339 } 340 } 341 for _, appInfo := range snapInfo.Apps { 342 if content == nil { 343 content = make(map[string]osutil.FileState) 344 } 345 securityTag := appInfo.SecurityTag() 346 path := securityTag + ".src" 347 content[path] = &osutil.MemoryFileState{ 348 Content: generateContent(opts, spec.SnippetForTag(securityTag), addSocketcall, b.versionInfo, uidGidChownSyscalls.String()), 349 Mode: 0644, 350 } 351 } 352 353 return content, nil 354 } 355 356 func generateContent(opts interfaces.ConfinementOptions, snippetForTag string, addSocketcall bool, versionInfo seccomp.VersionInfo, uidGidChownSyscalls string) []byte { 357 var buffer bytes.Buffer 358 359 if versionInfo != "" { 360 buffer.WriteString("# snap-seccomp version information:\n") 361 fmt.Fprintf(&buffer, "# %s\n", versionInfo) 362 } 363 364 if opts.Classic && !opts.JailMode { 365 // NOTE: This is understood by snap-confine 366 buffer.WriteString("@unrestricted\n") 367 } 368 if opts.DevMode && !opts.JailMode { 369 // NOTE: This is understood by snap-confine 370 buffer.WriteString("@complain\n") 371 if !seccomp.SupportsAction("log") { 372 buffer.WriteString("# complain mode logging unavailable\n") 373 } 374 } 375 376 buffer.Write(defaultTemplate) 377 buffer.WriteString(snippetForTag) 378 buffer.WriteString(uidGidChownSyscalls) 379 380 // For systems with partial or missing AppArmor support we need to apply 381 // a workaround to avoid failing hooks. See description in template.go 382 // for more details. 383 if apparmor.ProbedLevel() != apparmor.Full { 384 buffer.WriteString(bindSyscallWorkaround) 385 } 386 387 if addSocketcall { 388 buffer.WriteString(socketcallSyscallDeprecated) 389 } 390 391 return buffer.Bytes() 392 } 393 394 // NewSpecification returns an empty seccomp specification. 395 func (b *Backend) NewSpecification() interfaces.Specification { 396 return &Specification{} 397 } 398 399 // SandboxFeatures returns the list of seccomp features supported by the kernel 400 // and userspace. 401 func (b *Backend) SandboxFeatures() []string { 402 features := kernelFeatures() 403 tags := make([]string, 0, len(features)+1) 404 for _, feature := range features { 405 // Prepend "kernel:" to apparmor kernel features to namespace 406 // them. 407 tags = append(tags, "kernel:"+feature) 408 } 409 tags = append(tags, "bpf-argument-filtering") 410 411 if res, err := b.versionInfo.HasFeature("bpf-actlog"); err == nil && res { 412 tags = append(tags, "bpf-actlog") 413 } 414 415 return tags 416 } 417 418 // Determine if the system requires the use of socketcall(). Factors: 419 // - if the kernel architecture is amd64, armhf or arm64, do not require 420 // socketcall (unused on these architectures) 421 // - if the kernel architecture is i386 or s390x 422 // - if the kernel is < 4.3, force the use of socketcall() 423 // - for backwards compatibility, if the system is Ubuntu 14.04 or lower, 424 // force use of socketcall() 425 // - for backwards compatibility, if the base snap is unspecified, "core" or 426 // "core16", then force use of socketcall() 427 // - otherwise (ie, if new enough kernel, not 14.04, and a non-16 base 428 // snap), don't force use of socketcall() 429 // - if the kernel architecture is not any of the above, force the use of 430 // socketcall() 431 func requiresSocketcallImpl(baseSnap string) bool { 432 switch dpkgKernelArchitecture() { 433 case "i386", "s390x": 434 // glibc sysdeps/unix/sysv/linux/i386/kernel-features.h and 435 // sysdeps/unix/sysv/linux/s390/kernel-features.h added the 436 // individual socket syscalls in 4.3. 437 if cmp, _ := strutil.VersionCompare(osutil.KernelVersion(), "4.3"); cmp < 0 { 438 return true 439 } 440 441 // For now, on 14.04, always require socketcall() 442 if releaseInfoId == "ubuntu" { 443 if cmp, _ := strutil.VersionCompare(releaseInfoVersionId, "14.04"); cmp <= 0 { 444 return true 445 } 446 } 447 448 // Detect when the base snap requires the use of socketcall(). 449 // 450 // TODO: eventually try to auto-detect this. For now, err on 451 // the side of security and only require it for base snaps 452 // where we know we want it added. Technically, core16's glibc 453 // is new enough, but it always had socketcall in the template, 454 // so ensure backwards compatibility. 455 if baseSnap == "" || baseSnap == "core" || baseSnap == "core16" { 456 return true 457 } 458 459 // If none of the above, we don't need the syscall 460 return false 461 case "powerpc": 462 // glibc's sysdeps/unix/sysv/linux/powerpc/kernel-features.h 463 // states that the individual syscalls are all available as of 464 // 2.6.37. snapd isn't expected to run on these kernels so just 465 // default to unneeded. 466 return false 467 case "sparc", "sparc64": 468 // glibc's sysdeps/unix/sysv/linux/sparc/kernel-features.h 469 // indicates that socketcall() is used and the individual 470 // syscalls are undefined. 471 return true 472 default: 473 // amd64, arm64, armhf, ppc64el, etc 474 // glibc's sysdeps/unix/sysv/linux/kernel-features.h says that 475 // __ASSUME_SOCKETCALL will be defined on archs that need it. 476 // Modern architectures do not implement the socketcall() 477 // syscall and all the older architectures except sparc (see 478 // above) have introduced the individual syscalls, so default 479 // to unneeded. 480 return false 481 } 482 483 // If we got here, something went wrong, but if the code above changes 484 // the compiler will complain about the lack of 'return'. 485 } 486 487 // MockSnapSeccompVersionInfo is for use in tests only. 488 func MockSnapSeccompVersionInfo(versionInfo string) (restore func()) { 489 old := snapSeccompVersionInfo 490 snapSeccompVersionInfo = func(c Compiler) (seccomp.VersionInfo, error) { 491 return seccomp.VersionInfo(versionInfo), nil 492 } 493 return func() { 494 snapSeccompVersionInfo = old 495 } 496 }