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