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