Skip to content

Commit

Permalink
cmd/snap-preseed, systemd: fix handling of fuse.squashfuse when prese…
Browse files Browse the repository at this point in the history
…eding (2/3) (canonical#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.
  • Loading branch information
stolowski authored May 27, 2020
1 parent 140f1e7 commit 31b6dec
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 44 deletions.
5 changes: 4 additions & 1 deletion cmd/snap-preseed/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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) {
Expand Down Expand Up @@ -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"})
Expand Down
9 changes: 6 additions & 3 deletions cmd/snap-preseed/preseed_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
11 changes: 8 additions & 3 deletions osutil/squashfs/fstype.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -80,5 +85,5 @@ func FsType() (fstype string, options []string, err error) {
}
}

return fstype, options, nil
return fstype, options
}
5 changes: 1 addition & 4 deletions sanity/squashfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand Down
11 changes: 9 additions & 2 deletions systemd/emulation.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/osutil/squashfs"
)

type emulation struct {
Expand Down Expand Up @@ -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))
}
Expand Down
2 changes: 1 addition & 1 deletion systemd/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
66 changes: 39 additions & 27 deletions systemd/systemd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions systemd/systemd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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", "")
Expand All @@ -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) {
Expand Down

0 comments on commit 31b6dec

Please sign in to comment.