github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/interfaces/seccomp/backend_test.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_test 21 22 import ( 23 "bytes" 24 "errors" 25 "fmt" 26 "io/ioutil" 27 "os" 28 "path/filepath" 29 "runtime" 30 "sort" 31 "sync" 32 33 . "gopkg.in/check.v1" 34 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/interfaces" 37 "github.com/snapcore/snapd/interfaces/ifacetest" 38 "github.com/snapcore/snapd/interfaces/seccomp" 39 "github.com/snapcore/snapd/osutil" 40 apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor" 41 seccomp_sandbox "github.com/snapcore/snapd/sandbox/seccomp" 42 "github.com/snapcore/snapd/snap" 43 "github.com/snapcore/snapd/snap/snaptest" 44 "github.com/snapcore/snapd/snapdtool" 45 "github.com/snapcore/snapd/strutil" 46 "github.com/snapcore/snapd/testutil" 47 "github.com/snapcore/snapd/timings" 48 ) 49 50 type backendSuite struct { 51 ifacetest.BackendSuite 52 53 snapSeccomp *testutil.MockCmd 54 profileHeader string 55 meas *timings.Span 56 57 restoreReadlink func() 58 } 59 60 var _ = Suite(&backendSuite{}) 61 62 var testedConfinementOpts = []interfaces.ConfinementOptions{ 63 {}, 64 {DevMode: true}, 65 {JailMode: true}, 66 {Classic: true}, 67 } 68 69 func (s *backendSuite) SetUpTest(c *C) { 70 s.Backend = &seccomp.Backend{} 71 s.BackendSuite.SetUpTest(c) 72 c.Assert(s.Repo.AddBackend(s.Backend), IsNil) 73 74 // Prepare a directory for seccomp profiles. 75 // NOTE: Normally this is a part of the OS snap. 76 err := os.MkdirAll(dirs.SnapSeccompDir, 0700) 77 c.Assert(err, IsNil) 78 79 s.restoreReadlink = snapdtool.MockOsReadlink(func(string) (string, error) { 80 // pretend that snapd is run from distro libexecdir 81 return filepath.Join(dirs.DistroLibExecDir, "snapd"), nil 82 }) 83 snapSeccompPath := filepath.Join(dirs.DistroLibExecDir, "snap-seccomp") 84 s.snapSeccomp = testutil.MockLockedCommand(c, snapSeccompPath, ` 85 if [ "$1" = "version-info" ]; then 86 echo "abcdef 1.2.3 1234abcd -" 87 fi`) 88 89 s.Backend.Initialize(nil) 90 s.profileHeader = `# snap-seccomp version information: 91 # abcdef 1.2.3 1234abcd - 92 ` 93 // make sure initialize called version-info 94 c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{ 95 {"snap-seccomp", "version-info"}, 96 }) 97 s.snapSeccomp.ForgetCalls() 98 99 perf := timings.New(nil) 100 s.meas = perf.StartSpan("", "") 101 } 102 103 func (s *backendSuite) TearDownTest(c *C) { 104 s.BackendSuite.TearDownTest(c) 105 106 s.snapSeccomp.Restore() 107 s.restoreReadlink() 108 } 109 110 func (s *backendSuite) TestInitialize(c *C) { 111 err := s.Backend.Initialize(nil) 112 c.Assert(err, IsNil) 113 fname := filepath.Join(dirs.SnapSeccompDir, "global.bin") 114 if seccomp.IsBigEndian() { 115 c.Check(fname, testutil.FileEquals, seccomp.GlobalProfileBE) 116 } else { 117 c.Check(fname, testutil.FileEquals, seccomp.GlobalProfileLE) 118 } 119 } 120 121 // Tests for Setup() and Remove() 122 func (s *backendSuite) TestName(c *C) { 123 c.Check(s.Backend.Name(), Equals, interfaces.SecuritySecComp) 124 } 125 126 func (s *backendSuite) TestInstallingSnapWritesProfiles(c *C) { 127 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 0) 128 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 129 // file called "snap.sambda.smbd" was created 130 _, err := os.Stat(profile + ".src") 131 c.Check(err, IsNil) 132 // and got compiled 133 c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{ 134 {"snap-seccomp", "compile", profile + ".src", profile + ".bin"}, 135 }) 136 } 137 138 func (s *backendSuite) TestInstallingSnapWritesHookProfiles(c *C) { 139 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.HookYaml, 0) 140 profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.hook.configure") 141 142 // Verify that profile named "snap.foo.hook.configure" was created. 143 _, err := os.Stat(profile + ".src") 144 c.Check(err, IsNil) 145 // and got compiled 146 c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{ 147 {"snap-seccomp", "compile", profile + ".src", profile + ".bin"}, 148 }) 149 } 150 151 func (s *backendSuite) TestInstallingSnapWritesProfilesWithReexec(c *C) { 152 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 153 // simulate that we run snapd from core 154 return filepath.Join(dirs.SnapMountDir, "core/42/usr/lib/snapd/snapd"), nil 155 }) 156 defer restore() 157 158 // ensure we have a mocked snap-seccomp on core 159 snapSeccompOnCorePath := filepath.Join(dirs.SnapMountDir, "core/42/usr/lib/snapd/snap-seccomp") 160 snapSeccompOnCore := testutil.MockLockedCommand(c, snapSeccompOnCorePath, `if [ "$1" = "version-info" ]; then 161 echo "2345cdef 2.3.4 2345cdef -" 162 fi`) 163 defer snapSeccompOnCore.Restore() 164 165 // rerun initialization 166 err := s.Backend.Initialize(nil) 167 c.Assert(err, IsNil) 168 169 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 0) 170 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 171 // file called "snap.sambda.smbd" was created 172 _, err = os.Stat(profile + ".src") 173 c.Check(err, IsNil) 174 // ensure the snap-seccomp from the regular path was *not* used 175 c.Check(s.snapSeccomp.Calls(), HasLen, 0) 176 // ensure the snap-seccomp from the core snap was used instead 177 c.Check(snapSeccompOnCore.Calls(), DeepEquals, [][]string{ 178 {"snap-seccomp", "version-info"}, 179 {"snap-seccomp", "compile", profile + ".src", profile + ".bin"}, 180 }) 181 raw, err := ioutil.ReadFile(profile + ".src") 182 c.Assert(err, IsNil) 183 c.Assert(bytes.HasPrefix(raw, []byte(`# snap-seccomp version information: 184 # 2345cdef 2.3.4 2345cdef - 185 `)), Equals, true) 186 } 187 188 func (s *backendSuite) TestRemovingSnapRemovesProfiles(c *C) { 189 for _, opts := range testedConfinementOpts { 190 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0) 191 s.RemoveSnap(c, snapInfo) 192 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 193 // file called "snap.sambda.smbd" was removed 194 _, err := os.Stat(profile + ".src") 195 c.Check(os.IsNotExist(err), Equals, true) 196 } 197 } 198 199 func (s *backendSuite) TestRemovingSnapRemovesHookProfiles(c *C) { 200 for _, opts := range testedConfinementOpts { 201 snapInfo := s.InstallSnap(c, opts, "", ifacetest.HookYaml, 0) 202 s.RemoveSnap(c, snapInfo) 203 profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.hook.configure") 204 205 // Verify that profile "snap.foo.hook.configure" was removed. 206 _, err := os.Stat(profile + ".src") 207 c.Check(os.IsNotExist(err), Equals, true) 208 } 209 } 210 211 func (s *backendSuite) TestUpdatingSnapToOneWithMoreApps(c *C) { 212 for _, opts := range testedConfinementOpts { 213 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0) 214 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1WithNmbd, 0) 215 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.nmbd") 216 _, err := os.Stat(profile + ".src") 217 // file called "snap.sambda.nmbd" was created 218 c.Check(err, IsNil) 219 // and got compiled 220 c.Check(s.snapSeccomp.Calls(), testutil.DeepContains, []string{"snap-seccomp", "compile", profile + ".src", profile + ".bin"}) 221 s.snapSeccomp.ForgetCalls() 222 223 s.RemoveSnap(c, snapInfo) 224 } 225 } 226 227 func (s *backendSuite) TestUpdatingSnapToOneWithHooks(c *C) { 228 for _, opts := range testedConfinementOpts { 229 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1, 0) 230 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlWithHook, 0) 231 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.hook.configure") 232 233 _, err := os.Stat(profile + ".src") 234 // Verify that profile "snap.samba.hook.configure" was created. 235 c.Check(err, IsNil) 236 // and got compiled 237 c.Check(s.snapSeccomp.Calls(), testutil.DeepContains, []string{"snap-seccomp", "compile", profile + ".src", profile + ".bin"}) 238 s.snapSeccomp.ForgetCalls() 239 240 s.RemoveSnap(c, snapInfo) 241 } 242 } 243 244 func (s *backendSuite) TestUpdatingSnapToOneWithFewerApps(c *C) { 245 for _, opts := range testedConfinementOpts { 246 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlV1WithNmbd, 0) 247 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 0) 248 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.nmbd") 249 // file called "snap.sambda.nmbd" was removed 250 _, err := os.Stat(profile + ".src") 251 c.Check(os.IsNotExist(err), Equals, true) 252 s.RemoveSnap(c, snapInfo) 253 } 254 } 255 256 func (s *backendSuite) TestUpdatingSnapToOneWithNoHooks(c *C) { 257 for _, opts := range testedConfinementOpts { 258 snapInfo := s.InstallSnap(c, opts, "", ifacetest.SambaYamlWithHook, 0) 259 snapInfo = s.UpdateSnap(c, snapInfo, opts, ifacetest.SambaYamlV1, 0) 260 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.hook.configure") 261 262 // Verify that profile snap.samba.hook.configure was removed. 263 _, err := os.Stat(profile + ".src") 264 c.Check(os.IsNotExist(err), Equals, true) 265 s.RemoveSnap(c, snapInfo) 266 } 267 } 268 269 func (s *backendSuite) TestRealDefaultTemplateIsNormallyUsed(c *C) { 270 snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil) 271 // NOTE: we don't call seccomp.MockTemplate() 272 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 273 c.Assert(err, IsNil) 274 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 275 data, err := ioutil.ReadFile(profile + ".src") 276 c.Assert(err, IsNil) 277 for _, line := range []string{ 278 // NOTE: a few randomly picked lines from the real profile. Comments 279 // and empty lines are avoided as those can be discarded in the future. 280 "# - create_module, init_module, finit_module, delete_module (kernel modules)\n", 281 "open\n", 282 "getuid\n", 283 "setresuid\n", // this is not random 284 } { 285 c.Assert(string(data), testutil.Contains, line) 286 } 287 } 288 289 type combineSnippetsScenario struct { 290 opts interfaces.ConfinementOptions 291 snippet string 292 content string 293 } 294 295 var combineSnippetsScenarios = []combineSnippetsScenario{{ 296 opts: interfaces.ConfinementOptions{}, 297 content: "default\n", 298 }, { 299 opts: interfaces.ConfinementOptions{}, 300 snippet: "snippet", 301 content: "default\nsnippet\n", 302 }, { 303 opts: interfaces.ConfinementOptions{DevMode: true}, 304 content: "@complain\ndefault\n", 305 }, { 306 opts: interfaces.ConfinementOptions{DevMode: true}, 307 snippet: "snippet", 308 content: "@complain\ndefault\nsnippet\n", 309 }, { 310 opts: interfaces.ConfinementOptions{Classic: true}, 311 snippet: "snippet", 312 content: "@unrestricted\ndefault\nsnippet\n", 313 }, { 314 opts: interfaces.ConfinementOptions{Classic: true, JailMode: true}, 315 snippet: "snippet", 316 content: "default\nsnippet\n", 317 }} 318 319 func (s *backendSuite) TestCombineSnippets(c *C) { 320 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 321 defer restore() 322 restore = seccomp_sandbox.MockActions([]string{"log"}) 323 defer restore() 324 restore = seccomp.MockRequiresSocketcall(func(string) bool { return false }) 325 defer restore() 326 327 // NOTE: replace the real template with a shorter variant 328 restore = seccomp.MockTemplate([]byte("default\n")) 329 defer restore() 330 for _, scenario := range combineSnippetsScenarios { 331 s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error { 332 if scenario.snippet != "" { 333 spec.AddSnippet(scenario.snippet) 334 } 335 return nil 336 } 337 338 snapInfo := s.InstallSnap(c, scenario.opts, "", ifacetest.SambaYamlV1, 0) 339 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 340 c.Check(profile+".src", testutil.FileEquals, s.profileHeader+scenario.content) 341 stat, err := os.Stat(profile + ".src") 342 c.Assert(err, IsNil) 343 c.Check(stat.Mode(), Equals, os.FileMode(0644)) 344 s.RemoveSnap(c, snapInfo) 345 } 346 } 347 348 const snapYaml = ` 349 name: foo 350 version: 1 351 developer: acme 352 apps: 353 foo: 354 slots: [iface, iface2] 355 ` 356 357 // Ensure that combined snippets are sorted 358 func (s *backendSuite) TestCombineSnippetsOrdering(c *C) { 359 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 360 defer restore() 361 restore = seccomp.MockRequiresSocketcall(func(string) bool { return false }) 362 defer restore() 363 364 // NOTE: replace the real template with a shorter variant 365 restore = seccomp.MockTemplate([]byte("default\n")) 366 defer restore() 367 368 iface2 := &ifacetest.TestInterface{InterfaceName: "iface2"} 369 s.Repo.AddInterface(iface2) 370 371 s.Iface.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error { 372 spec.AddSnippet("zzz") 373 return nil 374 } 375 iface2.SecCompPermanentSlotCallback = func(spec *seccomp.Specification, slot *snap.SlotInfo) error { 376 spec.AddSnippet("aaa") 377 return nil 378 } 379 380 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", snapYaml, 0) 381 profile := filepath.Join(dirs.SnapSeccompDir, "snap.foo.foo") 382 c.Check(profile+".src", testutil.FileEquals, s.profileHeader+"default\naaa\nzzz\n") 383 stat, err := os.Stat(profile + ".src") 384 c.Assert(err, IsNil) 385 c.Check(stat.Mode(), Equals, os.FileMode(0644)) 386 } 387 388 func (s *backendSuite) TestBindIsAddedForNonFullApparmorSystems(c *C) { 389 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Partial) 390 defer restore() 391 392 snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil) 393 // NOTE: we don't call seccomp.MockTemplate() 394 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 395 c.Assert(err, IsNil) 396 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 397 c.Assert(profile+".src", testutil.FileContains, "# Add bind() for systems with only Seccomp enabled to workaround\n# LP #1644573\nbind\n") 398 } 399 400 func (s *backendSuite) TestSocketcallIsAddedWhenRequired(c *C) { 401 restore := seccomp.MockRequiresSocketcall(func(string) bool { return true }) 402 defer restore() 403 404 snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil) 405 // NOTE: we don't call seccomp.MockTemplate() 406 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 407 c.Assert(err, IsNil) 408 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 409 c.Assert(profile+".src", testutil.FileContains, "\nsocketcall\n") 410 } 411 412 func (s *backendSuite) TestSocketcallIsNotAddedWhenNotRequired(c *C) { 413 restore := seccomp.MockRequiresSocketcall(func(string) bool { return false }) 414 defer restore() 415 416 snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil) 417 // NOTE: we don't call seccomp.MockTemplate() 418 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 419 c.Assert(err, IsNil) 420 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 421 c.Assert(profile+".src", Not(testutil.FileContains), "\nsocketcall\n") 422 } 423 424 const ClassicYamlV1 = ` 425 name: test-classic 426 version: 1 427 developer: acme 428 confinement: classic 429 apps: 430 sh: 431 ` 432 433 func (s *backendSuite) TestSystemKeyRetLogSupported(c *C) { 434 restore := seccomp_sandbox.MockActions([]string{"allow", "errno", "kill", "log", "trace", "trap"}) 435 defer restore() 436 437 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: true}, "", ifacetest.SambaYamlV1, 0) 438 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 439 c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n") 440 s.RemoveSnap(c, snapInfo) 441 442 snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: false}, "", ifacetest.SambaYamlV1, 0) 443 profile = filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 444 c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n") 445 s.RemoveSnap(c, snapInfo) 446 447 snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{Classic: true}, "", ClassicYamlV1, 0) 448 profile = filepath.Join(dirs.SnapSeccompDir, "snap.test-classic.sh") 449 c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n") 450 s.RemoveSnap(c, snapInfo) 451 } 452 453 func (s *backendSuite) TestSystemKeyRetLogUnsupported(c *C) { 454 restore := seccomp_sandbox.MockActions([]string{"allow", "errno", "kill", "trace", "trap"}) 455 defer restore() 456 457 snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: true}, "", ifacetest.SambaYamlV1, 0) 458 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 459 c.Assert(profile+".src", testutil.FileContains, "# complain mode logging unavailable\n") 460 s.RemoveSnap(c, snapInfo) 461 462 snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{DevMode: false}, "", ifacetest.SambaYamlV1, 0) 463 profile = filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 464 c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n") 465 s.RemoveSnap(c, snapInfo) 466 467 snapInfo = s.InstallSnap(c, interfaces.ConfinementOptions{Classic: true}, "", ClassicYamlV1, 0) 468 profile = filepath.Join(dirs.SnapSeccompDir, "snap.test-classic.sh") 469 c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n") 470 s.RemoveSnap(c, snapInfo) 471 } 472 473 func (s *backendSuite) TestSandboxFeatures(c *C) { 474 restore := seccomp.MockKernelFeatures(func() []string { return []string{"foo", "bar"} }) 475 defer restore() 476 477 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "bpf-argument-filtering"}) 478 479 // change version reported by snap-seccomp 480 snapSeccomp := testutil.MockLockedCommand(c, filepath.Join(dirs.DistroLibExecDir, "snap-seccomp"), ` 481 if [ "$1" = "version-info" ]; then 482 echo "abcdef 1.2.3 1234abcd bpf-actlog" 483 fi`) 484 defer snapSeccomp.Restore() 485 486 // reload cached version info 487 err := s.Backend.Initialize(nil) 488 c.Assert(err, IsNil) 489 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "bpf-argument-filtering", "bpf-actlog"}) 490 } 491 492 func (s *backendSuite) TestRequiresSocketcallByNotNeededArch(c *C) { 493 testArchs := []string{"amd64", "armhf", "arm64", "powerpc", "ppc64el", "unknownDefault"} 494 for _, arch := range testArchs { 495 restore := seccomp.MockDpkgKernelArchitecture(func() string { return arch }) 496 defer restore() 497 c.Assert(seccomp.RequiresSocketcall(""), Equals, false) 498 } 499 } 500 501 func (s *backendSuite) TestRequiresSocketcallForceByArch(c *C) { 502 testArchs := []string{"sparc", "sparc64"} 503 for _, arch := range testArchs { 504 restore := seccomp.MockDpkgKernelArchitecture(func() string { return arch }) 505 defer restore() 506 c.Assert(seccomp.RequiresSocketcall(""), Equals, true) 507 } 508 } 509 510 func (s *backendSuite) TestRequiresSocketcallForcedViaUbuntuRelease(c *C) { 511 // specify "core18" with 4.4 kernel so as not to influence the release 512 // check. 513 base := "core18" 514 restore := osutil.MockKernelVersion("4.4") 515 defer restore() 516 517 tests := []struct { 518 distro string 519 arch string 520 release string 521 needsSocketcall bool 522 }{ 523 // with core18 as base and 4.4 kernel, we only require 524 // socketcall on i386/s390 525 {"ubuntu", "i386", "14.04", true}, 526 {"ubuntu", "s390x", "14.04", true}, 527 {"ubuntu", "other", "14.04", false}, 528 529 // releases after 14.04 aren't forced 530 {"ubuntu", "i386", "other", false}, 531 {"ubuntu", "s390x", "other", false}, 532 {"ubuntu", "other", "other", false}, 533 534 // other distros aren't forced 535 {"other", "i386", "14.04", false}, 536 {"other", "s390x", "14.04", false}, 537 {"other", "other", "14.04", false}, 538 {"other", "i386", "other", false}, 539 {"other", "s390x", "other", false}, 540 {"other", "other", "other", false}, 541 } 542 543 for _, t := range tests { 544 restore = seccomp.MockReleaseInfoId(t.distro) 545 defer restore() 546 restore = seccomp.MockDpkgKernelArchitecture(func() string { return t.arch }) 547 defer restore() 548 restore = seccomp.MockReleaseInfoVersionId(t.release) 549 defer restore() 550 551 c.Assert(seccomp.RequiresSocketcall(base), Equals, t.needsSocketcall) 552 } 553 } 554 555 func (s *backendSuite) TestRequiresSocketcallForcedViaKernelVersion(c *C) { 556 // specify "core18" with non-ubuntu so as not to influence the kernel 557 // check. 558 base := "core18" 559 560 restore := seccomp.MockReleaseInfoId("other") 561 defer restore() 562 563 tests := []struct { 564 arch string 565 version string 566 needsSocketcall bool 567 }{ 568 // i386 needs socketcall on <= 4.2 kernels 569 {"i386", "4.2", true}, 570 {"i386", "4.3", false}, 571 {"i386", "4.4", false}, 572 573 // s390x needs socketcall on <= 4.2 kernels 574 {"s390x", "4.2", true}, 575 {"s390x", "4.3", false}, 576 {"s390x", "4.4", false}, 577 578 // other architectures don't require it 579 {"other", "4.2", false}, 580 {"other", "4.3", false}, 581 {"other", "4.4", false}, 582 } 583 584 for _, t := range tests { 585 restore := seccomp.MockDpkgKernelArchitecture(func() string { return t.arch }) 586 defer restore() 587 restore = osutil.MockKernelVersion(t.version) 588 defer restore() 589 590 // specify "core18" here so as not to influence the 591 // kernel check. 592 c.Assert(seccomp.RequiresSocketcall(base), Equals, t.needsSocketcall) 593 } 594 } 595 596 func (s *backendSuite) TestRequiresSocketcallForcedViaBaseSnap(c *C) { 597 // Mock up as non-Ubuntu, i386 with new enough kernel so the base snap 598 // check is reached 599 restore := seccomp.MockReleaseInfoId("other") 600 defer restore() 601 restore = seccomp.MockDpkgKernelArchitecture(func() string { return "i386" }) 602 defer restore() 603 restore = osutil.MockKernelVersion("4.3") 604 defer restore() 605 606 testBases := []string{"", "core", "core16"} 607 for _, baseSnap := range testBases { 608 c.Assert(seccomp.RequiresSocketcall(baseSnap), Equals, true) 609 } 610 } 611 612 func (s *backendSuite) TestRequiresSocketcallNotForcedViaBaseSnap(c *C) { 613 // Mock up as non-Ubuntu, i386 with new enough kernel so the base snap 614 // check is reached 615 restore := seccomp.MockReleaseInfoId("other") 616 defer restore() 617 restore = seccomp.MockDpkgKernelArchitecture(func() string { return "i386" }) 618 defer restore() 619 restore = osutil.MockKernelVersion("4.3") 620 defer restore() 621 622 testBases := []string{"bare", "core18", "fedora-core"} 623 for _, baseSnap := range testBases { 624 c.Assert(seccomp.RequiresSocketcall(baseSnap), Equals, false) 625 } 626 } 627 628 func (s *backendSuite) TestRebuildsWithVersionInfoWhenNeeded(c *C) { 629 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 630 defer restore() 631 restore = seccomp_sandbox.MockActions([]string{"log"}) 632 defer restore() 633 restore = seccomp.MockRequiresSocketcall(func(string) bool { return false }) 634 defer restore() 635 636 // NOTE: replace the real template with a shorter variant 637 restore = seccomp.MockTemplate([]byte("\ndefault\n")) 638 defer restore() 639 640 profile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 641 642 snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1, nil) 643 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 644 c.Assert(err, IsNil) 645 c.Check(profile+".src", testutil.FileEquals, s.profileHeader+"\ndefault\n") 646 647 c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{ 648 {"snap-seccomp", "compile", profile + ".src", profile + ".bin"}, 649 }) 650 651 // unchanged snap-seccomp version will not trigger a rebuild 652 err = s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 653 c.Assert(err, IsNil) 654 655 // compilation from this first Setup() 656 c.Check(s.snapSeccomp.Calls(), HasLen, 1) 657 658 // change version reported by snap-seccomp 659 snapSeccomp := testutil.MockLockedCommand(c, filepath.Join(dirs.DistroLibExecDir, "snap-seccomp"), ` 660 if [ "$1" = "version-info" ]; then 661 echo "abcdef 2.3.3 2345abcd -" 662 fi`) 663 defer snapSeccomp.Restore() 664 updatedProfileHeader := `# snap-seccomp version information: 665 # abcdef 2.3.3 2345abcd - 666 ` 667 // reload cached version info 668 err = s.Backend.Initialize(nil) 669 c.Assert(err, IsNil) 670 671 c.Check(s.snapSeccomp.Calls(), HasLen, 2) 672 c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{ 673 // compilation from first Setup() 674 {"snap-seccomp", "compile", profile + ".src", profile + ".bin"}, 675 // initialization with new version 676 {"snap-seccomp", "version-info"}, 677 }) 678 679 // the profile should be rebuilt now 680 err = s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 681 c.Assert(err, IsNil) 682 c.Check(profile+".src", testutil.FileEquals, updatedProfileHeader+"\ndefault\n") 683 684 c.Check(s.snapSeccomp.Calls(), HasLen, 3) 685 c.Check(s.snapSeccomp.Calls(), DeepEquals, [][]string{ 686 // compilation from first Setup() 687 {"snap-seccomp", "compile", profile + ".src", profile + ".bin"}, 688 // initialization with new version 689 {"snap-seccomp", "version-info"}, 690 // compilation of profiles with new compiler version 691 {"snap-seccomp", "compile", profile + ".src", profile + ".bin"}, 692 }) 693 } 694 695 func (s *backendSuite) TestInitializationDuringBootstrap(c *C) { 696 // undo what was done in test set-up 697 s.snapSeccomp.Restore() 698 os.Remove(s.snapSeccomp.Exe()) 699 700 // during bootstrap, before seeding, snapd/core snap is mounted at some 701 // random location under /tmp 702 tmpDir := c.MkDir() 703 restore := snapdtool.MockOsReadlink(func(string) (string, error) { 704 return filepath.Join(tmpDir, "usr/lib/snapd/snapd"), nil 705 }) 706 defer restore() 707 708 // ensure we have a mocked snap-seccomp on core 709 snapSeccompInMountedPath := filepath.Join(tmpDir, "usr/lib/snapd/snap-seccomp") 710 err := os.MkdirAll(filepath.Dir(snapSeccompInMountedPath), 0755) 711 c.Assert(err, IsNil) 712 snapSeccompInMounted := testutil.MockLockedCommand(c, snapSeccompInMountedPath, `if [ "$1" = "version-info" ]; then 713 echo "2345cdef 2.3.4 2345cdef -" 714 fi`) 715 defer snapSeccompInMounted.Restore() 716 717 // rerun initialization 718 err = s.Backend.Initialize(nil) 719 c.Assert(err, IsNil) 720 721 // ensure the snap-seccomp from the regular path was *not* used 722 c.Check(s.snapSeccomp.Calls(), HasLen, 0) 723 // the one from mounted snap was used 724 c.Check(snapSeccompInMounted.Calls(), DeepEquals, [][]string{ 725 {"snap-seccomp", "version-info"}, 726 }) 727 728 sb, ok := s.Backend.(*seccomp.Backend) 729 c.Assert(ok, Equals, true) 730 c.Check(sb.VersionInfo(), Equals, seccomp_sandbox.VersionInfo("2345cdef 2.3.4 2345cdef -")) 731 } 732 733 func (s *backendSuite) TestCompilerInitUnhappy(c *C) { 734 restore := seccomp.MockSeccompCompilerLookup(func(name string) (string, error) { 735 c.Check(name, Equals, "snap-seccomp") 736 return "", errors.New("failed") 737 }) 738 defer restore() 739 err := s.Backend.Initialize(nil) 740 c.Assert(err, ErrorMatches, "cannot initialize seccomp profile compiler: failed") 741 } 742 743 func (s *backendSuite) TestSystemUsernamesPolicy(c *C) { 744 snapYaml := ` 745 name: app 746 version: 0.1 747 system-usernames: 748 testid: shared 749 testid2: shared 750 apps: 751 cmd: 752 ` 753 snapInfo := snaptest.MockInfo(c, snapYaml, nil) 754 // NOTE: we don't call seccomp.MockTemplate() 755 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 756 c.Assert(err, IsNil) 757 // NOTE: we don't call seccomp.MockTemplate() 758 profile := filepath.Join(dirs.SnapSeccompDir, "snap.app.cmd") 759 data, err := ioutil.ReadFile(profile + ".src") 760 c.Assert(err, IsNil) 761 for _, line := range []string{ 762 // NOTE: a few randomly picked lines from the real 763 // profile. Comments and empty lines are avoided as 764 // those can be discarded in the future. 765 "\n# - create_module, init_module, finit_module, delete_module (kernel modules)\n", 766 "\nopen\n", 767 "\ngetuid\n", 768 "\nsetgroups 0 -\n", 769 // and a few randomly picked lines from root syscalls 770 // with extra \n checks to ensure we have the right 771 // "paragraphs" in the generated output 772 "\n\n# allow setresgid to root\n", 773 "\n# allow setresuid to root\n", 774 "\nsetresuid u:root u:root u:root\n", 775 // and a few randomly picked lines from global id syscalls 776 "\n\n# allow setresgid to testid\n", 777 "\n\n# allow setresuid to testid\n", 778 "\nsetresuid -1 u:testid -1\n", 779 // also for the second user 780 "\n\n# allow setresgid to testid2\n", 781 "\n# allow setresuid to testid2\n", 782 "\nsetresuid -1 u:testid2 -1\n", 783 } { 784 c.Assert(string(data), testutil.Contains, line) 785 } 786 787 // make sure the bare syscalls aren't present 788 c.Assert(string(data), Not(testutil.Contains), "setresuid\n") 789 } 790 791 func (s *backendSuite) TestNoSystemUsernamesPolicy(c *C) { 792 snapYaml := ` 793 name: app 794 version: 0.1 795 apps: 796 cmd: 797 ` 798 snapInfo := snaptest.MockInfo(c, snapYaml, nil) 799 // NOTE: we don't call seccomp.MockTemplate() 800 err := s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 801 c.Assert(err, IsNil) 802 // NOTE: we don't call seccomp.MockTemplate() 803 profile := filepath.Join(dirs.SnapSeccompDir, "snap.app.cmd") 804 data, err := ioutil.ReadFile(profile + ".src") 805 c.Assert(err, IsNil) 806 for _, line := range []string{ 807 // and a few randomly picked lines from root syscalls 808 "# allow setresgid to root\n", 809 "# allow setresuid to root\n", 810 "setresuid u:root u:root u:root\n", 811 // and a few randomly picked lines from global id syscalls 812 "# allow setresgid to testid\n", 813 "# allow setresuid to testid\n", 814 "setresuid -1 u:testid -1\n", 815 } { 816 c.Assert(string(data), Not(testutil.Contains), line) 817 } 818 819 // make sure the bare syscalls are present 820 c.Assert(string(data), testutil.Contains, "setresuid\n") 821 } 822 823 func (s *backendSuite) TestCleanupWhenOneFailsParallel(c *C) { 824 restore := apparmor_sandbox.MockLevel(apparmor_sandbox.Full) 825 defer restore() 826 restore = seccomp_sandbox.MockActions([]string{"log"}) 827 defer restore() 828 restore = seccomp.MockRequiresSocketcall(func(string) bool { return false }) 829 defer restore() 830 831 // NOTE: replace the real template with a shorter variant 832 restore = seccomp.MockTemplate([]byte("\ndefault\n")) 833 defer restore() 834 835 snapSeccomp := testutil.MockLockedCommand(c, filepath.Join(dirs.DistroLibExecDir, "snap-seccomp"), 836 ` 837 if [ "$1" = "version-info" ]; then 838 echo "2345cdef 2.3.4 2345cdef -" 839 elif [ "$1" = "compile" ] && [ "${2//nmbd}" != "$2" ]; then 840 echo "mocked failure" 841 exit 1 842 fi 843 `) 844 defer snapSeccomp.Restore() 845 846 // rerun initialization 847 err := s.Backend.Initialize(nil) 848 c.Assert(err, IsNil) 849 850 smbdProfile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.smbd") 851 nmbdProfile := filepath.Join(dirs.SnapSeccompDir, "snap.samba.nmbd") 852 853 snapInfo := snaptest.MockInfo(c, ifacetest.SambaYamlV1WithNmbd, nil) 854 err = s.Backend.Setup(snapInfo, interfaces.ConfinementOptions{}, s.Repo, s.meas) 855 c.Assert(err, ErrorMatches, "cannot compile .*nmbd.src: mocked failure") 856 for _, profile := range []string{smbdProfile, nmbdProfile} { 857 c.Check(profile+".bin", testutil.FileAbsent) 858 } 859 860 // 2 compile calls + 1 version-info 861 c.Check(snapSeccomp.Calls(), HasLen, 3) 862 seen := make(map[string]bool, 2) 863 for _, call := range snapSeccomp.Calls() { 864 if len(call) == 2 && call[1] == "version-info" { 865 continue 866 } 867 c.Assert(call, HasLen, 4) 868 seen[call[2]] = true 869 } 870 c.Check(seen, DeepEquals, map[string]bool{ 871 nmbdProfile + ".src": true, 872 smbdProfile + ".src": true, 873 }) 874 875 } 876 877 type mockedSyncedCompiler struct { 878 lock sync.Mutex 879 profiles []string 880 } 881 882 func (m *mockedSyncedCompiler) Compile(in, out string) error { 883 m.lock.Lock() 884 m.profiles = append(m.profiles, filepath.Base(in)) 885 m.lock.Unlock() 886 887 f, err := os.Create(out) 888 if err != nil { 889 return err 890 } 891 defer f.Close() 892 fmt.Fprintf(f, "done %s", filepath.Base(out)) 893 return nil 894 } 895 896 func (m *mockedSyncedCompiler) VersionInfo() (seccomp_sandbox.VersionInfo, error) { 897 return "", nil 898 } 899 900 func (s *backendSuite) TestParallelCompileHappy(c *C) { 901 cpus := runtime.NumCPU() 902 903 m := mockedSyncedCompiler{} 904 profiles := make([]string, cpus*3) 905 for i := range profiles { 906 profiles[i] = fmt.Sprintf("profile-%03d", i) 907 } 908 err := seccomp.ParallelCompile(&m, profiles) 909 c.Assert(err, IsNil) 910 911 sort.Strings(m.profiles) 912 c.Assert(m.profiles, DeepEquals, profiles) 913 914 for _, p := range profiles { 915 c.Check(filepath.Join(dirs.SnapSeccompDir, p+".bin"), testutil.FileEquals, "done "+p+".bin") 916 } 917 } 918 919 type mockedSyncedFailingCompiler struct { 920 mockedSyncedCompiler 921 whichFail []string 922 } 923 924 func (m *mockedSyncedFailingCompiler) Compile(in, out string) error { 925 if b := filepath.Base(out); strutil.ListContains(m.whichFail, b) { 926 return fmt.Errorf("failed %v", b) 927 } 928 return m.mockedSyncedCompiler.Compile(in, out) 929 } 930 931 func (s *backendSuite) TestParallelCompileError(c *C) { 932 err := os.MkdirAll(dirs.SnapSeccompDir, 0755) 933 c.Assert(err, IsNil) 934 // 15 profiles 935 profiles := make([]string, 15) 936 for i := range profiles { 937 profiles[i] = fmt.Sprintf("profile-%03d", i) 938 } 939 m := mockedSyncedFailingCompiler{ 940 // pretend compilation of those 2 fails 941 whichFail: []string{"profile-005.bin", "profile-009.bin"}, 942 } 943 err = seccomp.ParallelCompile(&m, profiles) 944 c.Assert(err, ErrorMatches, "cannot compile .*/bpf/profile-00[59]: failed profile-00[59].bin") 945 946 // make sure all compiled profiles were removed 947 d, err := os.Open(dirs.SnapSeccompDir) 948 c.Assert(err, IsNil) 949 names, err := d.Readdirnames(-1) 950 c.Assert(err, IsNil) 951 // only global profile exists 952 c.Assert(names, DeepEquals, []string{"global.bin"}) 953 } 954 955 func (s *backendSuite) TestParallelCompileRemovesFirst(c *C) { 956 err := os.MkdirAll(dirs.SnapSeccompDir, 0755) 957 c.Assert(err, IsNil) 958 err = ioutil.WriteFile(filepath.Join(dirs.SnapSeccompDir, "profile-001.bin"), nil, 0755) 959 c.Assert(err, IsNil) 960 961 // make profiles directory non-accessible 962 err = os.Chmod(dirs.SnapSeccompDir, 0000) 963 c.Assert(err, IsNil) 964 965 defer os.Chmod(dirs.SnapSeccompDir, 0755) 966 967 m := mockedSyncedCompiler{} 968 err = seccomp.ParallelCompile(&m, []string{"profile-001"}) 969 c.Assert(err, ErrorMatches, "remove .*/profile-001.bin: permission denied") 970 }