github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_run_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 main_test 21 22 import ( 23 "errors" 24 "fmt" 25 "net/http" 26 "os" 27 "os/user" 28 "path/filepath" 29 "strings" 30 "time" 31 32 "gopkg.in/check.v1" 33 34 snaprun "github.com/snapcore/snapd/cmd/snap" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/logger" 37 "github.com/snapcore/snapd/osutil" 38 "github.com/snapcore/snapd/sandbox/cgroup" 39 "github.com/snapcore/snapd/sandbox/selinux" 40 "github.com/snapcore/snapd/snap" 41 "github.com/snapcore/snapd/snap/snaptest" 42 "github.com/snapcore/snapd/testutil" 43 "github.com/snapcore/snapd/x11" 44 ) 45 46 var mockYaml = []byte(`name: snapname 47 version: 1.0 48 apps: 49 app: 50 command: run-app 51 svc: 52 command: run-svc 53 daemon: simple 54 hooks: 55 configure: 56 `) 57 58 var mockYamlBaseNone1 = []byte(`name: snapname1 59 version: 1.0 60 base: none 61 apps: 62 app: 63 command: run-app 64 `) 65 66 var mockYamlBaseNone2 = []byte(`name: snapname2 67 version: 1.0 68 base: none 69 hooks: 70 configure: 71 `) 72 73 type RunSuite struct { 74 fakeHome string 75 BaseSnapSuite 76 } 77 78 var _ = check.Suite(&RunSuite{}) 79 80 func (s *RunSuite) SetUpTest(c *check.C) { 81 s.BaseSnapSuite.SetUpTest(c) 82 s.fakeHome = c.MkDir() 83 84 u, err := user.Current() 85 c.Assert(err, check.IsNil) 86 s.AddCleanup(snaprun.MockUserCurrent(func() (*user.User, error) { 87 return &user.User{Uid: u.Uid, HomeDir: s.fakeHome}, nil 88 })) 89 s.AddCleanup(snaprun.MockCreateTransientScopeForTracking(func(string, *cgroup.TrackingOptions) error { 90 return nil 91 })) 92 } 93 94 func (s *RunSuite) TestInvalidParameters(c *check.C) { 95 invalidParameters := []string{"run", "--hook=configure", "--command=command-name", "--", "snap-name"} 96 _, err := snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) 97 c.Check(err, check.ErrorMatches, ".*you can only use one of --hook, --command, and --timer.*") 98 99 invalidParameters = []string{"run", "--hook=configure", "--timer=10:00-12:00", "--", "snap-name"} 100 _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) 101 c.Check(err, check.ErrorMatches, ".*you can only use one of --hook, --command, and --timer.*") 102 103 invalidParameters = []string{"run", "--command=command-name", "--timer=10:00-12:00", "--", "snap-name"} 104 _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) 105 c.Check(err, check.ErrorMatches, ".*you can only use one of --hook, --command, and --timer.*") 106 107 invalidParameters = []string{"run", "-r=1", "--command=command-name", "--", "snap-name"} 108 _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) 109 c.Check(err, check.ErrorMatches, ".*-r can only be used with --hook.*") 110 111 invalidParameters = []string{"run", "-r=1", "--", "snap-name"} 112 _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) 113 c.Check(err, check.ErrorMatches, ".*-r can only be used with --hook.*") 114 115 invalidParameters = []string{"run", "--hook=configure", "--", "foo", "bar", "snap-name"} 116 _, err = snaprun.Parser(snaprun.Client()).ParseArgs(invalidParameters) 117 c.Check(err, check.ErrorMatches, ".*too many arguments for hook \"configure\": bar.*") 118 } 119 120 func (s *RunSuite) TestRunCmdWithBaseNone(c *check.C) { 121 defer mockSnapConfine(dirs.DistroLibExecDir)() 122 123 // mock installed snap 124 snaptest.MockSnapCurrent(c, string(mockYamlBaseNone1), &snap.SideInfo{ 125 Revision: snap.R("1"), 126 }) 127 snaptest.MockSnapCurrent(c, string(mockYamlBaseNone2), &snap.SideInfo{ 128 Revision: snap.R("1"), 129 }) 130 131 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname1.app", "--arg1", "arg2"}) 132 c.Assert(err, check.ErrorMatches, `cannot run hooks / applications with base \"none\"`) 133 134 _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "--", "snapname2"}) 135 c.Assert(err, check.ErrorMatches, `cannot run hooks / applications with base \"none\"`) 136 } 137 138 func (s *RunSuite) TestSnapRunWhenMissingConfine(c *check.C) { 139 _, r := logger.MockLogger() 140 defer r() 141 142 // mock installed snap 143 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 144 Revision: snap.R("x2"), 145 }) 146 147 // redirect exec 148 var execs [][]string 149 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 150 execs = append(execs, args) 151 return nil 152 }) 153 defer restorer() 154 155 // and run it! 156 // a regular run will fail 157 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 158 c.Assert(err, check.ErrorMatches, `.* your core/snapd package`) 159 // a hook run will not fail 160 _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "--", "snapname"}) 161 c.Assert(err, check.IsNil) 162 163 // but nothing is run ever 164 c.Check(execs, check.IsNil) 165 } 166 167 func (s *RunSuite) TestSnapRunAppIntegration(c *check.C) { 168 defer mockSnapConfine(dirs.DistroLibExecDir)() 169 170 tmpdir := os.Getenv("TMPDIR") 171 if tmpdir == "" { 172 tmpdir = "/var/tmp" 173 os.Setenv("TMPDIR", tmpdir) 174 defer os.Unsetenv("TMPDIR") 175 } 176 177 // mock installed snap 178 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 179 Revision: snap.R("x2"), 180 }) 181 182 // redirect exec 183 execArg0 := "" 184 execArgs := []string{} 185 execEnv := []string{} 186 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 187 execArg0 = arg0 188 execArgs = args 189 execEnv = envv 190 return nil 191 }) 192 defer restorer() 193 194 // and run it! 195 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 196 c.Assert(err, check.IsNil) 197 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 198 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 199 c.Check(execArgs, check.DeepEquals, []string{ 200 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 201 "snap.snapname.app", 202 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 203 "snapname.app", "--arg1", "arg2"}) 204 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 205 c.Check(execEnv, testutil.Contains, fmt.Sprintf("TMPDIR=%s", tmpdir)) 206 } 207 208 func (s *RunSuite) TestSnapRunClassicAppIntegration(c *check.C) { 209 defer mockSnapConfine(dirs.DistroLibExecDir)() 210 211 tmpdir := os.Getenv("TMPDIR") 212 if tmpdir == "" { 213 tmpdir = "/var/tmp" 214 os.Setenv("TMPDIR", tmpdir) 215 defer os.Unsetenv("TMPDIR") 216 } 217 218 // mock installed snap 219 snaptest.MockSnapCurrent(c, string(mockYaml)+"confinement: classic\n", &snap.SideInfo{ 220 Revision: snap.R("x2"), 221 }) 222 223 // redirect exec 224 execArg0 := "" 225 execArgs := []string{} 226 execEnv := []string{} 227 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 228 execArg0 = arg0 229 execArgs = args 230 execEnv = envv 231 return nil 232 }) 233 defer restorer() 234 235 // and run it! 236 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 237 c.Assert(err, check.IsNil) 238 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 239 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 240 c.Check(execArgs, check.DeepEquals, []string{ 241 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), "--classic", 242 "snap.snapname.app", 243 filepath.Join(dirs.DistroLibExecDir, "snap-exec"), 244 "snapname.app", "--arg1", "arg2"}) 245 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 246 c.Check(execEnv, testutil.Contains, fmt.Sprintf("SNAP_SAVED_TMPDIR=%s", tmpdir)) 247 } 248 249 func (s *RunSuite) TestSnapRunClassicAppIntegrationReexecedFromCore(c *check.C) { 250 mountedCorePath := filepath.Join(dirs.SnapMountDir, "core/current") 251 mountedCoreLibExecPath := filepath.Join(mountedCorePath, dirs.CoreLibExecDir) 252 253 defer mockSnapConfine(mountedCoreLibExecPath)() 254 255 // mock installed snap 256 snaptest.MockSnapCurrent(c, string(mockYaml)+"confinement: classic\n", &snap.SideInfo{ 257 Revision: snap.R("x2"), 258 }) 259 260 restore := snaprun.MockOsReadlink(func(name string) (string, error) { 261 // pretend 'snap' is reexeced from 'core' 262 return filepath.Join(mountedCorePath, "usr/bin/snap"), nil 263 }) 264 defer restore() 265 266 execArgs := []string{} 267 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 268 execArgs = args 269 return nil 270 }) 271 defer restorer() 272 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 273 c.Assert(err, check.IsNil) 274 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 275 c.Check(execArgs, check.DeepEquals, []string{ 276 filepath.Join(mountedCoreLibExecPath, "snap-confine"), "--classic", 277 "snap.snapname.app", 278 filepath.Join(mountedCoreLibExecPath, "snap-exec"), 279 "snapname.app", "--arg1", "arg2"}) 280 } 281 282 func (s *RunSuite) TestSnapRunClassicAppIntegrationReexecedFromSnapd(c *check.C) { 283 mountedSnapdPath := filepath.Join(dirs.SnapMountDir, "snapd/current") 284 mountedSnapdLibExecPath := filepath.Join(mountedSnapdPath, dirs.CoreLibExecDir) 285 286 defer mockSnapConfine(mountedSnapdLibExecPath)() 287 288 // mock installed snap 289 snaptest.MockSnapCurrent(c, string(mockYaml)+"confinement: classic\n", &snap.SideInfo{ 290 Revision: snap.R("x2"), 291 }) 292 293 restore := snaprun.MockOsReadlink(func(name string) (string, error) { 294 // pretend 'snap' is reexeced from 'core' 295 return filepath.Join(mountedSnapdPath, "usr/bin/snap"), nil 296 }) 297 defer restore() 298 299 execArgs := []string{} 300 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 301 execArgs = args 302 return nil 303 }) 304 defer restorer() 305 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 306 c.Assert(err, check.IsNil) 307 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 308 c.Check(execArgs, check.DeepEquals, []string{ 309 filepath.Join(mountedSnapdLibExecPath, "snap-confine"), "--classic", 310 "snap.snapname.app", 311 filepath.Join(mountedSnapdLibExecPath, "snap-exec"), 312 "snapname.app", "--arg1", "arg2"}) 313 } 314 315 func (s *RunSuite) TestSnapRunAppWithCommandIntegration(c *check.C) { 316 defer mockSnapConfine(dirs.DistroLibExecDir)() 317 318 // mock installed snap 319 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 320 Revision: snap.R(42), 321 }) 322 323 // redirect exec 324 execArg0 := "" 325 execArgs := []string{} 326 execEnv := []string{} 327 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 328 execArg0 = arg0 329 execArgs = args 330 execEnv = envv 331 return nil 332 }) 333 defer restorer() 334 335 // and run it! 336 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--command=my-command", "--", "snapname.app", "arg1", "arg2"}) 337 c.Assert(err, check.IsNil) 338 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 339 c.Check(execArgs, check.DeepEquals, []string{ 340 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 341 "snap.snapname.app", 342 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 343 "--command=my-command", "snapname.app", "arg1", "arg2"}) 344 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") 345 } 346 347 func (s *RunSuite) TestSnapRunCreateDataDirs(c *check.C) { 348 info, err := snap.InfoFromSnapYaml(mockYaml) 349 c.Assert(err, check.IsNil) 350 info.SideInfo.Revision = snap.R(42) 351 352 err = snaprun.CreateUserDataDirs(info) 353 c.Assert(err, check.IsNil) 354 c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname/42")), check.Equals, true) 355 c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname/common")), check.Equals, true) 356 } 357 358 func (s *RunSuite) TestParallelInstanceSnapRunCreateDataDirs(c *check.C) { 359 info, err := snap.InfoFromSnapYaml(mockYaml) 360 c.Assert(err, check.IsNil) 361 info.SideInfo.Revision = snap.R(42) 362 info.InstanceKey = "foo" 363 364 err = snaprun.CreateUserDataDirs(info) 365 c.Assert(err, check.IsNil) 366 c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname_foo/42")), check.Equals, true) 367 c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname_foo/common")), check.Equals, true) 368 // mount point for snap instance mapping has been created 369 c.Check(osutil.FileExists(filepath.Join(s.fakeHome, "/snap/snapname")), check.Equals, true) 370 // and it's empty inside 371 m, err := filepath.Glob(filepath.Join(s.fakeHome, "/snap/snapname/*")) 372 c.Assert(err, check.IsNil) 373 c.Assert(m, check.HasLen, 0) 374 } 375 376 func (s *RunSuite) TestSnapRunHookIntegration(c *check.C) { 377 defer mockSnapConfine(dirs.DistroLibExecDir)() 378 379 // mock installed snap 380 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 381 Revision: snap.R(42), 382 }) 383 384 // redirect exec 385 execArg0 := "" 386 execArgs := []string{} 387 execEnv := []string{} 388 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 389 execArg0 = arg0 390 execArgs = args 391 execEnv = envv 392 return nil 393 }) 394 defer restorer() 395 396 // Run a hook from the active revision 397 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "--", "snapname"}) 398 c.Assert(err, check.IsNil) 399 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 400 c.Check(execArgs, check.DeepEquals, []string{ 401 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 402 "snap.snapname.hook.configure", 403 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 404 "--hook=configure", "snapname"}) 405 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") 406 } 407 408 func (s *RunSuite) TestSnapRunHookUnsetRevisionIntegration(c *check.C) { 409 defer mockSnapConfine(dirs.DistroLibExecDir)() 410 411 // mock installed snap 412 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 413 Revision: snap.R(42), 414 }) 415 416 // redirect exec 417 execArg0 := "" 418 execArgs := []string{} 419 execEnv := []string{} 420 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 421 execArg0 = arg0 422 execArgs = args 423 execEnv = envv 424 return nil 425 }) 426 defer restorer() 427 428 // Specifically pass "unset" which would use the active version. 429 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=unset", "--", "snapname"}) 430 c.Assert(err, check.IsNil) 431 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 432 c.Check(execArgs, check.DeepEquals, []string{ 433 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 434 "snap.snapname.hook.configure", 435 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 436 "--hook=configure", "snapname"}) 437 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") 438 } 439 440 func (s *RunSuite) TestSnapRunHookSpecificRevisionIntegration(c *check.C) { 441 defer mockSnapConfine(dirs.DistroLibExecDir)() 442 443 // mock installed snap 444 // Create both revisions 41 and 42 445 snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ 446 Revision: snap.R(41), 447 }) 448 snaptest.MockSnap(c, string(mockYaml), &snap.SideInfo{ 449 Revision: snap.R(42), 450 }) 451 452 // redirect exec 453 execArg0 := "" 454 execArgs := []string{} 455 execEnv := []string{} 456 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 457 execArg0 = arg0 458 execArgs = args 459 execEnv = envv 460 return nil 461 }) 462 defer restorer() 463 464 // Run a hook on revision 41 465 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=41", "--", "snapname"}) 466 c.Assert(err, check.IsNil) 467 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 468 c.Check(execArgs, check.DeepEquals, []string{ 469 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 470 "snap.snapname.hook.configure", 471 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 472 "--hook=configure", "snapname"}) 473 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=41") 474 } 475 476 func (s *RunSuite) TestSnapRunHookMissingRevisionIntegration(c *check.C) { 477 // Only create revision 42 478 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 479 Revision: snap.R(42), 480 }) 481 482 // redirect exec 483 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 484 return nil 485 }) 486 defer restorer() 487 488 // Attempt to run a hook on revision 41, which doesn't exist 489 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=41", "--", "snapname"}) 490 c.Assert(err, check.NotNil) 491 c.Check(err, check.ErrorMatches, "cannot find .*") 492 } 493 494 func (s *RunSuite) TestSnapRunHookInvalidRevisionIntegration(c *check.C) { 495 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=configure", "-r=invalid", "--", "snapname"}) 496 c.Assert(err, check.NotNil) 497 c.Check(err, check.ErrorMatches, "invalid snap revision: \"invalid\"") 498 } 499 500 func (s *RunSuite) TestSnapRunHookMissingHookIntegration(c *check.C) { 501 // Only create revision 42 502 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 503 Revision: snap.R(42), 504 }) 505 506 // redirect exec 507 called := false 508 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 509 called = true 510 return nil 511 }) 512 defer restorer() 513 514 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=missing-hook", "--", "snapname"}) 515 c.Assert(err, check.ErrorMatches, `cannot find hook "missing-hook" in "snapname"`) 516 c.Check(called, check.Equals, false) 517 } 518 519 func (s *RunSuite) TestSnapRunErorsForUnknownRunArg(c *check.C) { 520 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--unknown", "--", "snapname.app", "--arg1", "arg2"}) 521 c.Assert(err, check.ErrorMatches, "unknown flag `unknown'") 522 } 523 524 func (s *RunSuite) TestSnapRunErorsForMissingApp(c *check.C) { 525 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--command=shell"}) 526 c.Assert(err, check.ErrorMatches, "need the application to run as argument") 527 } 528 529 func (s *RunSuite) TestSnapRunErorrForUnavailableApp(c *check.C) { 530 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "not-there"}) 531 c.Assert(err, check.ErrorMatches, fmt.Sprintf("cannot find current revision for snap not-there: readlink %s/not-there/current: no such file or directory", dirs.SnapMountDir)) 532 } 533 534 func (s *RunSuite) TestSnapRunSaneEnvironmentHandling(c *check.C) { 535 defer mockSnapConfine(dirs.DistroLibExecDir)() 536 537 // mock installed snap 538 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 539 Revision: snap.R(42), 540 }) 541 542 // redirect exec 543 execEnv := []string{} 544 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 545 execEnv = envv 546 return nil 547 }) 548 defer restorer() 549 550 // set a SNAP{,_*} variable in the environment 551 os.Setenv("SNAP_NAME", "something-else") 552 os.Setenv("SNAP_ARCH", "PDP-7") 553 defer os.Unsetenv("SNAP_NAME") 554 defer os.Unsetenv("SNAP_ARCH") 555 // but unrelated stuff is ok 556 os.Setenv("SNAP_THE_WORLD", "YES") 557 defer os.Unsetenv("SNAP_THE_WORLD") 558 559 // and ensure those SNAP_ vars get overridden 560 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 561 c.Assert(err, check.IsNil) 562 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 563 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") 564 c.Check(execEnv, check.Not(testutil.Contains), "SNAP_NAME=something-else") 565 c.Check(execEnv, check.Not(testutil.Contains), "SNAP_ARCH=PDP-7") 566 c.Check(execEnv, testutil.Contains, "SNAP_THE_WORLD=YES") 567 } 568 569 func (s *RunSuite) TestSnapRunSnapdHelperPath(c *check.C) { 570 _, r := logger.MockLogger() 571 defer r() 572 573 var osReadlinkResult string 574 restore := snaprun.MockOsReadlink(func(name string) (string, error) { 575 return osReadlinkResult, nil 576 }) 577 defer restore() 578 579 tool := "snap-confine" 580 for _, t := range []struct { 581 readlink string 582 expected string 583 }{ 584 { 585 filepath.Join(dirs.SnapMountDir, "core/current/usr/bin/snap"), 586 filepath.Join(dirs.SnapMountDir, "core/current", dirs.CoreLibExecDir, tool), 587 }, 588 { 589 filepath.Join(dirs.SnapMountDir, "snapd/current/usr/bin/snap"), 590 filepath.Join(dirs.SnapMountDir, "snapd/current", dirs.CoreLibExecDir, tool), 591 }, 592 { 593 filepath.Join("/usr/bin/snap"), 594 filepath.Join(dirs.DistroLibExecDir, tool), 595 }, 596 { 597 filepath.Join("/home/foo/ws/snapd/snap"), 598 filepath.Join(dirs.DistroLibExecDir, tool), 599 }, 600 // unexpected case 601 { 602 filepath.Join(dirs.SnapMountDir, "snapd2/current/bin/snap"), 603 filepath.Join(dirs.DistroLibExecDir, tool), 604 }, 605 } { 606 osReadlinkResult = t.readlink 607 toolPath, err := snaprun.SnapdHelperPath(tool) 608 c.Assert(err, check.IsNil) 609 c.Check(toolPath, check.Equals, t.expected) 610 } 611 } 612 613 func (s *RunSuite) TestSnapRunAppIntegrationFromCore(c *check.C) { 614 defer mockSnapConfine(filepath.Join(dirs.SnapMountDir, "core", "111", dirs.CoreLibExecDir))() 615 616 // mock installed snap 617 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 618 Revision: snap.R("x2"), 619 }) 620 621 // pretend to be running from core 622 restorer := snaprun.MockOsReadlink(func(string) (string, error) { 623 return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil 624 }) 625 defer restorer() 626 627 // redirect exec 628 execArg0 := "" 629 execArgs := []string{} 630 execEnv := []string{} 631 restorer = snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 632 execArg0 = arg0 633 execArgs = args 634 execEnv = envv 635 return nil 636 }) 637 defer restorer() 638 639 // and run it! 640 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 641 c.Assert(err, check.IsNil) 642 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 643 c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine")) 644 c.Check(execArgs, check.DeepEquals, []string{ 645 filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine"), 646 "snap.snapname.app", 647 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 648 "snapname.app", "--arg1", "arg2"}) 649 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 650 } 651 652 func (s *RunSuite) TestSnapRunAppIntegrationFromSnapd(c *check.C) { 653 defer mockSnapConfine(filepath.Join(dirs.SnapMountDir, "snapd", "222", dirs.CoreLibExecDir))() 654 655 // mock installed snap 656 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 657 Revision: snap.R("x2"), 658 }) 659 660 // pretend to be running from snapd 661 restorer := snaprun.MockOsReadlink(func(string) (string, error) { 662 return filepath.Join(dirs.SnapMountDir, "snapd/222/usr/bin/snap"), nil 663 }) 664 defer restorer() 665 666 // redirect exec 667 execArg0 := "" 668 execArgs := []string{} 669 execEnv := []string{} 670 restorer = snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 671 execArg0 = arg0 672 execArgs = args 673 execEnv = envv 674 return nil 675 }) 676 defer restorer() 677 678 // and run it! 679 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 680 c.Assert(err, check.IsNil) 681 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 682 c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/snapd/222", dirs.CoreLibExecDir, "snap-confine")) 683 c.Check(execArgs, check.DeepEquals, []string{ 684 filepath.Join(dirs.SnapMountDir, "/snapd/222", dirs.CoreLibExecDir, "snap-confine"), 685 "snap.snapname.app", 686 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 687 "snapname.app", "--arg1", "arg2"}) 688 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 689 } 690 691 func (s *RunSuite) TestSnapRunXauthorityMigration(c *check.C) { 692 defer mockSnapConfine(dirs.DistroLibExecDir)() 693 694 u, err := user.Current() 695 c.Assert(err, check.IsNil) 696 697 // Ensure XDG_RUNTIME_DIR exists for the user we're testing with 698 err = os.MkdirAll(filepath.Join(dirs.XdgRuntimeDirBase, u.Uid), 0700) 699 c.Assert(err, check.IsNil) 700 701 // mock installed snap; happily this also gives us a directory 702 // below /tmp which the Xauthority migration expects. 703 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 704 Revision: snap.R("x2"), 705 }) 706 707 // redirect exec 708 execArg0 := "" 709 execArgs := []string{} 710 execEnv := []string{} 711 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 712 execArg0 = arg0 713 execArgs = args 714 execEnv = envv 715 return nil 716 }) 717 defer restorer() 718 719 xauthPath, err := x11.MockXauthority(2) 720 c.Assert(err, check.IsNil) 721 defer os.Remove(xauthPath) 722 723 defer snaprun.MockGetEnv(func(name string) string { 724 if name == "XAUTHORITY" { 725 return xauthPath 726 } 727 return "" 728 })() 729 730 // and run it! 731 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) 732 c.Assert(err, check.IsNil) 733 c.Assert(rest, check.DeepEquals, []string{"snapname.app"}) 734 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 735 c.Check(execArgs, check.DeepEquals, []string{ 736 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 737 "snap.snapname.app", 738 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 739 "snapname.app"}) 740 741 expectedXauthPath := filepath.Join(dirs.XdgRuntimeDirBase, u.Uid, ".Xauthority") 742 c.Check(execEnv, testutil.Contains, fmt.Sprintf("XAUTHORITY=%s", expectedXauthPath)) 743 744 info, err := os.Stat(expectedXauthPath) 745 c.Assert(err, check.IsNil) 746 c.Assert(info.Mode().Perm(), check.Equals, os.FileMode(0600)) 747 748 err = x11.ValidateXauthorityFile(expectedXauthPath) 749 c.Assert(err, check.IsNil) 750 } 751 752 // build the args for a hypothetical completer 753 func mkCompArgs(compPoint string, argv ...string) []string { 754 out := []string{ 755 "99", // COMP_TYPE 756 "99", // COMP_KEY 757 "", // COMP_POINT 758 "2", // COMP_CWORD 759 " ", // COMP_WORDBREAKS 760 } 761 out[2] = compPoint 762 out = append(out, strings.Join(argv, " ")) 763 out = append(out, argv...) 764 return out 765 } 766 767 func (s *RunSuite) TestAntialiasHappy(c *check.C) { 768 c.Assert(os.MkdirAll(dirs.SnapBinariesDir, 0755), check.IsNil) 769 770 inArgs := mkCompArgs("10", "alias", "alias", "bo-alias") 771 772 // first not so happy because no alias symlink 773 app, outArgs := snaprun.Antialias("alias", inArgs) 774 c.Check(app, check.Equals, "alias") 775 c.Check(outArgs, check.DeepEquals, inArgs) 776 777 c.Assert(os.Symlink("an-app", filepath.Join(dirs.SnapBinariesDir, "alias")), check.IsNil) 778 779 // now really happy 780 app, outArgs = snaprun.Antialias("alias", inArgs) 781 c.Check(app, check.Equals, "an-app") 782 c.Check(outArgs, check.DeepEquals, []string{ 783 "99", // COMP_TYPE (no change) 784 "99", // COMP_KEY (no change) 785 "11", // COMP_POINT (+1 because "an-app" is one longer than "alias") 786 "2", // COMP_CWORD (no change) 787 " ", // COMP_WORDBREAKS (no change) 788 "an-app alias bo-alias", // COMP_LINE (argv[0] changed) 789 "an-app", // argv (arv[0] changed) 790 "alias", 791 "bo-alias", 792 }) 793 } 794 795 func (s *RunSuite) TestAntialiasBailsIfUnhappy(c *check.C) { 796 // alias exists but args are somehow wonky 797 c.Assert(os.MkdirAll(dirs.SnapBinariesDir, 0755), check.IsNil) 798 c.Assert(os.Symlink("an-app", filepath.Join(dirs.SnapBinariesDir, "alias")), check.IsNil) 799 800 // weird1 has COMP_LINE not start with COMP_WORDS[0], argv[0] equal to COMP_WORDS[0] 801 weird1 := mkCompArgs("6", "alias", "") 802 weird1[5] = "xxxxx " 803 // weird2 has COMP_LINE not start with COMP_WORDS[0], argv[0] equal to the first word in COMP_LINE 804 weird2 := mkCompArgs("6", "xxxxx", "") 805 weird2[5] = "alias " 806 807 for desc, inArgs := range map[string][]string{ 808 "nil args": nil, 809 "too-short args": {"alias"}, 810 "COMP_POINT not a number": mkCompArgs("hello", "alias"), 811 "COMP_POINT is inside argv[0]": mkCompArgs("2", "alias", ""), 812 "COMP_POINT is outside argv": mkCompArgs("99", "alias", ""), 813 "COMP_WORDS[0] is not argv[0]": mkCompArgs("10", "not-alias", ""), 814 "mismatch between argv[0], COMP_LINE and COMP_WORDS, #1": weird1, 815 "mismatch between argv[0], COMP_LINE and COMP_WORDS, #2": weird2, 816 } { 817 // antialias leaves args alone if it's too short 818 app, outArgs := snaprun.Antialias("alias", inArgs) 819 c.Check(app, check.Equals, "alias", check.Commentf(desc)) 820 c.Check(outArgs, check.DeepEquals, inArgs, check.Commentf(desc)) 821 } 822 } 823 824 func (s *RunSuite) TestSnapRunAppWithStraceIntegration(c *check.C) { 825 defer mockSnapConfine(dirs.DistroLibExecDir)() 826 827 // mock installed snap 828 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 829 Revision: snap.R("x2"), 830 }) 831 832 // pretend we have sudo and simulate some useful output that would 833 // normally come from strace 834 sudoCmd := testutil.MockCommand(c, "sudo", fmt.Sprintf(` 835 echo "stdout output 1" 836 >&2 echo 'execve("/path/to/snap-confine")' 837 >&2 echo "snap-confine/snap-exec strace stuff" 838 >&2 echo "getuid() = 1000" 839 >&2 echo 'execve("%s/snapName/x2/bin/foo")' 840 >&2 echo "interessting strace output" 841 >&2 echo "and more" 842 echo "stdout output 2" 843 `, dirs.SnapMountDir)) 844 defer sudoCmd.Restore() 845 846 // pretend we have strace 847 straceCmd := testutil.MockCommand(c, "strace", "") 848 defer straceCmd.Restore() 849 850 user, err := user.Current() 851 c.Assert(err, check.IsNil) 852 853 // and run it under strace 854 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--strace", "--", "snapname.app", "--arg1", "arg2"}) 855 c.Assert(err, check.IsNil) 856 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 857 c.Check(sudoCmd.Calls(), check.DeepEquals, [][]string{ 858 { 859 "sudo", "-E", 860 filepath.Join(straceCmd.BinDir(), "strace"), 861 "-u", user.Username, 862 "-f", 863 "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday,nanosleep", 864 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 865 "snap.snapname.app", 866 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 867 "snapname.app", "--arg1", "arg2", 868 }, 869 }) 870 c.Check(s.Stdout(), check.Equals, "stdout output 1\nstdout output 2\n") 871 c.Check(s.Stderr(), check.Equals, fmt.Sprintf("execve(%q)\ninteressting strace output\nand more\n", filepath.Join(dirs.SnapMountDir, "snapName/x2/bin/foo"))) 872 873 s.ResetStdStreams() 874 sudoCmd.ForgetCalls() 875 876 // try again without filtering 877 rest, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--strace=--raw", "--", "snapname.app", "--arg1", "arg2"}) 878 c.Assert(err, check.IsNil) 879 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 880 c.Check(sudoCmd.Calls(), check.DeepEquals, [][]string{ 881 { 882 "sudo", "-E", 883 filepath.Join(straceCmd.BinDir(), "strace"), 884 "-u", user.Username, 885 "-f", 886 "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday,nanosleep", 887 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 888 "snap.snapname.app", 889 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 890 "snapname.app", "--arg1", "arg2", 891 }, 892 }) 893 c.Check(s.Stdout(), check.Equals, "stdout output 1\nstdout output 2\n") 894 expectedFullFmt := `execve("/path/to/snap-confine") 895 snap-confine/snap-exec strace stuff 896 getuid() = 1000 897 execve("%s/snapName/x2/bin/foo") 898 interessting strace output 899 and more 900 ` 901 c.Check(s.Stderr(), check.Equals, fmt.Sprintf(expectedFullFmt, dirs.SnapMountDir)) 902 } 903 904 func (s *RunSuite) TestSnapRunAppWithStraceOptions(c *check.C) { 905 defer mockSnapConfine(dirs.DistroLibExecDir)() 906 907 // mock installed snap 908 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 909 Revision: snap.R("x2"), 910 }) 911 912 // pretend we have sudo 913 sudoCmd := testutil.MockCommand(c, "sudo", "") 914 defer sudoCmd.Restore() 915 916 // pretend we have strace 917 straceCmd := testutil.MockCommand(c, "strace", "") 918 defer straceCmd.Restore() 919 920 user, err := user.Current() 921 c.Assert(err, check.IsNil) 922 923 // and run it under strace 924 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", `--strace=-tt --raw -o "file with spaces"`, "--", "snapname.app", "--arg1", "arg2"}) 925 c.Assert(err, check.IsNil) 926 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 927 c.Check(sudoCmd.Calls(), check.DeepEquals, [][]string{ 928 { 929 "sudo", "-E", 930 filepath.Join(straceCmd.BinDir(), "strace"), 931 "-u", user.Username, 932 "-f", 933 "-e", "!select,pselect6,_newselect,clock_gettime,sigaltstack,gettid,gettimeofday,nanosleep", 934 "-tt", 935 "-o", 936 "file with spaces", 937 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 938 "snap.snapname.app", 939 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 940 "snapname.app", "--arg1", "arg2", 941 }, 942 }) 943 } 944 945 func (s *RunSuite) TestSnapRunShellIntegration(c *check.C) { 946 defer mockSnapConfine(dirs.DistroLibExecDir)() 947 948 // mock installed snap 949 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 950 Revision: snap.R("x2"), 951 }) 952 953 // redirect exec 954 execArg0 := "" 955 execArgs := []string{} 956 execEnv := []string{} 957 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 958 execArg0 = arg0 959 execArgs = args 960 execEnv = envv 961 return nil 962 }) 963 defer restorer() 964 965 // and run it! 966 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--shell", "--", "snapname.app", "--arg1", "arg2"}) 967 c.Assert(err, check.IsNil) 968 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 969 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 970 c.Check(execArgs, check.DeepEquals, []string{ 971 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 972 "snap.snapname.app", 973 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 974 "--command=shell", "snapname.app", "--arg1", "arg2"}) 975 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 976 } 977 978 func (s *RunSuite) TestSnapRunAppTimer(c *check.C) { 979 defer mockSnapConfine(dirs.DistroLibExecDir)() 980 981 // mock installed snap 982 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 983 Revision: snap.R("x2"), 984 }) 985 986 // redirect exec 987 execArg0 := "" 988 execArgs := []string{} 989 execCalled := false 990 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 991 execArg0 = arg0 992 execArgs = args 993 execCalled = true 994 return nil 995 }) 996 defer restorer() 997 998 fakeNow := time.Date(2018, 02, 12, 9, 55, 0, 0, time.Local) 999 restorer = snaprun.MockTimeNow(func() time.Time { 1000 // Monday Feb 12, 9:55 1001 return fakeNow 1002 }) 1003 defer restorer() 1004 1005 // pretend we are outside of timer range 1006 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", `--timer="mon,10:00~12:00,,fri,13:00"`, "--", "snapname.app", "--arg1", "arg2"}) 1007 c.Assert(err, check.IsNil) 1008 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 1009 c.Assert(execCalled, check.Equals, false) 1010 1011 c.Check(s.Stderr(), check.Equals, fmt.Sprintf(`%s: attempted to run "snapname.app" timer outside of scheduled time "mon,10:00~12:00,,fri,13:00" 1012 `, fakeNow.Format(time.RFC3339))) 1013 s.ResetStdStreams() 1014 1015 restorer = snaprun.MockTimeNow(func() time.Time { 1016 // Monday Feb 12, 10:20 1017 return time.Date(2018, 02, 12, 10, 20, 0, 0, time.Local) 1018 }) 1019 defer restorer() 1020 1021 // and run it under strace 1022 _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", `--timer="mon,10:00~12:00,,fri,13:00"`, "--", "snapname.app", "--arg1", "arg2"}) 1023 c.Assert(err, check.IsNil) 1024 c.Assert(execCalled, check.Equals, true) 1025 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 1026 c.Check(execArgs, check.DeepEquals, []string{ 1027 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 1028 "snap.snapname.app", 1029 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 1030 "snapname.app", "--arg1", "arg2"}) 1031 } 1032 1033 func (s *RunSuite) TestRunCmdWithTraceExecUnhappy(c *check.C) { 1034 _, r := logger.MockLogger() 1035 defer r() 1036 1037 defer mockSnapConfine(dirs.DistroLibExecDir)() 1038 1039 // mock installed snap 1040 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1041 Revision: snap.R("1"), 1042 }) 1043 1044 // pretend we have sudo 1045 sudoCmd := testutil.MockCommand(c, "sudo", "echo unhappy; exit 12") 1046 defer sudoCmd.Restore() 1047 1048 // pretend we have strace 1049 straceCmd := testutil.MockCommand(c, "strace", "") 1050 defer straceCmd.Restore() 1051 1052 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--trace-exec", "--", "snapname.app", "--arg1", "arg2"}) 1053 c.Assert(err, check.ErrorMatches, "exit status 12") 1054 c.Assert(rest, check.DeepEquals, []string{"--", "snapname.app", "--arg1", "arg2"}) 1055 c.Check(s.Stdout(), check.Equals, "unhappy\n") 1056 c.Check(s.Stderr(), check.Equals, "") 1057 } 1058 1059 func (s *RunSuite) TestSnapRunRestoreSecurityContextHappy(c *check.C) { 1060 logbuf, restorer := logger.MockLogger() 1061 defer restorer() 1062 1063 defer mockSnapConfine(dirs.DistroLibExecDir)() 1064 1065 // mock installed snap 1066 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1067 Revision: snap.R("x2"), 1068 }) 1069 1070 // redirect exec 1071 execCalled := 0 1072 restorer = snaprun.MockSyscallExec(func(_ string, args []string, envv []string) error { 1073 execCalled++ 1074 return nil 1075 }) 1076 defer restorer() 1077 1078 verifyCalls := 0 1079 restoreCalls := 0 1080 isEnabledCalls := 0 1081 enabled := false 1082 verify := true 1083 1084 snapUserDir := filepath.Join(s.fakeHome, dirs.UserHomeSnapDir) 1085 1086 restorer = snaprun.MockSELinuxVerifyPathContext(func(what string) (bool, error) { 1087 c.Check(what, check.Equals, snapUserDir) 1088 verifyCalls++ 1089 return verify, nil 1090 }) 1091 defer restorer() 1092 1093 restorer = snaprun.MockSELinuxRestoreContext(func(what string, mode selinux.RestoreMode) error { 1094 c.Check(mode, check.Equals, selinux.RestoreMode{Recursive: true}) 1095 c.Check(what, check.Equals, snapUserDir) 1096 restoreCalls++ 1097 return nil 1098 }) 1099 defer restorer() 1100 1101 restorer = snaprun.MockSELinuxIsEnabled(func() (bool, error) { 1102 isEnabledCalls++ 1103 return enabled, nil 1104 }) 1105 defer restorer() 1106 1107 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) 1108 c.Assert(err, check.IsNil) 1109 c.Check(execCalled, check.Equals, 1) 1110 c.Check(isEnabledCalls, check.Equals, 1) 1111 c.Check(verifyCalls, check.Equals, 0) 1112 c.Check(restoreCalls, check.Equals, 0) 1113 1114 // pretend SELinux is on 1115 enabled = true 1116 1117 _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) 1118 c.Assert(err, check.IsNil) 1119 c.Check(execCalled, check.Equals, 2) 1120 c.Check(isEnabledCalls, check.Equals, 2) 1121 c.Check(verifyCalls, check.Equals, 1) 1122 c.Check(restoreCalls, check.Equals, 0) 1123 1124 // pretend the context does not match 1125 verify = false 1126 1127 logbuf.Reset() 1128 1129 _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) 1130 c.Assert(err, check.IsNil) 1131 c.Check(execCalled, check.Equals, 3) 1132 c.Check(isEnabledCalls, check.Equals, 3) 1133 c.Check(verifyCalls, check.Equals, 2) 1134 c.Check(restoreCalls, check.Equals, 1) 1135 1136 // and we let the user know what we're doing 1137 c.Check(logbuf.String(), testutil.Contains, fmt.Sprintf("restoring default SELinux context of %s", snapUserDir)) 1138 } 1139 1140 func (s *RunSuite) TestSnapRunRestoreSecurityContextFail(c *check.C) { 1141 logbuf, restorer := logger.MockLogger() 1142 defer restorer() 1143 1144 defer mockSnapConfine(dirs.DistroLibExecDir)() 1145 1146 // mock installed snap 1147 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1148 Revision: snap.R("x2"), 1149 }) 1150 1151 // redirect exec 1152 execCalled := 0 1153 restorer = snaprun.MockSyscallExec(func(_ string, args []string, envv []string) error { 1154 execCalled++ 1155 return nil 1156 }) 1157 defer restorer() 1158 1159 verifyCalls := 0 1160 restoreCalls := 0 1161 isEnabledCalls := 0 1162 enabledErr := errors.New("enabled failed") 1163 verifyErr := errors.New("verify failed") 1164 restoreErr := errors.New("restore failed") 1165 1166 snapUserDir := filepath.Join(s.fakeHome, dirs.UserHomeSnapDir) 1167 1168 restorer = snaprun.MockSELinuxVerifyPathContext(func(what string) (bool, error) { 1169 c.Check(what, check.Equals, snapUserDir) 1170 verifyCalls++ 1171 return false, verifyErr 1172 }) 1173 defer restorer() 1174 1175 restorer = snaprun.MockSELinuxRestoreContext(func(what string, mode selinux.RestoreMode) error { 1176 c.Check(mode, check.Equals, selinux.RestoreMode{Recursive: true}) 1177 c.Check(what, check.Equals, snapUserDir) 1178 restoreCalls++ 1179 return restoreErr 1180 }) 1181 defer restorer() 1182 1183 restorer = snaprun.MockSELinuxIsEnabled(func() (bool, error) { 1184 isEnabledCalls++ 1185 return enabledErr == nil, enabledErr 1186 }) 1187 defer restorer() 1188 1189 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) 1190 // these errors are only logged, but we still run the snap 1191 c.Assert(err, check.IsNil) 1192 c.Check(execCalled, check.Equals, 1) 1193 c.Check(logbuf.String(), testutil.Contains, "cannot determine SELinux status: enabled failed") 1194 c.Check(isEnabledCalls, check.Equals, 1) 1195 c.Check(verifyCalls, check.Equals, 0) 1196 c.Check(restoreCalls, check.Equals, 0) 1197 // pretend selinux is on 1198 enabledErr = nil 1199 1200 logbuf.Reset() 1201 1202 _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) 1203 c.Assert(err, check.IsNil) 1204 c.Check(execCalled, check.Equals, 2) 1205 c.Check(logbuf.String(), testutil.Contains, fmt.Sprintf("failed to verify SELinux context of %s: verify failed", snapUserDir)) 1206 c.Check(isEnabledCalls, check.Equals, 2) 1207 c.Check(verifyCalls, check.Equals, 1) 1208 c.Check(restoreCalls, check.Equals, 0) 1209 1210 // pretend the context does not match 1211 verifyErr = nil 1212 1213 logbuf.Reset() 1214 1215 _, err = snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app"}) 1216 c.Assert(err, check.IsNil) 1217 c.Check(execCalled, check.Equals, 3) 1218 c.Check(logbuf.String(), testutil.Contains, fmt.Sprintf("cannot restore SELinux context of %s: restore failed", snapUserDir)) 1219 c.Check(isEnabledCalls, check.Equals, 3) 1220 c.Check(verifyCalls, check.Equals, 2) 1221 c.Check(restoreCalls, check.Equals, 1) 1222 } 1223 1224 // systemctl is-system-running returns "running" in normal situations. 1225 func (s *RunSuite) TestIsStoppingRunning(c *check.C) { 1226 systemctl := testutil.MockCommand(c, "systemctl", ` 1227 case "$1" in 1228 is-system-running) 1229 echo "running" 1230 exit 0 1231 ;; 1232 esac 1233 `) 1234 defer systemctl.Restore() 1235 stop, err := snaprun.IsStopping() 1236 c.Check(err, check.IsNil) 1237 c.Check(stop, check.Equals, false) 1238 c.Check(systemctl.Calls(), check.DeepEquals, [][]string{ 1239 {"systemctl", "is-system-running"}, 1240 }) 1241 } 1242 1243 // systemctl is-system-running returns "stopping" when the system is 1244 // shutting down or rebooting. At the same time it returns a non-zero 1245 // exit status. 1246 func (s *RunSuite) TestIsStoppingStopping(c *check.C) { 1247 systemctl := testutil.MockCommand(c, "systemctl", ` 1248 case "$1" in 1249 is-system-running) 1250 echo "stopping" 1251 exit 1 1252 ;; 1253 esac 1254 `) 1255 defer systemctl.Restore() 1256 stop, err := snaprun.IsStopping() 1257 c.Check(err, check.IsNil) 1258 c.Check(stop, check.Equals, true) 1259 c.Check(systemctl.Calls(), check.DeepEquals, [][]string{ 1260 {"systemctl", "is-system-running"}, 1261 }) 1262 } 1263 1264 // systemctl is-system-running can often return "degraded" 1265 // Let's make sure that is not confusing us. 1266 func (s *RunSuite) TestIsStoppingDegraded(c *check.C) { 1267 systemctl := testutil.MockCommand(c, "systemctl", ` 1268 case "$1" in 1269 is-system-running) 1270 echo "degraded" 1271 exit 1 1272 ;; 1273 esac 1274 `) 1275 defer systemctl.Restore() 1276 stop, err := snaprun.IsStopping() 1277 c.Check(err, check.IsNil) 1278 c.Check(stop, check.Equals, false) 1279 c.Check(systemctl.Calls(), check.DeepEquals, [][]string{ 1280 {"systemctl", "is-system-running"}, 1281 }) 1282 } 1283 1284 func (s *RunSuite) TestSnapRunTrackingApps(c *check.C) { 1285 restore := mockSnapConfine(filepath.Join(dirs.SnapMountDir, "core", "111", dirs.CoreLibExecDir)) 1286 defer restore() 1287 1288 // mock installed snap 1289 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1290 Revision: snap.R("x2"), 1291 }) 1292 1293 // pretend to be running from core 1294 restore = snaprun.MockOsReadlink(func(string) (string, error) { 1295 return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil 1296 }) 1297 defer restore() 1298 1299 created := false 1300 restore = snaprun.MockCreateTransientScopeForTracking(func(securityTag string, opts *cgroup.TrackingOptions) error { 1301 c.Assert(securityTag, check.Equals, "snap.snapname.app") 1302 c.Assert(opts, check.NotNil) 1303 c.Assert(opts.AllowSessionBus, check.Equals, true) 1304 created = true 1305 return nil 1306 }) 1307 defer restore() 1308 1309 restore = snaprun.MockConfirmSystemdServiceTracking(func(securityTag string) error { 1310 panic("apps need to create a scope and do not use systemd service tracking") 1311 }) 1312 defer restore() 1313 1314 // redirect exec 1315 execArg0 := "" 1316 execArgs := []string{} 1317 execEnv := []string{} 1318 restore = snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 1319 execArg0 = arg0 1320 execArgs = args 1321 execEnv = envv 1322 return nil 1323 }) 1324 defer restore() 1325 1326 // and run it! 1327 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 1328 c.Assert(err, check.IsNil) 1329 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 1330 c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine")) 1331 c.Check(execArgs, check.DeepEquals, []string{ 1332 filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine"), 1333 "snap.snapname.app", 1334 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 1335 "snapname.app", "--arg1", "arg2"}) 1336 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 1337 c.Assert(created, check.Equals, true) 1338 } 1339 1340 func (s *RunSuite) TestSnapRunTrackingHooks(c *check.C) { 1341 restore := mockSnapConfine(filepath.Join(dirs.SnapMountDir, "core", "111", dirs.CoreLibExecDir)) 1342 defer restore() 1343 1344 // mock installed snap 1345 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1346 Revision: snap.R("x2"), 1347 }) 1348 1349 // pretend to be running from core 1350 restore = snaprun.MockOsReadlink(func(string) (string, error) { 1351 return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil 1352 }) 1353 defer restore() 1354 1355 created := false 1356 restore = snaprun.MockCreateTransientScopeForTracking(func(securityTag string, opts *cgroup.TrackingOptions) error { 1357 c.Assert(securityTag, check.Equals, "snap.snapname.hook.configure") 1358 c.Assert(opts, check.NotNil) 1359 c.Assert(opts.AllowSessionBus, check.Equals, false) 1360 created = true 1361 return nil 1362 }) 1363 defer restore() 1364 1365 restore = snaprun.MockConfirmSystemdServiceTracking(func(securityTag string) error { 1366 panic("hooks need to create a scope and do not use systemd service tracking") 1367 }) 1368 defer restore() 1369 1370 // redirect exec 1371 execArg0 := "" 1372 execArgs := []string{} 1373 execEnv := []string{} 1374 restore = snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 1375 execArg0 = arg0 1376 execArgs = args 1377 execEnv = envv 1378 return nil 1379 }) 1380 defer restore() 1381 1382 // and run it! 1383 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook", "configure", "-r", "x2", "snapname"}) 1384 c.Assert(err, check.IsNil) 1385 c.Assert(rest, check.DeepEquals, []string{"snapname"}) 1386 c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine")) 1387 c.Check(execArgs, check.DeepEquals, []string{ 1388 filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine"), 1389 "snap.snapname.hook.configure", 1390 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 1391 "--hook=configure", "snapname"}) 1392 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 1393 c.Assert(created, check.Equals, true) 1394 } 1395 1396 func (s *RunSuite) TestSnapRunTrackingServices(c *check.C) { 1397 restore := mockSnapConfine(filepath.Join(dirs.SnapMountDir, "core", "111", dirs.CoreLibExecDir)) 1398 defer restore() 1399 1400 // mock installed snap 1401 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1402 Revision: snap.R("x2"), 1403 }) 1404 1405 // pretend to be running from core 1406 restore = snaprun.MockOsReadlink(func(string) (string, error) { 1407 return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil 1408 }) 1409 defer restore() 1410 1411 restore = snaprun.MockCreateTransientScopeForTracking(func(securityTag string, opts *cgroup.TrackingOptions) error { 1412 panic("services rely on systemd tracking, should not have created a transient scope") 1413 }) 1414 defer restore() 1415 1416 confirmed := false 1417 restore = snaprun.MockConfirmSystemdServiceTracking(func(securityTag string) error { 1418 confirmed = true 1419 c.Assert(securityTag, check.Equals, "snap.snapname.svc") 1420 return nil 1421 }) 1422 defer restore() 1423 1424 // redirect exec 1425 execArg0 := "" 1426 execArgs := []string{} 1427 execEnv := []string{} 1428 restore = snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 1429 execArg0 = arg0 1430 execArgs = args 1431 execEnv = envv 1432 return nil 1433 }) 1434 defer restore() 1435 1436 // and run it! 1437 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.svc", "--arg1", "arg2"}) 1438 c.Assert(err, check.IsNil) 1439 c.Assert(rest, check.DeepEquals, []string{"snapname.svc", "--arg1", "arg2"}) 1440 c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine")) 1441 c.Check(execArgs, check.DeepEquals, []string{ 1442 filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine"), 1443 "snap.snapname.svc", 1444 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 1445 "snapname.svc", "--arg1", "arg2"}) 1446 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 1447 c.Assert(confirmed, check.Equals, true) 1448 } 1449 1450 func (s *RunSuite) TestSnapRunTrackingFailure(c *check.C) { 1451 restore := mockSnapConfine(filepath.Join(dirs.SnapMountDir, "core", "111", dirs.CoreLibExecDir)) 1452 defer restore() 1453 1454 // mock installed snap 1455 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1456 Revision: snap.R("x2"), 1457 }) 1458 1459 // pretend to be running from core 1460 restore = snaprun.MockOsReadlink(func(string) (string, error) { 1461 return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil 1462 }) 1463 defer restore() 1464 1465 created := false 1466 restore = snaprun.MockCreateTransientScopeForTracking(func(securityTag string, opts *cgroup.TrackingOptions) error { 1467 c.Assert(securityTag, check.Equals, "snap.snapname.app") 1468 c.Assert(opts, check.NotNil) 1469 c.Assert(opts.AllowSessionBus, check.Equals, true) 1470 created = true 1471 // Pretend that the tracking system was unable to track this application. 1472 return cgroup.ErrCannotTrackProcess 1473 }) 1474 defer restore() 1475 1476 restore = snaprun.MockConfirmSystemdServiceTracking(func(securityTag string) error { 1477 panic("apps need to create a scope and do not use systemd service tracking") 1478 }) 1479 defer restore() 1480 1481 // redirect exec 1482 execArg0 := "" 1483 execArgs := []string{} 1484 execEnv := []string{} 1485 restore = snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 1486 execArg0 = arg0 1487 execArgs = args 1488 execEnv = envv 1489 return nil 1490 }) 1491 defer restore() 1492 1493 // Capture the debug log that is printed by this test. 1494 os.Setenv("SNAPD_DEBUG", "1") 1495 defer os.Unsetenv("SNAPD_DEBUG") 1496 logbuf, restore := logger.MockLogger() 1497 defer restore() 1498 1499 // and run it! 1500 rest, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--", "snapname.app", "--arg1", "arg2"}) 1501 c.Assert(err, check.IsNil) 1502 c.Assert(rest, check.DeepEquals, []string{"snapname.app", "--arg1", "arg2"}) 1503 c.Check(execArg0, check.Equals, filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine")) 1504 c.Check(execArgs, check.DeepEquals, []string{ 1505 filepath.Join(dirs.SnapMountDir, "/core/111", dirs.CoreLibExecDir, "snap-confine"), 1506 "snap.snapname.app", 1507 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 1508 "snapname.app", "--arg1", "arg2"}) 1509 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=x2") 1510 c.Assert(created, check.Equals, true) 1511 1512 // Ensure that the debug message is printed. 1513 c.Assert(logbuf.String(), testutil.Contains, "snapd cannot track the started application\n") 1514 } 1515 1516 var mockKernelYaml = []byte(`name: pc-kernel 1517 type: kernel 1518 version: 1.0 1519 hooks: 1520 fde-setup: 1521 `) 1522 1523 func (s *RunSuite) TestSnapRunHookKernelImplicitBase(c *check.C) { 1524 defer mockSnapConfine(dirs.DistroLibExecDir)() 1525 1526 nModel := 0 1527 s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) { 1528 switch r.URL.Path { 1529 case "/v2/model": 1530 switch nModel { 1531 case 0: 1532 c.Check(r.Method, check.Equals, "GET") 1533 c.Check(r.URL.RawQuery, check.Equals, "") 1534 fmt.Fprintln(w, happyUC20ModelAssertionResponse) 1535 default: 1536 c.Fatalf("expected to get 1 request for /v2/model, now on %d", nModel+1) 1537 } 1538 nModel++ 1539 } 1540 }) 1541 1542 // mock installed kernel 1543 snaptest.MockSnapCurrent(c, string(mockKernelYaml), &snap.SideInfo{ 1544 Revision: snap.R(42), 1545 }) 1546 1547 // redirect exec 1548 execArg0 := "" 1549 execArgs := []string{} 1550 execEnv := []string{} 1551 restorer := snaprun.MockSyscallExec(func(arg0 string, args []string, envv []string) error { 1552 execArg0 = arg0 1553 execArgs = args 1554 execEnv = envv 1555 return nil 1556 }) 1557 defer restorer() 1558 1559 // Run a hook from the active revision 1560 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--hook=fde-setup", "--", "pc-kernel"}) 1561 c.Assert(err, check.IsNil) 1562 c.Check(execArg0, check.Equals, filepath.Join(dirs.DistroLibExecDir, "snap-confine")) 1563 c.Check(execArgs, check.DeepEquals, []string{ 1564 filepath.Join(dirs.DistroLibExecDir, "snap-confine"), 1565 "--base", "core20", 1566 "snap.pc-kernel.hook.fde-setup", 1567 filepath.Join(dirs.CoreLibExecDir, "snap-exec"), 1568 "--hook=fde-setup", "pc-kernel"}) 1569 c.Check(execEnv, testutil.Contains, "SNAP_REVISION=42") 1570 c.Check(nModel, check.Equals, 1) 1571 } 1572 1573 func (s *RunSuite) TestRunGdbserverNoGdbserver(c *check.C) { 1574 oldPath := os.Getenv("PATH") 1575 os.Setenv("PATH", "/no-path:/really-not") 1576 defer os.Setenv("PATH", oldPath) 1577 1578 defer mockSnapConfine(dirs.DistroLibExecDir)() 1579 snaptest.MockSnapCurrent(c, string(mockYaml), &snap.SideInfo{ 1580 Revision: snap.R("x2"), 1581 }) 1582 1583 _, err := snaprun.Parser(snaprun.Client()).ParseArgs([]string{"run", "--gdbserver", "snapname.app"}) 1584 c.Assert(err, check.ErrorMatches, "please install gdbserver on your system") 1585 }