From 31b6deca70ddabbc86ab2793ddf16e500ae66fde Mon Sep 17 00:00:00 2001 From: Pawel Stolowski Date: Wed, 27 May 2020 16:18:53 +0200 Subject: [PATCH] cmd/snap-preseed, systemd: fix handling of fuse.squashfuse when preseeding (2/3) (#8709) Fix handling of fuse.squashfuse when preseeding: fuse needs to be used when mounting snapd/core in snap-preseed, and then also when mounting seeded snaps during preseeding in snapd, but generated mount units should use squashfs. This affects preseeding inside lxd containers. --- cmd/snap-preseed/main_test.go | 5 ++- cmd/snap-preseed/preseed_linux.go | 9 +++-- osutil/squashfs/fstype.go | 11 ++++-- sanity/squashfs.go | 5 +-- systemd/emulation.go | 11 +++++- systemd/export_test.go | 2 +- systemd/systemd.go | 66 ++++++++++++++++++------------- systemd/systemd_test.go | 6 +-- 8 files changed, 71 insertions(+), 44 deletions(-) diff --git a/cmd/snap-preseed/main_test.go b/cmd/snap-preseed/main_test.go index 0060f8bc834..0922ecd5403 100644 --- a/cmd/snap-preseed/main_test.go +++ b/cmd/snap-preseed/main_test.go @@ -36,6 +36,7 @@ import ( "github.com/snapcore/snapd/cmd/snap-preseed" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/osutil/squashfs" apparmor_sandbox "github.com/snapcore/snapd/sandbox/apparmor" "github.com/snapcore/snapd/seed" "github.com/snapcore/snapd/snap" @@ -53,6 +54,8 @@ type startPreseedSuite struct { func (s *startPreseedSuite) SetUpTest(c *C) { s.BaseTest.SetUpTest(c) + restore := squashfs.MockNeedsFuse(false) + s.BaseTest.AddCleanup(restore) } func (s *startPreseedSuite) TearDownTest(c *C) { @@ -198,7 +201,7 @@ func (s *startPreseedSuite) TestRunPreseedHappy(c *C) { c.Assert(mockMountCmd.Calls(), HasLen, 1) // note, tmpDir, targetSnapdRoot are contactenated again cause we're not really chrooting in the test // and mocking dirs.RootDir - c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)}) + c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "squashfs", "-o", "ro,x-gdu.hide", "/a/core.snap", filepath.Join(tmpDir, targetSnapdRoot)}) c.Assert(mockTargetSnapd.Calls(), HasLen, 1) c.Check(mockTargetSnapd.Calls()[0], DeepEquals, []string{"snapd"}) diff --git a/cmd/snap-preseed/preseed_linux.go b/cmd/snap-preseed/preseed_linux.go index 8259fb72957..e0a480c283b 100644 --- a/cmd/snap-preseed/preseed_linux.go +++ b/cmd/snap-preseed/preseed_linux.go @@ -31,6 +31,7 @@ import ( "github.com/snapcore/snapd/cmd/cmdutil" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/osutil/squashfs" "github.com/snapcore/snapd/seed" "github.com/snapcore/snapd/strutil" "github.com/snapcore/snapd/timings" @@ -78,12 +79,13 @@ func checkChroot(preseedChroot string) error { } // non empty required indicates missing mountpoint(s) if len(required) > 0 { - sorted := []string{""} + var sorted []string for path := range required { sorted = append(sorted, path) } sort.Strings(sorted) - return fmt.Errorf("cannot preseed without the following mountpoints:%s", strings.Join(sorted, "\n - ")) + parts := append([]string{""}, sorted...) + return fmt.Errorf("cannot preseed without the following mountpoints:%s", strings.Join(parts, "\n - ")) } path := filepath.Join(preseedChroot, "/sys/kernel/security/apparmor") @@ -198,7 +200,8 @@ func prepareChroot(preseedChroot string) (func(), error) { } } - cmd := exec.Command("mount", "-t", "squashfs", coreSnapPath, where) + fstype, fsopts := squashfs.FsType() + cmd := exec.Command("mount", "-t", fstype, "-o", strings.Join(fsopts, ","), coreSnapPath, where) if err := cmd.Run(); err != nil { removeMountpoint() return nil, fmt.Errorf("cannot mount %s at %s in preseed mode: %v ", coreSnapPath, where, err) diff --git a/osutil/squashfs/fstype.go b/osutil/squashfs/fstype.go index 53bd77d00c3..1362399b0e1 100644 --- a/osutil/squashfs/fstype.go +++ b/osutil/squashfs/fstype.go @@ -62,11 +62,16 @@ func NeedsFuse() bool { return needsFuseImpl() } +// StandardOptions returns base squashfs options. +func StandardOptions() []string { + return []string{"ro", "x-gdu.hide"} +} + // FsType returns what fstype to use for squashfs mounts and what // mount options -func FsType() (fstype string, options []string, err error) { +func FsType() (fstype string, options []string) { fstype = "squashfs" - options = []string{"ro", "x-gdu.hide"} + options = StandardOptions() if NeedsFuse() { options = append(options, "allow_other") @@ -80,5 +85,5 @@ func FsType() (fstype string, options []string, err error) { } } - return fstype, options, nil + return fstype, options } diff --git a/sanity/squashfs.go b/sanity/squashfs.go index 2036f111340..678b075b54d 100644 --- a/sanity/squashfs.go +++ b/sanity/squashfs.go @@ -108,10 +108,7 @@ func checkSquashfsMount() error { } // the fstype can be squashfs or fuse.{snap,squash}fuse - fstype, _, err := squashfs.FsType() - if err != nil { - return err - } + fstype, _ := squashfs.FsType() options := []string{"-t", fstype} if selinux.ProbedLevel() != selinux.Unsupported { if ctx := selinux.SnapMountContext(); ctx != "" { diff --git a/systemd/emulation.go b/systemd/emulation.go index 45e7446dd60..e8ebc579f2e 100644 --- a/systemd/emulation.go +++ b/systemd/emulation.go @@ -31,6 +31,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/osutil/squashfs" ) type emulation struct { @@ -102,12 +103,18 @@ func (s *emulation) AddMountUnitFile(snapName, revision, what, where, fstype str return "", fmt.Errorf("bind-mounted directory is not supported in emulation mode") } - mountUnitName, actualFsType, options, err := writeMountUnitFile(snapName, revision, what, where, fstype) + // In emulation mode actualFsType is the fs we want to use to manually mount + // the snap below, but fstype is used for the created mount unit. + // This means that when preseeding in a lxd container, the snap will be + // mounted with fuse, but mount unit will use squashfs. + mountUnitOptions := append(fsMountOptions(fstype), squashfs.StandardOptions()...) + mountUnitName, err := writeMountUnitFile(snapName, revision, what, where, fstype, mountUnitOptions) if err != nil { return "", err } - cmd := exec.Command("mount", "-t", actualFsType, what, where, "-o", strings.Join(options, ",")) + actualFsType, actualOptions := actualFsTypeAndMountOptions(fstype) + cmd := exec.Command("mount", "-t", actualFsType, what, where, "-o", strings.Join(actualOptions, ",")) if out, err := cmd.CombinedOutput(); err != nil { return "", fmt.Errorf("cannot mount %s (%s) at %s in preseed mode: %s; %s", what, actualFsType, where, err, string(out)) } diff --git a/systemd/export_test.go b/systemd/export_test.go index 58355cd289b..c24eabd3e78 100644 --- a/systemd/export_test.go +++ b/systemd/export_test.go @@ -55,7 +55,7 @@ func MockOsutilIsMounted(f func(path string) (bool, error)) func() { } } -func MockSquashFsType(f func() (string, []string, error)) func() { +func MockSquashFsType(f func() (string, []string)) func() { old := squashfsFsType squashfsFsType = f return func() { diff --git a/systemd/systemd.go b/systemd/systemd.go index 1e5bb8f4d8b..7dd5c230aad 100644 --- a/systemd/systemd.go +++ b/systemd/systemd.go @@ -743,28 +743,7 @@ func MountUnitPath(baseDir string) string { var squashfsFsType = squashfs.FsType -func writeMountUnitFile(snapName, revision, what, where, fstype string) (mountUnitName, actualFsType string, options []string, err error) { - options = []string{"nodev"} - actualFsType = fstype - if fstype == "squashfs" { - newFsType, newOptions, err := squashfsFsType() - if err != nil { - return "", "", nil, err - } - options = append(options, newOptions...) - actualFsType = newFsType - if selinux.ProbedLevel() != selinux.Unsupported { - if mountCtx := selinux.SnapMountContext(); mountCtx != "" { - options = append(options, "context="+mountCtx) - } - } - } - if osutil.IsDirectory(what) { - options = append(options, "bind") - actualFsType = "none" - } - - c := fmt.Sprintf(`[Unit] +var mountUnitTemplate = `[Unit] Description=Mount unit for %s, revision %s Before=snapd.service @@ -777,25 +756,58 @@ LazyUnmount=yes [Install] WantedBy=multi-user.target -`, snapName, revision, what, where, actualFsType, strings.Join(options, ",")) +` +func writeMountUnitFile(snapName, revision, what, where, fstype string, options []string) (mountUnitName string, err error) { + content := fmt.Sprintf(mountUnitTemplate, snapName, revision, what, where, fstype, strings.Join(options, ",")) mu := MountUnitPath(where) - mountUnitName, err = filepath.Base(mu), osutil.AtomicWriteFile(mu, []byte(c), 0644, 0) + mountUnitName, err = filepath.Base(mu), osutil.AtomicWriteFile(mu, []byte(content), 0644, 0) if err != nil { - return "", "", nil, err + return "", err } + return mountUnitName, nil +} - return mountUnitName, actualFsType, options, err +func fsMountOptions(fstype string) []string { + options := []string{"nodev"} + if fstype == "squashfs" { + if selinux.ProbedLevel() != selinux.Unsupported { + if mountCtx := selinux.SnapMountContext(); mountCtx != "" { + options = append(options, "context="+mountCtx) + } + } + } + return options +} + +// actualFsTypeAndMountOptions returns filesystem type and options to actually +// mount the given fstype at runtime, i.e. it determines if fuse should be used +// for squashfs. +func actualFsTypeAndMountOptions(fstype string) (actualFsType string, options []string) { + options = fsMountOptions(fstype) + actualFsType = fstype + if fstype == "squashfs" { + newFsType, newOptions := squashfsFsType() + options = append(options, newOptions...) + actualFsType = newFsType + } + return actualFsType, options } func (s *systemd) AddMountUnitFile(snapName, revision, what, where, fstype string) (string, error) { daemonReloadLock.Lock() defer daemonReloadLock.Unlock() - mountUnitName, _, _, err := writeMountUnitFile(snapName, revision, what, where, fstype) + actualFsType, options := actualFsTypeAndMountOptions(fstype) + if osutil.IsDirectory(what) { + options = append(options, "bind") + actualFsType = "none" + } + mountUnitName, err := writeMountUnitFile(snapName, revision, what, where, actualFsType, options) if err != nil { return "", err } + // we need to do a daemon-reload here to ensure that systemd really // knows about this new mount unit file if err := s.daemonReloadNoLock(); err != nil { diff --git a/systemd/systemd_test.go b/systemd/systemd_test.go index 42184c387d2..05b7973d2cc 100644 --- a/systemd/systemd_test.go +++ b/systemd/systemd_test.go @@ -641,7 +641,7 @@ Before=snapd.service What=%s Where=/snap/snapname/123 Type=squashfs -Options=nodev,ro,x-gdu.hide,context=system_u:object_r:snappy_snap_t:s0 +Options=nodev,context=system_u:object_r:snappy_snap_t:s0,ro,x-gdu.hide LazyUnmount=yes [Install] @@ -949,7 +949,7 @@ func (s *SystemdTestSuite) TestPreseedModeAddMountUnit(c *C) { func (s *SystemdTestSuite) TestPreseedModeAddMountUnitWithFuse(c *C) { sysd := NewEmulationMode(dirs.GlobalRootDir) - restore := MockSquashFsType(func() (string, []string, error) { return "fuse.squashfuse", []string{"a,b,c"}, nil }) + restore := MockSquashFsType(func() (string, []string) { return "fuse.squashfuse", []string{"a,b,c"} }) defer restore() mockMountCmd := testutil.MockCommand(c, "mount", "") @@ -963,7 +963,7 @@ func (s *SystemdTestSuite) TestPreseedModeAddMountUnitWithFuse(c *C) { defer os.Remove(mountUnitName) c.Check(mockMountCmd.Calls()[0], DeepEquals, []string{"mount", "-t", "fuse.squashfuse", mockSnapPath, "/snap/snapname/123", "-o", "nodev,a,b,c"}) - c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "fuse.squashfuse", "nodev,a,b,c")) + c.Check(filepath.Join(dirs.SnapServicesDir, mountUnitName), testutil.FileEquals, fmt.Sprintf(unitTemplate[1:], mockSnapPath, "squashfs", "nodev,ro,x-gdu.hide")) } func (s *SystemdTestSuite) TestPreseedModeMountError(c *C) {