github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap-update-ns/utils_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"syscall"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	update "github.com/snapcore/snapd/cmd/snap-update-ns"
    33  	"github.com/snapcore/snapd/logger"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/osutil/sys"
    36  	"github.com/snapcore/snapd/testutil"
    37  )
    38  
    39  type utilsSuite struct {
    40  	testutil.BaseTest
    41  	sys *testutil.SyscallRecorder
    42  	log *bytes.Buffer
    43  	as  *update.Assumptions
    44  }
    45  
    46  var _ = Suite(&utilsSuite{})
    47  
    48  func (s *utilsSuite) SetUpTest(c *C) {
    49  	s.BaseTest.SetUpTest(c)
    50  	s.sys = &testutil.SyscallRecorder{}
    51  	s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys))
    52  	buf, restore := logger.MockLogger()
    53  	s.BaseTest.AddCleanup(restore)
    54  	s.log = buf
    55  	s.as = &update.Assumptions{}
    56  }
    57  
    58  func (s *utilsSuite) TearDownTest(c *C) {
    59  	s.BaseTest.TearDownTest(c)
    60  	s.sys.CheckForStrayDescriptors(c)
    61  }
    62  
    63  // secure-mkdir-all
    64  
    65  // Ensure that we reject unclean paths.
    66  func (s *utilsSuite) TestSecureMkdirAllUnclean(c *C) {
    67  	err := update.MkdirAll("/unclean//path", 0755, 123, 456, nil)
    68  	c.Assert(err, ErrorMatches, `cannot split unclean path .*`)
    69  	c.Assert(s.sys.RCalls(), HasLen, 0)
    70  }
    71  
    72  // Ensure that we refuse to create a directory with an relative path.
    73  func (s *utilsSuite) TestSecureMkdirAllRelative(c *C) {
    74  	err := update.MkdirAll("rel/path", 0755, 123, 456, nil)
    75  	c.Assert(err, ErrorMatches, `cannot create directory with relative path: "rel/path"`)
    76  	c.Assert(s.sys.RCalls(), HasLen, 0)
    77  }
    78  
    79  // Ensure that we can "create the root directory.
    80  func (s *utilsSuite) TestSecureMkdirAllLevel0(c *C) {
    81  	c.Assert(update.MkdirAll("/", 0755, 123, 456, nil), IsNil)
    82  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
    83  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
    84  		{C: `close 3`},
    85  	})
    86  }
    87  
    88  // Ensure that we can create a directory in the top-level directory.
    89  func (s *utilsSuite) TestSecureMkdirAllLevel1(c *C) {
    90  	c.Assert(update.MkdirAll("/path", 0755, 123, 456, nil), IsNil)
    91  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
    92  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
    93  		{C: `mkdirat 3 "path" 0755`},
    94  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
    95  		{C: `fchown 4 123 456`},
    96  		{C: `close 4`},
    97  		{C: `close 3`},
    98  	})
    99  }
   100  
   101  // Ensure that we can create a directory two levels from the top-level directory.
   102  func (s *utilsSuite) TestSecureMkdirAllLevel2(c *C) {
   103  	c.Assert(update.MkdirAll("/path/to", 0755, 123, 456, nil), IsNil)
   104  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   105  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   106  		{C: `mkdirat 3 "path" 0755`},
   107  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   108  		{C: `fchown 4 123 456`},
   109  		{C: `close 3`},
   110  		{C: `mkdirat 4 "to" 0755`},
   111  		{C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   112  		{C: `fchown 3 123 456`},
   113  		{C: `close 3`},
   114  		{C: `close 4`},
   115  	})
   116  }
   117  
   118  // Ensure that we can create a directory three levels from the top-level directory.
   119  func (s *utilsSuite) TestSecureMkdirAllLevel3(c *C) {
   120  	c.Assert(update.MkdirAll("/path/to/something", 0755, 123, 456, nil), IsNil)
   121  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   122  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   123  		{C: `mkdirat 3 "path" 0755`},
   124  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   125  		{C: `fchown 4 123 456`},
   126  		{C: `mkdirat 4 "to" 0755`},
   127  		{C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5},
   128  		{C: `fchown 5 123 456`},
   129  		{C: `close 4`},
   130  		{C: `close 3`},
   131  		{C: `mkdirat 5 "something" 0755`},
   132  		{C: `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   133  		{C: `fchown 3 123 456`},
   134  		{C: `close 3`},
   135  		{C: `close 5`},
   136  	})
   137  }
   138  
   139  // Ensure that trespassing for prefix is matched using clean base path.
   140  func (s *utilsSuite) TestTrespassingMatcher(c *C) {
   141  	// We mounted tmpfs at "/path".
   142  	s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/path", Type: "tmpfs", Name: "tmpfs"}})
   143  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   144  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   145  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic})
   146  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   147  	s.sys.InsertFault(`mkdirat 3 "path" 0755`, syscall.EEXIST)
   148  	rs := s.as.RestrictionsFor("/path/to/something")
   149  	// Trespassing detector checked "/path", not "/path/" (which would not match).
   150  	c.Assert(update.MkdirAll("/path/to/something", 0755, 123, 456, rs), IsNil)
   151  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   152  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   153  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   154  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   155  		{C: `mkdirat 3 "path" 0755`, E: syscall.EEXIST},
   156  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   157  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}},
   158  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   159  
   160  		{C: `mkdirat 4 "to" 0755`},
   161  		{C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5},
   162  		{C: `fchown 5 123 456`},
   163  		{C: `close 4`},
   164  		{C: `close 3`},
   165  		{C: `mkdirat 5 "something" 0755`},
   166  		{C: `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   167  		{C: `fchown 3 123 456`},
   168  		{C: `close 3`},
   169  		{C: `close 5`},
   170  	})
   171  }
   172  
   173  // Ensure that writes to /etc/demo are interrupted if /etc is restricted.
   174  func (s *utilsSuite) TestSecureMkdirAllWithRestrictedEtc(c *C) {
   175  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   176  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   177  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic})
   178  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   179  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   180  	rs := s.as.RestrictionsFor("/etc/demo")
   181  	err := update.MkdirAll("/etc/demo", 0755, 123, 456, rs)
   182  	c.Assert(err, ErrorMatches, `cannot write to "/etc/demo" because it would affect the host in "/etc"`)
   183  	c.Assert(err.(*update.TrespassingError).ViolatedPath, Equals, "/etc")
   184  	c.Assert(err.(*update.TrespassingError).DesiredPath, Equals, "/etc/demo")
   185  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   186  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   187  		// we are inspecting the type of the filesystem we are about to perform operation on.
   188  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   189  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   190  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   191  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   192  		{C: `close 3`},
   193  		// ext4 is writable, refuse further operations.
   194  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}},
   195  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   196  		{C: `close 4`},
   197  	})
   198  }
   199  
   200  // Ensure that writes to /etc/demo allowed if /etc is unrestricted.
   201  func (s *utilsSuite) TestSecureMkdirAllWithUnrestrictedEtc(c *C) {
   202  	defer s.as.MockUnrestrictedPaths("/etc")() // Mark /etc as unrestricted.
   203  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   204  	rs := s.as.RestrictionsFor("/etc/demo")
   205  	c.Assert(update.MkdirAll("/etc/demo", 0755, 123, 456, rs), IsNil)
   206  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   207  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   208  		// We are not interested in the type of filesystem at /
   209  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   210  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   211  		// We are not interested in the type of filesystem at /etc
   212  		{C: `close 3`},
   213  		{C: `mkdirat 4 "demo" 0755`},
   214  		{C: `openat 4 "demo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   215  		{C: `fchown 3 123 456`},
   216  		{C: `close 3`},
   217  		{C: `close 4`},
   218  	})
   219  }
   220  
   221  // Ensure that we can detect read only filesystems.
   222  func (s *utilsSuite) TestSecureMkdirAllROFS(c *C) {
   223  	s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) // just realistic
   224  	s.sys.InsertFault(`mkdirat 4 "path" 0755`, syscall.EROFS)
   225  	err := update.MkdirAll("/rofs/path", 0755, 123, 456, nil)
   226  	c.Assert(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`)
   227  	c.Assert(err.(*update.ReadOnlyFsError).Path, Equals, "/rofs")
   228  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   229  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   230  		{C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST},
   231  		{C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   232  		{C: `close 3`},
   233  		{C: `mkdirat 4 "path" 0755`, E: syscall.EROFS},
   234  		{C: `close 4`},
   235  	})
   236  }
   237  
   238  // Ensure that we don't chown existing directories.
   239  func (s *utilsSuite) TestSecureMkdirAllExistingDirsDontChown(c *C) {
   240  	s.sys.InsertFault(`mkdirat 3 "abs" 0755`, syscall.EEXIST)
   241  	s.sys.InsertFault(`mkdirat 4 "path" 0755`, syscall.EEXIST)
   242  	err := update.MkdirAll("/abs/path", 0755, 123, 456, nil)
   243  	c.Assert(err, IsNil)
   244  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   245  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   246  		{C: `mkdirat 3 "abs" 0755`, E: syscall.EEXIST},
   247  		{C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   248  		{C: `close 3`},
   249  		{C: `mkdirat 4 "path" 0755`, E: syscall.EEXIST},
   250  		{C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   251  		{C: `close 3`},
   252  		{C: `close 4`},
   253  	})
   254  }
   255  
   256  // Ensure that we we close everything when mkdirat fails.
   257  func (s *utilsSuite) TestSecureMkdirAllMkdiratError(c *C) {
   258  	s.sys.InsertFault(`mkdirat 3 "abs" 0755`, errTesting)
   259  	err := update.MkdirAll("/abs", 0755, 123, 456, nil)
   260  	c.Assert(err, ErrorMatches, `cannot create directory "/abs": testing`)
   261  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   262  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   263  		{C: `mkdirat 3 "abs" 0755`, E: errTesting},
   264  		{C: `close 3`},
   265  	})
   266  }
   267  
   268  // Ensure that we we close everything when fchown fails.
   269  func (s *utilsSuite) TestSecureMkdirAllFchownError(c *C) {
   270  	s.sys.InsertFault(`fchown 4 123 456`, errTesting)
   271  	err := update.MkdirAll("/path", 0755, 123, 456, nil)
   272  	c.Assert(err, ErrorMatches, `cannot chown directory "/path" to 123.456: testing`)
   273  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   274  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   275  		{C: `mkdirat 3 "path" 0755`},
   276  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   277  		{C: `fchown 4 123 456`, E: errTesting},
   278  		{C: `close 4`},
   279  		{C: `close 3`},
   280  	})
   281  }
   282  
   283  // Check error path when we cannot open root directory.
   284  func (s *utilsSuite) TestSecureMkdirAllOpenRootError(c *C) {
   285  	s.sys.InsertFault(`open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting)
   286  	err := update.MkdirAll("/abs/path", 0755, 123, 456, nil)
   287  	c.Assert(err, ErrorMatches, "cannot open root directory: testing")
   288  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   289  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting},
   290  	})
   291  }
   292  
   293  // Check error path when we cannot open non-root directory.
   294  func (s *utilsSuite) TestSecureMkdirAllOpenError(c *C) {
   295  	s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting)
   296  	err := update.MkdirAll("/abs/path", 0755, 123, 456, nil)
   297  	c.Assert(err, ErrorMatches, `cannot open directory "/abs": testing`)
   298  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   299  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   300  		{C: `mkdirat 3 "abs" 0755`},
   301  		{C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting},
   302  		{C: `close 3`},
   303  	})
   304  }
   305  
   306  func (s *utilsSuite) TestPlanWritableMimic(c *C) {
   307  	s.sys.InsertSysLstatResult(`lstat "/foo" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755})
   308  	restore := update.MockReadDir(func(dir string) ([]os.FileInfo, error) {
   309  		c.Assert(dir, Equals, "/foo")
   310  		return []os.FileInfo{
   311  			testutil.FakeFileInfo("file", 0),
   312  			testutil.FakeFileInfo("dir", os.ModeDir),
   313  			testutil.FakeFileInfo("symlink", os.ModeSymlink),
   314  			testutil.FakeFileInfo("error-symlink-readlink", os.ModeSymlink),
   315  			// NOTE: None of the filesystem entries below are supported because
   316  			// they cannot be placed inside snaps or can only be created at
   317  			// runtime in areas that are already writable and this would never
   318  			// have to be handled in a writable mimic.
   319  			testutil.FakeFileInfo("block-dev", os.ModeDevice),
   320  			testutil.FakeFileInfo("char-dev", os.ModeDevice|os.ModeCharDevice),
   321  			testutil.FakeFileInfo("socket", os.ModeSocket),
   322  			testutil.FakeFileInfo("pipe", os.ModeNamedPipe),
   323  		}, nil
   324  	})
   325  	defer restore()
   326  	restore = update.MockReadlink(func(name string) (string, error) {
   327  		switch name {
   328  		case "/foo/symlink":
   329  			return "target", nil
   330  		case "/foo/error-symlink-readlink":
   331  			return "", errTesting
   332  		}
   333  		panic("unexpected")
   334  	})
   335  	defer restore()
   336  
   337  	changes, err := update.PlanWritableMimic("/foo", "/foo/bar")
   338  	c.Assert(err, IsNil)
   339  
   340  	c.Assert(changes, DeepEquals, []*update.Change{
   341  		// Store /foo in /tmp/.snap/foo while we set things up
   342  		{Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount},
   343  		// Put a tmpfs over /foo
   344  		{Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar", "mode=0755", "uid=0", "gid=0"}}, Action: update.Mount},
   345  		// Bind mount files and directories over. Note that files are identified by x-snapd.kind=file option.
   346  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   347  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   348  		// Create symlinks.
   349  		// Bad symlinks and all other file types are skipped and not
   350  		// recorded in mount changes.
   351  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   352  		// Unmount the safe-keeping directory
   353  		{Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount},
   354  	})
   355  }
   356  
   357  func (s *utilsSuite) TestPlanWritableMimicErrors(c *C) {
   358  	s.sys.InsertSysLstatResult(`lstat "/foo" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755})
   359  	restore := update.MockReadDir(func(dir string) ([]os.FileInfo, error) {
   360  		c.Assert(dir, Equals, "/foo")
   361  		return nil, errTesting
   362  	})
   363  	defer restore()
   364  	restore = update.MockReadlink(func(name string) (string, error) {
   365  		return "", errTesting
   366  	})
   367  	defer restore()
   368  
   369  	changes, err := update.PlanWritableMimic("/foo", "/foo/bar")
   370  	c.Assert(err, ErrorMatches, "testing")
   371  	c.Assert(changes, HasLen, 0)
   372  }
   373  
   374  func (s *utilsSuite) TestExecWirableMimicSuccess(c *C) {
   375  	// This plan is the same as in the test above. This is what comes out of planWritableMimic.
   376  	plan := []*update.Change{
   377  		{Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount},
   378  		{Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   379  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   380  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   381  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   382  		{Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount},
   383  	}
   384  
   385  	// Mock the act of performing changes, each of the change we perform is coming from the plan.
   386  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   387  		c.Assert(plan, testutil.DeepContains, chg)
   388  		return nil, nil
   389  	})
   390  	defer restore()
   391  
   392  	// The executed plan leaves us with a simplified view of the plan that is suitable for undo.
   393  	undoPlan, err := update.ExecWritableMimic(plan, s.as)
   394  	c.Assert(err, IsNil)
   395  	c.Assert(undoPlan, DeepEquals, []*update.Change{
   396  		{Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   397  		{Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   398  		{Entry: osutil.MountEntry{Name: "/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar", "x-snapd.detach"}}, Action: update.Mount},
   399  	})
   400  }
   401  
   402  func (s *utilsSuite) TestExecWirableMimicErrorWithRecovery(c *C) {
   403  	// This plan is the same as in the test above. This is what comes out of planWritableMimic.
   404  	plan := []*update.Change{
   405  		{Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount},
   406  		{Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   407  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   408  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   409  		// NOTE: the next perform will fail. Notably the symlink did not fail.
   410  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind"}}, Action: update.Mount},
   411  		{Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount},
   412  	}
   413  
   414  	// Mock the act of performing changes. Before we inject a failure we ensure
   415  	// that each of the change we perform is coming from the plan. For the
   416  	// purpose of the test the change that bind mounts the "dir" over itself
   417  	// will fail and will trigger an recovery path. The changes performed in
   418  	// the recovery path are recorded.
   419  	var recoveryPlan []*update.Change
   420  	recovery := false
   421  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   422  		if !recovery {
   423  			c.Assert(plan, testutil.DeepContains, chg)
   424  			if chg.Entry.Name == "/tmp/.snap/foo/dir" {
   425  				recovery = true // switch to recovery mode
   426  				return nil, errTesting
   427  			}
   428  		} else {
   429  			recoveryPlan = append(recoveryPlan, chg)
   430  		}
   431  		return nil, nil
   432  	})
   433  	defer restore()
   434  
   435  	// The executed plan fails, leaving us with the error and an empty undo plan.
   436  	undoPlan, err := update.ExecWritableMimic(plan, s.as)
   437  	c.Assert(err, Equals, errTesting)
   438  	c.Assert(undoPlan, HasLen, 0)
   439  	// The changes we managed to perform were undone correctly.
   440  	c.Assert(recoveryPlan, DeepEquals, []*update.Change{
   441  		// NOTE: there is no symlink undo entry as it is implicitly undone by unmounting the tmpfs.
   442  		{Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Unmount},
   443  		{Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Unmount},
   444  		{Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind", "x-snapd.detach"}}, Action: update.Unmount},
   445  	})
   446  }
   447  
   448  func (s *utilsSuite) TestExecWirableMimicErrorNothingDone(c *C) {
   449  	// This plan is the same as in the test above. This is what comes out of planWritableMimic.
   450  	plan := []*update.Change{
   451  		{Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount},
   452  		{Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   453  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   454  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   455  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   456  		{Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount},
   457  	}
   458  
   459  	// Mock the act of performing changes and just fail on any request.
   460  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   461  		return nil, errTesting
   462  	})
   463  	defer restore()
   464  
   465  	// The executed plan fails, the recovery didn't fail (it's empty) so we just return that error.
   466  	undoPlan, err := update.ExecWritableMimic(plan, s.as)
   467  	c.Assert(err, Equals, errTesting)
   468  	c.Assert(undoPlan, HasLen, 0)
   469  }
   470  
   471  func (s *utilsSuite) TestExecWirableMimicErrorCannotUndo(c *C) {
   472  	// This plan is the same as in the test above. This is what comes out of planWritableMimic.
   473  	plan := []*update.Change{
   474  		{Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount},
   475  		{Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   476  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   477  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   478  		{Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount},
   479  		{Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount},
   480  	}
   481  
   482  	// Mock the act of performing changes. After performing the first change
   483  	// correctly we will fail forever (this includes the recovery path) so the
   484  	// execute function ends up in a situation where it cannot perform the
   485  	// recovery path and will have to return a fatal error.
   486  	i := -1
   487  	restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) {
   488  		i++
   489  		if i > 0 {
   490  			return nil, fmt.Errorf("failure-%d", i)
   491  		}
   492  		return nil, nil
   493  	})
   494  	defer restore()
   495  
   496  	// The plan partially succeeded and we cannot undo those changes.
   497  	_, err := update.ExecWritableMimic(plan, s.as)
   498  	c.Assert(err, ErrorMatches, `cannot undo change ".*" while recovering from earlier error failure-1: failure-2`)
   499  	c.Assert(err, FitsTypeOf, &update.FatalError{})
   500  }
   501  
   502  // realSystemSuite is not isolated / mocked from the system.
   503  type realSystemSuite struct {
   504  	as *update.Assumptions
   505  }
   506  
   507  var _ = Suite(&realSystemSuite{})
   508  
   509  func (s *realSystemSuite) SetUpTest(c *C) {
   510  	s.as = &update.Assumptions{}
   511  	s.as.AddUnrestrictedPaths("/tmp")
   512  }
   513  
   514  // Check that we can actually create directories.
   515  // This doesn't test the chown logic as that requires root.
   516  func (s *realSystemSuite) TestSecureMkdirAllForReal(c *C) {
   517  	d := c.MkDir()
   518  
   519  	// Create d (which already exists) with mode 0777 (but c.MkDir() used 0700
   520  	// internally and since we are not creating the directory we should not be
   521  	// changing that.
   522  	c.Assert(update.MkdirAll(d, 0777, sys.FlagID, sys.FlagID, nil), IsNil)
   523  	fi, err := os.Stat(d)
   524  	c.Assert(err, IsNil)
   525  	c.Check(fi.IsDir(), Equals, true)
   526  	c.Check(fi.Mode().Perm(), Equals, os.FileMode(0700))
   527  
   528  	// Create d1, which is a simple subdirectory, with a distinct mode and
   529  	// check that it was applied. Note that default umask 022 is subtracted so
   530  	// effective directory has different permissions.
   531  	d1 := filepath.Join(d, "subdir")
   532  	c.Assert(update.MkdirAll(d1, 0707, sys.FlagID, sys.FlagID, nil), IsNil)
   533  	fi, err = os.Stat(d1)
   534  	c.Assert(err, IsNil)
   535  	c.Check(fi.IsDir(), Equals, true)
   536  	c.Check(fi.Mode().Perm(), Equals, os.FileMode(0705))
   537  
   538  	// Create d2, which is a deeper subdirectory, with another distinct mode
   539  	// and check that it was applied.
   540  	d2 := filepath.Join(d, "subdir/subdir/subdir")
   541  	c.Assert(update.MkdirAll(d2, 0750, sys.FlagID, sys.FlagID, nil), IsNil)
   542  	fi, err = os.Stat(d2)
   543  	c.Assert(err, IsNil)
   544  	c.Check(fi.IsDir(), Equals, true)
   545  	c.Check(fi.Mode().Perm(), Equals, os.FileMode(0750))
   546  }
   547  
   548  // secure-mkfile-all
   549  
   550  // Ensure that we reject unclean paths.
   551  func (s *utilsSuite) TestSecureMkfileAllUnclean(c *C) {
   552  	err := update.MkfileAll("/unclean//path", 0755, 123, 456, nil)
   553  	c.Assert(err, ErrorMatches, `cannot split unclean path .*`)
   554  	c.Assert(s.sys.RCalls(), HasLen, 0)
   555  }
   556  
   557  // Ensure that we refuse to create a file with an relative path.
   558  func (s *utilsSuite) TestSecureMkfileAllRelative(c *C) {
   559  	err := update.MkfileAll("rel/path", 0755, 123, 456, nil)
   560  	c.Assert(err, ErrorMatches, `cannot create file with relative path: "rel/path"`)
   561  	c.Assert(s.sys.RCalls(), HasLen, 0)
   562  }
   563  
   564  // Ensure that we refuse creating the root directory as a file.
   565  func (s *utilsSuite) TestSecureMkfileAllLevel0(c *C) {
   566  	err := update.MkfileAll("/", 0755, 123, 456, nil)
   567  	c.Assert(err, ErrorMatches, `cannot create non-file path: "/"`)
   568  	c.Assert(s.sys.RCalls(), HasLen, 0)
   569  }
   570  
   571  // Ensure that we can create a file in the top-level directory.
   572  func (s *utilsSuite) TestSecureMkfileAllLevel1(c *C) {
   573  	c.Assert(update.MkfileAll("/path", 0755, 123, 456, nil), IsNil)
   574  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   575  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   576  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 4},
   577  		{C: `fchown 4 123 456`},
   578  		{C: `close 4`},
   579  		{C: `close 3`},
   580  	})
   581  }
   582  
   583  // Ensure that we can create a file two levels from the top-level directory.
   584  func (s *utilsSuite) TestSecureMkfileAllLevel2(c *C) {
   585  	c.Assert(update.MkfileAll("/path/to", 0755, 123, 456, nil), IsNil)
   586  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   587  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   588  		{C: `mkdirat 3 "path" 0755`},
   589  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   590  		{C: `fchown 4 123 456`},
   591  		{C: `close 3`},
   592  		{C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3},
   593  		{C: `fchown 3 123 456`},
   594  		{C: `close 3`},
   595  		{C: `close 4`},
   596  	})
   597  }
   598  
   599  // Ensure that we can create a file three levels from the top-level directory.
   600  func (s *utilsSuite) TestSecureMkfileAllLevel3(c *C) {
   601  	c.Assert(update.MkfileAll("/path/to/something", 0755, 123, 456, nil), IsNil)
   602  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   603  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   604  		{C: `mkdirat 3 "path" 0755`},
   605  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   606  		{C: `fchown 4 123 456`},
   607  		{C: `mkdirat 4 "to" 0755`},
   608  		{C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5},
   609  		{C: `fchown 5 123 456`},
   610  		{C: `close 4`},
   611  		{C: `close 3`},
   612  		{C: `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3},
   613  		{C: `fchown 3 123 456`},
   614  		{C: `close 3`},
   615  		{C: `close 5`},
   616  	})
   617  }
   618  
   619  // Ensure that we can detect read only filesystems.
   620  func (s *utilsSuite) TestSecureMkfileAllROFS(c *C) {
   621  	s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) // just realistic
   622  	s.sys.InsertFault(`openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EROFS)
   623  	err := update.MkfileAll("/rofs/path", 0755, 123, 456, nil)
   624  	c.Check(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`)
   625  	c.Assert(err.(*update.ReadOnlyFsError).Path, Equals, "/rofs")
   626  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   627  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   628  		{C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST},
   629  		{C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   630  		{C: `close 3`},
   631  		{C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: syscall.EROFS},
   632  		{C: `close 4`},
   633  	})
   634  }
   635  
   636  // Ensure that we don't chown existing files or directories.
   637  func (s *utilsSuite) TestSecureMkfileAllExistingDirsDontChown(c *C) {
   638  	s.sys.InsertFault(`mkdirat 3 "abs" 0755`, syscall.EEXIST)
   639  	s.sys.InsertFault(`openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EEXIST)
   640  	err := update.MkfileAll("/abs/path", 0755, 123, 456, nil)
   641  	c.Check(err, IsNil)
   642  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   643  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   644  		{C: `mkdirat 3 "abs" 0755`, E: syscall.EEXIST},
   645  		{C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   646  		{C: `close 3`},
   647  		{C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: syscall.EEXIST},
   648  		{C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC 0`, R: 3},
   649  		{C: `close 3`},
   650  		{C: `close 4`},
   651  	})
   652  }
   653  
   654  // Ensure that we we close everything when openat fails.
   655  func (s *utilsSuite) TestSecureMkfileAllOpenat2ndError(c *C) {
   656  	s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EEXIST)
   657  	s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC 0`, errTesting)
   658  	err := update.MkfileAll("/abs", 0755, 123, 456, nil)
   659  	c.Assert(err, ErrorMatches, `cannot open file "/abs": testing`)
   660  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   661  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   662  		{C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: syscall.EEXIST},
   663  		{C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC 0`, E: errTesting},
   664  		{C: `close 3`},
   665  	})
   666  }
   667  
   668  // Ensure that we we close everything when openat (non-exclusive) fails.
   669  func (s *utilsSuite) TestSecureMkfileAllOpenatError(c *C) {
   670  	s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, errTesting)
   671  	err := update.MkfileAll("/abs", 0755, 123, 456, nil)
   672  	c.Assert(err, ErrorMatches, `cannot open file "/abs": testing`)
   673  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   674  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   675  		{C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: errTesting},
   676  		{C: `close 3`},
   677  	})
   678  }
   679  
   680  // Ensure that we we close everything when fchown fails.
   681  func (s *utilsSuite) TestSecureMkfileAllFchownError(c *C) {
   682  	s.sys.InsertFault(`fchown 4 123 456`, errTesting)
   683  	err := update.MkfileAll("/path", 0755, 123, 456, nil)
   684  	c.Assert(err, ErrorMatches, `cannot chown file "/path" to 123.456: testing`)
   685  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   686  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   687  		{C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 4},
   688  		{C: `fchown 4 123 456`, E: errTesting},
   689  		{C: `close 4`},
   690  		{C: `close 3`},
   691  	})
   692  }
   693  
   694  // Check error path when we cannot open root directory.
   695  func (s *utilsSuite) TestSecureMkfileAllOpenRootError(c *C) {
   696  	s.sys.InsertFault(`open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting)
   697  	err := update.MkfileAll("/abs/path", 0755, 123, 456, nil)
   698  	c.Assert(err, ErrorMatches, "cannot open root directory: testing")
   699  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   700  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting},
   701  	})
   702  }
   703  
   704  // Check error path when we cannot open non-root directory.
   705  func (s *utilsSuite) TestSecureMkfileAllOpenError(c *C) {
   706  	s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting)
   707  	err := update.MkfileAll("/abs/path", 0755, 123, 456, nil)
   708  	c.Assert(err, ErrorMatches, `cannot open directory "/abs": testing`)
   709  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   710  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   711  		{C: `mkdirat 3 "abs" 0755`},
   712  		{C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting},
   713  		{C: `close 3`},
   714  	})
   715  }
   716  
   717  // We want to create a symlink in $SNAP_DATA and that's fine.
   718  func (s *utilsSuite) TestSecureMksymlinkAllInSnapData(c *C) {
   719  	s.sys.InsertFault(`mkdirat 3 "var" 0755`, syscall.EEXIST)
   720  	s.sys.InsertFault(`mkdirat 4 "snap" 0755`, syscall.EEXIST)
   721  	s.sys.InsertFault(`mkdirat 5 "foo" 0755`, syscall.EEXIST)
   722  	s.sys.InsertFault(`mkdirat 6 "42" 0755`, syscall.EEXIST)
   723  
   724  	err := update.MksymlinkAll("/var/snap/foo/42/symlink", 0755, 0, 0, "/oldname", nil)
   725  	c.Assert(err, IsNil)
   726  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   727  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   728  		{C: `mkdirat 3 "var" 0755`, E: syscall.EEXIST},
   729  		{C: `openat 3 "var" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   730  		{C: `mkdirat 4 "snap" 0755`, E: syscall.EEXIST},
   731  		{C: `openat 4 "snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5},
   732  		{C: `mkdirat 5 "foo" 0755`, E: syscall.EEXIST},
   733  		{C: `openat 5 "foo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 6},
   734  		{C: `mkdirat 6 "42" 0755`, E: syscall.EEXIST},
   735  		{C: `openat 6 "42" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 7},
   736  		{C: `close 6`},
   737  		{C: `close 5`},
   738  		{C: `close 4`},
   739  		{C: `close 3`},
   740  		{C: `symlinkat "/oldname" 7 "symlink"`},
   741  		{C: `close 7`},
   742  	})
   743  }
   744  
   745  // We want to create a symlink in /etc but the host filesystem would be affected.
   746  func (s *utilsSuite) TestSecureMksymlinkAllInEtc(c *C) {
   747  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   748  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   749  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic})
   750  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   751  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   752  	rs := s.as.RestrictionsFor("/etc/symlink")
   753  	err := update.MksymlinkAll("/etc/symlink", 0755, 0, 0, "/oldname", rs)
   754  	c.Assert(err, ErrorMatches, `cannot write to "/etc/symlink" because it would affect the host in "/etc"`)
   755  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   756  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   757  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   758  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   759  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   760  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   761  		{C: `close 3`},
   762  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}},
   763  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   764  		{C: `close 4`},
   765  	})
   766  }
   767  
   768  // We want to create a symlink deep in /etc but the host filesystem would be affected.
   769  // This just shows that we pick the right place to construct the mimic
   770  func (s *utilsSuite) TestSecureMksymlinkAllDeepInEtc(c *C) {
   771  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   772  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   773  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic})
   774  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   775  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   776  	rs := s.as.RestrictionsFor("/etc/some/other/stuff/symlink")
   777  	err := update.MksymlinkAll("/etc/some/other/stuff/symlink", 0755, 0, 0, "/oldname", rs)
   778  	c.Assert(err, ErrorMatches, `cannot write to "/etc/some/other/stuff/symlink" because it would affect the host in "/etc"`)
   779  	c.Assert(err.(*update.TrespassingError).ViolatedPath, Equals, "/etc")
   780  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   781  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   782  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   783  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   784  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   785  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   786  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}},
   787  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   788  		{C: `close 4`},
   789  		{C: `close 3`},
   790  	})
   791  }
   792  
   793  // We want to create a file in /etc but the host filesystem would be affected.
   794  func (s *utilsSuite) TestSecureMkfileAllInEtc(c *C) {
   795  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   796  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   797  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic})
   798  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   799  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   800  	rs := s.as.RestrictionsFor("/etc/file")
   801  	err := update.MkfileAll("/etc/file", 0755, 0, 0, rs)
   802  	c.Assert(err, ErrorMatches, `cannot write to "/etc/file" because it would affect the host in "/etc"`)
   803  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   804  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   805  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   806  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   807  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   808  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   809  		{C: `close 3`},
   810  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}},
   811  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   812  		{C: `close 4`},
   813  	})
   814  }
   815  
   816  // We want to create a directory in /etc but the host filesystem would be affected.
   817  func (s *utilsSuite) TestSecureMkdirAllInEtc(c *C) {
   818  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   819  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   820  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic})
   821  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   822  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   823  	rs := s.as.RestrictionsFor("/etc/dir")
   824  	err := update.MkdirAll("/etc/dir", 0755, 0, 0, rs)
   825  	c.Assert(err, ErrorMatches, `cannot write to "/etc/dir" because it would affect the host in "/etc"`)
   826  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   827  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   828  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   829  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   830  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   831  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   832  		{C: `close 3`},
   833  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}},
   834  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   835  		{C: `close 4`},
   836  	})
   837  }
   838  
   839  // We want to create a directory in /snap/foo/42/dir and want to know what happens.
   840  func (s *utilsSuite) TestSecureMkdirAllInSNAP(c *C) {
   841  	// Allow creating directories under /snap/ related to this snap ("foo").
   842  	// This matches what is done inside main().
   843  	restore := s.as.MockUnrestrictedPaths("/snap/foo")
   844  	defer restore()
   845  
   846  	s.sys.InsertFault(`mkdirat 3 "snap" 0755`, syscall.EEXIST)
   847  	s.sys.InsertFault(`mkdirat 4 "foo" 0755`, syscall.EEXIST)
   848  	s.sys.InsertFault(`mkdirat 5 "42" 0755`, syscall.EEXIST)
   849  
   850  	rs := s.as.RestrictionsFor("/snap/foo/42/dir")
   851  	err := update.MkdirAll("/snap/foo/42/dir", 0755, 0, 0, rs)
   852  	c.Assert(err, IsNil)
   853  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   854  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   855  		{C: `mkdirat 3 "snap" 0755`, E: syscall.EEXIST},
   856  		{C: `openat 3 "snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   857  		{C: `mkdirat 4 "foo" 0755`, E: syscall.EEXIST},
   858  		{C: `openat 4 "foo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5},
   859  		{C: `mkdirat 5 "42" 0755`, E: syscall.EEXIST},
   860  		{C: `openat 5 "42" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 6},
   861  		{C: `close 5`},
   862  		{C: `close 4`},
   863  		{C: `close 3`},
   864  		{C: `mkdirat 6 "dir" 0755`},
   865  		{C: `openat 6 "dir" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   866  		{C: `fchown 3 0 0`},
   867  		{C: `close 3`},
   868  		{C: `close 6`},
   869  	})
   870  }
   871  
   872  // We want to create a symlink in /etc which is a tmpfs that we mounted so that is ok.
   873  func (s *utilsSuite) TestSecureMksymlinkAllInEtcAfterMimic(c *C) {
   874  	// Because /etc is not on a list of unrestricted paths the write to
   875  	// /etc/symlink must be validated with step-by-step operation.
   876  	rootStatfs := syscall.Statfs_t{Type: update.SquashfsMagic, Flags: update.StReadOnly}
   877  	rootStat := syscall.Stat_t{}
   878  	etcStatfs := syscall.Statfs_t{Type: update.TmpfsMagic}
   879  	etcStat := syscall.Stat_t{}
   880  	s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/etc", Type: "tmpfs", Name: "tmpfs"}})
   881  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, rootStatfs)
   882  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, rootStat)
   883  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   884  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, etcStatfs)
   885  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, etcStat)
   886  	rs := s.as.RestrictionsFor("/etc/symlink")
   887  	err := update.MksymlinkAll("/etc/symlink", 0755, 0, 0, "/oldname", rs)
   888  	c.Assert(err, IsNil)
   889  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   890  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   891  		{C: `fstatfs 3 <ptr>`, R: rootStatfs},
   892  		{C: `fstat 3 <ptr>`, R: rootStat},
   893  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   894  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   895  		{C: `close 3`},
   896  		{C: `fstatfs 4 <ptr>`, R: etcStatfs},
   897  		{C: `fstat 4 <ptr>`, R: etcStat},
   898  		{C: `symlinkat "/oldname" 4 "symlink"`},
   899  		{C: `close 4`},
   900  	})
   901  }
   902  
   903  // We want to create a file in /etc which is a tmpfs created by snapd so that's okay.
   904  func (s *utilsSuite) TestSecureMkfileAllInEtcAfterMimic(c *C) {
   905  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   906  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   907  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic})
   908  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   909  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   910  	s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/etc", Type: "tmpfs", Name: "tmpfs"}})
   911  	rs := s.as.RestrictionsFor("/etc/file")
   912  	err := update.MkfileAll("/etc/file", 0755, 0, 0, rs)
   913  	c.Assert(err, IsNil)
   914  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   915  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   916  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   917  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   918  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   919  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   920  		{C: `close 3`},
   921  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}},
   922  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   923  		{C: `openat 4 "file" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3},
   924  		{C: `fchown 3 0 0`},
   925  		{C: `close 3`},
   926  		{C: `close 4`},
   927  	})
   928  }
   929  
   930  // We want to create a directory in /etc which is a tmpfs created by snapd so that is ok.
   931  func (s *utilsSuite) TestSecureMkdirAllInEtcAfterMimic(c *C) {
   932  	s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic})
   933  	s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{})
   934  	s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic})
   935  	s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{})
   936  	s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST)
   937  	s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/etc", Type: "tmpfs", Name: "tmpfs"}})
   938  	rs := s.as.RestrictionsFor("/etc/dir")
   939  	err := update.MkdirAll("/etc/dir", 0755, 0, 0, rs)
   940  	c.Assert(err, IsNil)
   941  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
   942  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   943  		{C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}},
   944  		{C: `fstat 3 <ptr>`, R: syscall.Stat_t{}},
   945  		{C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST},
   946  		{C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4},
   947  		{C: `close 3`},
   948  		{C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}},
   949  		{C: `fstat 4 <ptr>`, R: syscall.Stat_t{}},
   950  		{C: `mkdirat 4 "dir" 0755`},
   951  		{C: `openat 4 "dir" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3},
   952  		{C: `fchown 3 0 0`},
   953  		{C: `close 3`},
   954  		{C: `close 4`},
   955  	})
   956  }
   957  
   958  // Check that we can actually create files.
   959  // This doesn't test the chown logic as that requires root.
   960  func (s *realSystemSuite) TestSecureMkfileAllForReal(c *C) {
   961  	d := c.MkDir()
   962  
   963  	// Create f1, which is a simple subdirectory, with a distinct mode and
   964  	// check that it was applied. Note that default umask 022 is subtracted so
   965  	// effective directory has different permissions.
   966  	f1 := filepath.Join(d, "file")
   967  	c.Assert(update.MkfileAll(f1, 0707, sys.FlagID, sys.FlagID, nil), IsNil)
   968  	fi, err := os.Stat(f1)
   969  	c.Assert(err, IsNil)
   970  	c.Check(fi.Mode().IsRegular(), Equals, true)
   971  	c.Check(fi.Mode().Perm(), Equals, os.FileMode(0705))
   972  
   973  	// Create f2, which is a deeper subdirectory, with another distinct mode
   974  	// and check that it was applied.
   975  	f2 := filepath.Join(d, "subdir/subdir/file")
   976  	c.Assert(update.MkfileAll(f2, 0750, sys.FlagID, sys.FlagID, nil), IsNil)
   977  	fi, err = os.Stat(f2)
   978  	c.Assert(err, IsNil)
   979  	c.Check(fi.Mode().IsRegular(), Equals, true)
   980  	c.Check(fi.Mode().Perm(), Equals, os.FileMode(0750))
   981  }
   982  
   983  // Check that we can actually create symlinks.
   984  // This doesn't test the chown logic as that requires root.
   985  func (s *realSystemSuite) TestSecureMksymlinkAllForReal(c *C) {
   986  	d := c.MkDir()
   987  
   988  	// Create symlink f1 that points to "oldname" and check that it
   989  	// is correct. Note that symlink permissions are always set to 0777
   990  	f1 := filepath.Join(d, "symlink")
   991  	err := update.MksymlinkAll(f1, 0755, sys.FlagID, sys.FlagID, "oldname", nil)
   992  	c.Assert(err, IsNil)
   993  	fi, err := os.Lstat(f1)
   994  	c.Assert(err, IsNil)
   995  	c.Check(fi.Mode()&os.ModeSymlink, Equals, os.ModeSymlink)
   996  	c.Check(fi.Mode().Perm(), Equals, os.FileMode(0777))
   997  
   998  	target, err := os.Readlink(f1)
   999  	c.Assert(err, IsNil)
  1000  	c.Check(target, Equals, "oldname")
  1001  
  1002  	// Create an identical symlink to see that it doesn't fail.
  1003  	err = update.MksymlinkAll(f1, 0755, sys.FlagID, sys.FlagID, "oldname", nil)
  1004  	c.Assert(err, IsNil)
  1005  
  1006  	// Create a different symlink and see that it fails now
  1007  	err = update.MksymlinkAll(f1, 0755, sys.FlagID, sys.FlagID, "other", nil)
  1008  	c.Assert(err, ErrorMatches, `cannot create symbolic link ".*/symlink": existing symbolic link in the way`)
  1009  
  1010  	// Create an file and check that it clashes with a symlink we attempt to create.
  1011  	f2 := filepath.Join(d, "file")
  1012  	err = update.MkfileAll(f2, 0755, sys.FlagID, sys.FlagID, nil)
  1013  	c.Assert(err, IsNil)
  1014  	err = update.MksymlinkAll(f2, 0755, sys.FlagID, sys.FlagID, "oldname", nil)
  1015  	c.Assert(err, ErrorMatches, `cannot create symbolic link ".*/file": existing file in the way`)
  1016  
  1017  	// Create an file and check that it clashes with a symlink we attempt to create.
  1018  	f3 := filepath.Join(d, "dir")
  1019  	err = update.MkdirAll(f3, 0755, sys.FlagID, sys.FlagID, nil)
  1020  	c.Assert(err, IsNil)
  1021  	err = update.MksymlinkAll(f3, 0755, sys.FlagID, sys.FlagID, "oldname", nil)
  1022  	c.Assert(err, ErrorMatches, `cannot create symbolic link ".*/dir": existing file in the way`)
  1023  
  1024  	err = update.MksymlinkAll("/", 0755, sys.FlagID, sys.FlagID, "oldname", nil)
  1025  	c.Assert(err, ErrorMatches, `cannot create non-file path: "/"`)
  1026  }
  1027  
  1028  func (s *utilsSuite) TestCleanTrailingSlash(c *C) {
  1029  	// This is a sanity test for the use of filepath.Clean in secureMk{dir,file}All
  1030  	c.Assert(filepath.Clean("/path/"), Equals, "/path")
  1031  	c.Assert(filepath.Clean("path/"), Equals, "path")
  1032  	c.Assert(filepath.Clean("path/."), Equals, "path")
  1033  	c.Assert(filepath.Clean("path/.."), Equals, ".")
  1034  	c.Assert(filepath.Clean("other/path/.."), Equals, "other")
  1035  }
  1036  
  1037  // secure-open-path
  1038  
  1039  func (s *utilsSuite) TestSecureOpenPath(c *C) {
  1040  	stat := syscall.Stat_t{Mode: syscall.S_IFDIR}
  1041  	s.sys.InsertFstatResult("fstat 5 <ptr>", stat)
  1042  	fd, err := update.OpenPath("/foo/bar")
  1043  	c.Assert(err, IsNil)
  1044  	defer s.sys.Close(fd)
  1045  	c.Assert(fd, Equals, 5)
  1046  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
  1047  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3},
  1048  		{C: `openat 3 "foo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4},
  1049  		{C: `openat 4 "bar" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5},
  1050  		{C: `fstat 5 <ptr>`, R: stat},
  1051  		{C: `close 4`},
  1052  		{C: `close 3`},
  1053  	})
  1054  }
  1055  
  1056  func (s *utilsSuite) TestSecureOpenPathSingleSegment(c *C) {
  1057  	stat := syscall.Stat_t{Mode: syscall.S_IFDIR}
  1058  	s.sys.InsertFstatResult("fstat 4 <ptr>", stat)
  1059  	fd, err := update.OpenPath("/foo")
  1060  	c.Assert(err, IsNil)
  1061  	defer s.sys.Close(fd)
  1062  	c.Assert(fd, Equals, 4)
  1063  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
  1064  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3},
  1065  		{C: `openat 3 "foo" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4},
  1066  		{C: `fstat 4 <ptr>`, R: stat},
  1067  		{C: `close 3`},
  1068  	})
  1069  }
  1070  
  1071  func (s *utilsSuite) TestSecureOpenPathRoot(c *C) {
  1072  	stat := syscall.Stat_t{Mode: syscall.S_IFDIR}
  1073  	s.sys.InsertFstatResult("fstat 3 <ptr>", stat)
  1074  	fd, err := update.OpenPath("/")
  1075  	c.Assert(err, IsNil)
  1076  	defer s.sys.Close(fd)
  1077  	c.Assert(fd, Equals, 3)
  1078  	c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{
  1079  		{C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3},
  1080  		{C: `fstat 3 <ptr>`, R: stat},
  1081  	})
  1082  }
  1083  
  1084  func (s *realSystemSuite) TestSecureOpenPathDirectory(c *C) {
  1085  	path := filepath.Join(c.MkDir(), "test")
  1086  	c.Assert(os.Mkdir(path, 0755), IsNil)
  1087  
  1088  	fd, err := update.OpenPath(path)
  1089  	c.Assert(err, IsNil)
  1090  	defer syscall.Close(fd)
  1091  
  1092  	// check that the file descriptor is for the expected path
  1093  	origDir, err := os.Getwd()
  1094  	c.Assert(err, IsNil)
  1095  	defer os.Chdir(origDir)
  1096  
  1097  	c.Assert(syscall.Fchdir(fd), IsNil)
  1098  	cwd, err := os.Getwd()
  1099  	c.Assert(err, IsNil)
  1100  	c.Check(cwd, Equals, path)
  1101  }
  1102  
  1103  func (s *realSystemSuite) TestSecureOpenPathRelativePath(c *C) {
  1104  	fd, err := update.OpenPath("relative/path")
  1105  	c.Check(fd, Equals, -1)
  1106  	c.Check(err, ErrorMatches, "path .* is not absolute")
  1107  }
  1108  
  1109  func (s *realSystemSuite) TestSecureOpenPathUncleanPath(c *C) {
  1110  	base := c.MkDir()
  1111  	path := filepath.Join(base, "test")
  1112  	c.Assert(os.Mkdir(path, 0755), IsNil)
  1113  
  1114  	fd, err := update.OpenPath(base + "//test")
  1115  	c.Check(fd, Equals, -1)
  1116  	c.Check(err, ErrorMatches, `cannot open path: cannot iterate over unclean path ".*//test"`)
  1117  
  1118  	fd, err = update.OpenPath(base + "/./test")
  1119  	c.Check(fd, Equals, -1)
  1120  	c.Check(err, ErrorMatches, `cannot open path: cannot iterate over unclean path ".*/./test"`)
  1121  
  1122  	fd, err = update.OpenPath(base + "/test/../test")
  1123  	c.Check(fd, Equals, -1)
  1124  	c.Check(err, ErrorMatches, `cannot open path: cannot iterate over unclean path ".*/test/../test"`)
  1125  }
  1126  
  1127  func (s *realSystemSuite) TestSecureOpenPathFile(c *C) {
  1128  	path := filepath.Join(c.MkDir(), "file.txt")
  1129  	c.Assert(ioutil.WriteFile(path, []byte("hello"), 0644), IsNil)
  1130  
  1131  	fd, err := update.OpenPath(path)
  1132  	c.Assert(err, IsNil)
  1133  	defer syscall.Close(fd)
  1134  
  1135  	// Check that the file descriptor matches the file.
  1136  	var pathStat, fdStat syscall.Stat_t
  1137  	c.Assert(syscall.Stat(path, &pathStat), IsNil)
  1138  	c.Assert(syscall.Fstat(fd, &fdStat), IsNil)
  1139  	c.Check(pathStat, Equals, fdStat)
  1140  }
  1141  
  1142  func (s *realSystemSuite) TestSecureOpenPathNotFound(c *C) {
  1143  	path := filepath.Join(c.MkDir(), "test")
  1144  
  1145  	fd, err := update.OpenPath(path)
  1146  	c.Check(fd, Equals, -1)
  1147  	c.Check(err, ErrorMatches, "no such file or directory")
  1148  }
  1149  
  1150  func (s *realSystemSuite) TestSecureOpenPathSymlink(c *C) {
  1151  	base := c.MkDir()
  1152  	dir := filepath.Join(base, "test")
  1153  	c.Assert(os.Mkdir(dir, 0755), IsNil)
  1154  
  1155  	symlink := filepath.Join(base, "symlink")
  1156  	c.Assert(os.Symlink(dir, symlink), IsNil)
  1157  
  1158  	fd, err := update.OpenPath(symlink)
  1159  	c.Check(fd, Equals, -1)
  1160  	c.Check(err, ErrorMatches, `".*" is a symbolic link`)
  1161  }
  1162  
  1163  func (s *realSystemSuite) TestSecureOpenPathSymlinkedParent(c *C) {
  1164  	base := c.MkDir()
  1165  	dir := filepath.Join(base, "dir1")
  1166  	symlink := filepath.Join(base, "symlink")
  1167  
  1168  	path := filepath.Join(dir, "dir2")
  1169  	symlinkedPath := filepath.Join(symlink, "dir2")
  1170  
  1171  	c.Assert(os.Mkdir(dir, 0755), IsNil)
  1172  	c.Assert(os.Symlink(dir, symlink), IsNil)
  1173  	c.Assert(os.Mkdir(path, 0755), IsNil)
  1174  
  1175  	fd, err := update.OpenPath(symlinkedPath)
  1176  	c.Check(fd, Equals, -1)
  1177  	c.Check(err, ErrorMatches, "not a directory")
  1178  }