diff --git a/pkg/squashfs/squashfs.go b/pkg/squashfs/squashfs.go index aa710d9fb..2e9c91ae6 100644 --- a/pkg/squashfs/squashfs.go +++ b/pkg/squashfs/squashfs.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "strings" "sync" "syscall" @@ -71,7 +72,7 @@ func (eps *ExcludePaths) AddInclude(orig string, isDir bool) { } delete(eps.exclude, p) - p = path.Dir(p) + p = filepath.Dir(p) } // now add it to the list of includes, so we don't accidentally re-add @@ -214,6 +215,44 @@ func maybeKernelSquashMount(squashFile, extractDir string) (bool, error) { return false, kernelSquashMountFailed } +func findSquashfusePath() string { + if p := which("squashfuse_ll"); p != "" { + return p + } + return which("squashfuse") +} + +func squashFuse(cmdPath, squashFile, extractDir string) (*exec.Cmd, error) { + // given extractDir of path/to/some/dir[/], log to path/to/some/.dir-squashfs.log + extractDir = strings.TrimSuffix(extractDir, "/") + + var cmdOut io.Writer + var err error + var nilCmd *exec.Cmd + + logf := filepath.Join(path.Dir(extractDir), "."+filepath.Base(extractDir)+"-squashfuse.log") + if cmdOut, err = os.OpenFile(logf, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644); err != nil { + log.Infof("Failed to open %s for write: %v", logf, err) + return nilCmd, err + } + + // It would be nice to only enable debug (or maybe to only log to file at all) + // if 'stacker --debug', but we do not have access to that info here. + // to debug squashfuse, use "allow_other,debug" + cmd := exec.Command("squashfuse", "-f", "-o", "allow_other,debug", squashFile, extractDir) + cmd.Stdin = nil + cmd.Stdout = cmdOut + cmd.Stderr = cmdOut + cmdOut.Write([]byte(fmt.Sprintf("# %s\n", strings.Join(cmd.Args, " ")))) + log.Debugf("Extracting %s -> %s with squashfuse [%s]", squashFile, extractDir, logf) + err = cmd.Start() + if err != nil { + return nilCmd, err + } + + return cmd, nil +} + func ExtractSingleSquash(squashFile string, extractDir string, storageType string) error { err := os.MkdirAll(extractDir, 0755) if err != nil { @@ -226,35 +265,11 @@ func ExtractSingleSquash(squashFile string, extractDir string, storageType strin return err } - findSqfusePath := func() string { - if p := which("squashfuse_ll"); p != "" { - return p - } - return which("squashfuse") + if sqfuse := findSquashfusePath(); sqfuse != "" { + _, err = squashFuse(sqfuse, squashFile, extractDir) + return err } - - if sqfuse := findSqfusePath(); sqfuse != "" { - // given extractDir of path/to/some/dir[/], log to path/to/some/.dir-squashfs.log - extractDir := strings.TrimSuffix(extractDir, "/") - - var cmdOut io.Writer - logf := path.Join(path.Dir(extractDir), "."+path.Base(extractDir)+"-squashfuse.log") - if cmdOut, err = os.OpenFile(logf, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644); err != nil { - log.Infof("Failed to open %s for write: %v", logf, err) - return err - } - - // It would be nice to only enable debug (or maybe to only log to file at all) - // if 'stacker --debug', but we do not have access to that info here. - // to debug squashfuse, use "allow_other,debug" - cmd := exec.Command(sqfuse, "-f", "-o", "allow_other,debug", squashFile, extractDir) - cmd.Stdin = nil - cmd.Stdout = cmdOut - cmd.Stderr = cmdOut - cmdOut.Write([]byte(fmt.Sprintf("# %s\n", strings.Join(cmd.Args, " ")))) - log.Debugf("Extracting %s -> %s with squashfuse [%s]", squashFile, extractDir, logf) - return cmd.Start() - } else if p := which("unsquashfs"); p != "" { + if p := which("unsquashfs"); p != "" { log.Debugf("Extracting %s -> %s with unsquashfs -f -d %s %s", extractDir, squashFile, extractDir, squashFile) cmd := exec.Command("unsquashfs", "-f", "-d", extractDir, squashFile) cmd.Stdout = os.Stdout @@ -297,7 +312,7 @@ func whichSearch(name string, paths []string) string { var search []string if strings.ContainsRune(name, os.PathSeparator) { - if path.IsAbs(name) { + if filepath.IsAbs(name) { search = []string{name} } else { search = []string{"./" + name} @@ -305,7 +320,7 @@ func whichSearch(name string, paths []string) string { } else { search = []string{} for _, p := range paths { - search = append(search, path.Join(p, name)) + search = append(search, filepath.Join(p, name)) } } diff --git a/pkg/squashfs/verity.go b/pkg/squashfs/verity.go index 59411832f..a83daf1c9 100644 --- a/pkg/squashfs/verity.go +++ b/pkg/squashfs/verity.go @@ -71,6 +71,7 @@ import ( "strconv" "strings" "syscall" + "time" "unsafe" "github.com/anuvu/squashfs" @@ -78,6 +79,7 @@ import ( "github.com/martinjungblut/go-cryptsetup" "github.com/pkg/errors" "golang.org/x/sys/unix" + "stackerbuild.io/stacker/pkg/log" "stackerbuild.io/stacker/pkg/mount" ) @@ -200,7 +202,103 @@ func verityName(p string) string { return fmt.Sprintf("%s-%s", p, veritySuffix) } -func Mount(squashfs string, mountpoint string, rootHash string) error { +func fileChanged(a os.FileInfo, path string) bool { + b, err := os.Lstat(path) + if err != nil { + return true + } + return !os.SameFile(a, b) +} + +// Mount a filesystem as container root, without host root +// privileges. We do this using squashfuse. +func GuestMount(squashFile string, mountpoint string) error { + if isMountpoint(mountpoint) { + return fmt.Errorf("%s is already mounted") + } + + abs, err := filepath.Abs(squashFile) + if err != nil { + return errors.Errorf("Failed to get absolute path for %s: %v", squashFile, err) + } + squashFile = abs + + abs, err = filepath.Abs(mountpoint) + if err != nil { + return errors.Errorf("Failed to get absolute path for %s: %v", mountpoint, err) + } + mountpoint = abs + + cmdPath := findSquashfusePath() + if cmdPath == "" { + return fmt.Errorf("no squashfs binary found") + } + fiPre, err := os.Lstat(mountpoint) + if err != nil { + return fmt.Errorf("Failed stat'ing %q: %w", mountpoint, err) + } + if fiPre.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("Refusing to mount onto a symbolic linkd") + } + + cmd, err := squashFuse("", squashFile, mountpoint) + if err != nil { + return err + } + err = cmd.Process.Release() + if err != nil { + return fmt.Errorf("Failed releasing squashfuse process: %w", err) + } + + for count := 0; !fileChanged(fiPre, mountpoint); count++ { + if count % 10 == 0 { + log.Debugf("%s is not yet mounted...\n", mountpoint) + } + time.Sleep(time.Duration(100 * time.Millisecond)) + } + + return nil +} + +func isMountpoint(dest string) bool { + mounted, err := mount.IsMountpoint(dest) + return err == nil && mounted +} + +func amHostRoot() bool { + // if not uid 0, not host root + // if uid_map doesn't map 0 to 0, not host root + if os.Geteuid() != 0 { + return false + } + bytes, err := os.ReadFile("/proc/self/uid_map") + if err != nil { + return false + } + lines := string(bytes) + if len(lines) != 1 { + return false + } + words := strings.Fields(lines) + if len(words) != 3 || words[0] != "0" || words[1] != "0" { + return false + } + + return true +} + +func Mount(squashfs, mountpoint, rootHash string) error { + if !amHostRoot() || !tryKernelMountSquash { + return GuestMount(squashfs, mountpoint) + } + err := HostMount(squashfs, mountpoint, rootHash) + if err == nil || os.IsPermission(err) { + return err + } + return GuestMount(squashfs, mountpoint) +} + +func HostMount(squashfs string, mountpoint string, rootHash string) error { fi, err := os.Stat(squashfs) if err != nil { return errors.WithStack(err)