Skip to content

Commit

Permalink
fuse: automatically use squashfuse for images, deprecate --sif-fuse
Browse files Browse the repository at this point in the history
Deprecate the explicit `--sif-fuse` flag and `sif fuse` directive for
`singularity.conf`. These were previously used to enable experimental
FUSE mount of SIF/SquashFS containers.

Modify image handling so that we now try squashfuse mounts
automatically, with fall back to temporary sandbox extraction, when:

* squashfs kernel mounts have been disabled in `singularity.conf`
* we are running in a non-setuid / user namespace flow.

Fixes sylabs#2216
  • Loading branch information
dtrudg committed Dec 18, 2023
1 parent 320f254 commit a1c9882
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 38 deletions.
20 changes: 16 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## Changes Since Last Release

### Changed defaults / behaviours

- In native mode, SIF/SquashFS container images will now be mounted with
squashfuse when kernel mounts are disabled in `singularity.conf`, or cannot be
used (non-setuid / user namespace workflow). If the FUSE mount fails,
Singularity will fall back to extracting the container to a temporary sandbox
in order to run it.

### New Features & Functionality

- The `registry login` and `registry logout` commands now support a `--authfile
Expand All @@ -27,10 +35,14 @@
executable, then the `run` / `exec` / `shell` commands in `--oci` mode can be
given the `--app <appname>` flag, and will automatically invoke the relevant
SCIF command.
- SIF/SquashFS container images can now be mounted using FUSE in all native mode
flows, including setuid mode. To enable, use the `--sif-fuse` flag, or set
`sif fuse = yes` in `singularity.conf`. Overlay partitions and extfs images
are not yet supported.

### Deprecated Functionality

- The experimental `--sif-fuse` flag, and `sif fuse` directive in
`singularity.conf` are deprecated. The flag and directive were used to enable
experimental mounting of SIF/SquashFS container images with FUSE in prior
versions of Singularity. From 4.1, FUSE mounts are used automatically when
kernel mounts are disabled / not available.

### Bug Fixes

Expand Down
1 change: 1 addition & 0 deletions cmd/internal/cli/action_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ var actionSIFFUSEFlag = cmdline.Flag{
Name: "sif-fuse",
Usage: "attempt FUSE mount of SIF",
EnvKeys: []string{"SIF_FUSE"},
Deprecated: "FUSE mounts are now used automatically when kernel mounts are disabled / unavailable.",
}

// --proot (hidden)
Expand Down
9 changes: 6 additions & 3 deletions e2e/actions/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2507,7 +2507,9 @@ func (c actionTests) actionCompat(t *testing.T) {
}
}

// actionSquashfuse tests that squashfuse SIF mount works.
// actionSquashfuse tests that squashfuse SIF mount works. Currently forced here
// via deprecated `--sif-fuse` flag as this is convenient to include non-userns
// profiles without changing global config.
func (c actionTests) actionSIFFUSE(t *testing.T) {
require.Command(t, "squashfuse")
require.Command(t, "fusermount")
Expand Down Expand Up @@ -2536,12 +2538,12 @@ func (c actionTests) actionSIFFUSE(t *testing.T) {
}
}

// Verify that the FUSE mounts, and the CleanupHost() process are not seen when
// Verify that the FUSE mounts, and the PostStartHost/CleanupHost() processes are not seen when
// --sif-fuse should not be in effect.
func (c actionTests) actionNoSIFFUSE(t *testing.T) {
e2e.EnsureImage(t, c.env)

for _, p := range e2e.NativeProfiles {
for _, p := range []e2e.Profile{e2e.RootProfile, e2e.UserProfile} {
c.env.RunSingularity(
t,
e2e.AsSubtest(p.String()),
Expand All @@ -2552,6 +2554,7 @@ func (c actionTests) actionNoSIFFUSE(t *testing.T) {
e2e.ExpectExit(
0,
e2e.ExpectError(e2e.UnwantedContainMatch, "squashfuse"),
e2e.ExpectError(e2e.UnwantedContainMatch, "PostStartHost()"),
e2e.ExpectError(e2e.UnwantedContainMatch, "CleanupHost()"),
),
)
Expand Down
8 changes: 6 additions & 2 deletions e2e/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,8 @@ func (c configTests) configGlobal(t *testing.T) {
profile: e2e.UserProfile,
directive: "allow kernel squashfs",
directiveValue: "no",
exit: 255,
exit: 0,
resultOp: e2e.ExpectError(e2e.ContainMatch, "Mounting image with FUSE"),
},
{
name: "AllowKernelSquashfsYes_Container",
Expand All @@ -542,6 +543,7 @@ func (c configTests) configGlobal(t *testing.T) {
directive: "allow kernel squashfs",
directiveValue: "yes",
exit: 0,
resultOp: e2e.ExpectError(e2e.UnwantedContainMatch, "Mounting image with FUSE"),
},
// Standalone ext3 rootfs
{
Expand Down Expand Up @@ -635,7 +637,8 @@ func (c configTests) configGlobal(t *testing.T) {
profile: e2e.UserProfile,
directive: "allow kernel squashfs",
directiveValue: "no",
exit: 255,
exit: 0,
resultOp: e2e.ExpectError(e2e.ContainMatch, "Mounting image with FUSE"),
},
{
name: "AllowKernelSquashfsYes_SIF",
Expand All @@ -644,6 +647,7 @@ func (c configTests) configGlobal(t *testing.T) {
directive: "allow kernel squashfs",
directiveValue: "yes",
exit: 0,
resultOp: e2e.ExpectError(e2e.UnwantedContainMatch, "Mounting image with FUSE"),
},
// Encrypted squashFS rootfs in SIF
{
Expand Down
51 changes: 24 additions & 27 deletions internal/pkg/runtime/launcher/native/launcher_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,9 +977,8 @@ func (l *Launcher) setCgroups(instanceName string) error {
return nil
}

// PrepareImage perfoms any image preparation required before execution.
// This is currently limited to extraction or FUSE mount when using the user namespace,
// and activating any image driver plugins that might handle the image mount.
// PrepareImage performs any image preparation required before execution.
// This is currently limited to extraction or FUSE mount.
func (l *Launcher) prepareImage(c context.Context, image string) error {
if strings.HasPrefix(image, "instance://") {
return nil
Expand All @@ -1002,20 +1001,20 @@ func (l *Launcher) prepareImage(c context.Context, image string) error {
case imgutil.SANDBOX:
return nil
case imgutil.SQUASHFS:
if isUserNs || l.cfg.SIFFUSE {
return l.prepareSquashfs(c, img, l.cfg.SIFFUSE)
if !l.engineConfig.File.AllowContainerSquashfs || isUserNs || l.cfg.SIFFUSE {
return l.prepareSquashfs(c, img)
}
// setuid, kernel squashfs permitted, fuse not requested - no action needed
return nil
case imgutil.ENCRYPTSQUASHFS:
if isUserNs || l.cfg.SIFFUSE {
if !l.engineConfig.File.AllowContainerSquashfs || isUserNs || l.cfg.SIFFUSE {
return fmt.Errorf("encrypted SIF files are only supported in setuid mode, with kernel mounts")
}
// setuid, kernel squashfs permitted, fuse not requested - no action needed
return nil
case imgutil.EXT3:
if isUserNs || l.cfg.SIFFUSE {
return l.prepareExtfs(c, img, l.cfg.SIFFUSE)
return l.prepareExtfs(c, img)
}
// setuid, kernel extfs permitted, fuse not requested - no action needed
return nil
Expand All @@ -1024,33 +1023,31 @@ func (l *Launcher) prepareImage(c context.Context, image string) error {
return fmt.Errorf("unsupported image rootfs type: %d", part.Type)
}

func (l *Launcher) prepareSquashfs(ctx context.Context, img *imgutil.Image, tryFuse bool) error {
func (l *Launcher) prepareSquashfs(ctx context.Context, img *imgutil.Image) error {
tempDir, imageDir, err := mkContainerDirs()
if err != nil {
return err
}

if tryFuse {
allowOther := false
// In fakeroot mode, the users is able to assume a subuid/subgid, so allow
// others to access the FUSE mount.
if l.cfg.Fakeroot {
allowOther = true
}

sylog.Infof("Mounting image with FUSE.")
err = squashfuseMount(ctx, img, imageDir, allowOther)
if err == nil {
l.engineConfig.SetImage(imageDir)
l.engineConfig.SetImageFuse(true)
l.engineConfig.SetDeleteTempDir(tempDir)
l.generator.AddProcessEnv("SINGULARITY_CONTAINER", imageDir)
return nil
}
allowOther := false
// In fakeroot mode, the users is able to assume a subuid/subgid, so allow
// others to access the FUSE mount.
if l.cfg.Fakeroot {
allowOther = true
}

sylog.Warningf("squashfuse mount failed, falling back to extraction: %v", err)
sylog.Infof("Mounting image with FUSE.")
err = squashfuseMount(ctx, img, imageDir, allowOther)
if err == nil {
l.engineConfig.SetImage(imageDir)
l.engineConfig.SetImageFuse(true)
l.engineConfig.SetDeleteTempDir(tempDir)
l.generator.AddProcessEnv("SINGULARITY_CONTAINER", imageDir)
return nil
}

sylog.Warningf("squashfuse mount failed, falling back to extraction: %v", err)

if l.cfg.NoTmpSandbox || !l.engineConfig.File.TmpSandboxAllowed {
return fmt.Errorf("unpacking image to temporary sandbox dir required, but is prohibited by 'tmp sandbox = no' in singularity.conf or --no-tmp-sandbox command-line flag")
}
Expand All @@ -1069,7 +1066,7 @@ func (l *Launcher) prepareSquashfs(ctx context.Context, img *imgutil.Image, tryF
return fmt.Errorf("extraction failed: %v", err)
}

func (l *Launcher) prepareExtfs(_ context.Context, _ *imgutil.Image, _ bool) error {
func (l *Launcher) prepareExtfs(_ context.Context, _ *imgutil.Image) error {
// TODO - Enable fuse2fs handling
return fmt.Errorf("extfs images can only be run in setuid mode with kernel extfs mounts enabled")
}
Expand Down
5 changes: 3 additions & 2 deletions pkg/util/singularityconf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ allow container dir = {{ if eq .AllowContainerDir true }}yes{{ else }}no{{ end }
# ALLOW KERNEL SQUASHFS: [BOOL]
# DEFAULT: yes
# If set to no, Singularity will not perform any kernel mounts of squashfs filesystems.
# This affects both stand-alone image files and filesystems embedded in a SIF file.
# Instead, for SIF / SquashFS containers, a squashfuse mount will be attempted, with
# extraction to a temporary sandbox directory if this fails.
# Applicable to setuid mode only.
allow kernel squashfs = {{ if eq .AllowKernelSquashfs true }}yes{{ else }}no{{ end }}
Expand Down Expand Up @@ -523,7 +524,7 @@ systemd cgroups = {{ if eq .SystemdCgroups true }}yes{{ else }}no{{ end }}
# SIF FUSE: [BOOL]
# DEFAULT: no
# EXPERIMENTAL
# DEPRECATED - FUSE mounts are now used automatically when kernel mounts are disabled / unavailable.
# Whether to try mounting SIF images with Squashfuse by default.
# Applies only to unprivileged / user namespace flows. Requires squashfuse and
# fusermount on PATH. Will fall back to extracting the SIF on failure.
Expand Down

0 comments on commit a1c9882

Please sign in to comment.