Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package apptainer for openSUSE:Factory checked in at 2023-04-28 16:23:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/apptainer (Old) and /work/SRC/openSUSE:Factory/.apptainer.new.1533 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "apptainer" Fri Apr 28 16:23:41 2023 rev:17 rq:1083268 version:1.1.8 Changes: -------- --- /work/SRC/openSUSE:Factory/apptainer/apptainer.changes 2023-03-29 23:28:07.343752744 +0200 +++ /work/SRC/openSUSE:Factory/.apptainer.new.1533/apptainer.changes 2023-04-28 16:24:39.822463887 +0200 @@ -1,0 +2,19 @@ +Thu Apr 27 12:59:22 UTC 2023 - Christian Goll <cg...@suse.com> + +- Included a fix for CVE-2023-30549 which is a vulnerability in setuid-root + installations of Apptainer iwhich was not active in the recent openSUSE + packages. Still this is included for completenss. The fix adds allow + setuid-mount configuration options encrypted, squashfs, and extfs, and makes + the default for extfs be "no". That disables the use of extfs mounts + including for overlays or binds while in the setuid-root mode, while leaving + it enabled for unprivileged user namespace mode. The default for encrypted + and squashfs is "yes". +- Other bug fixes: + * Fix loop device 'no such device or address' spurious errors when using shared + loop devices. + * Add xino=on mount option for writable kernel overlay mount points to fix + inode numbers consistency after kernel cache flush (not applicable to + fuse-overlayfs). + + +------------------------------------------------------------------- Old: ---- apptainer-1.1.7.tar.gz New: ---- apptainer-1.1.8.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ apptainer.spec ++++++ --- /var/tmp/diff_new_pack.sMCmqG/_old 2023-04-28 16:24:43.114483102 +0200 +++ /var/tmp/diff_new_pack.sMCmqG/_new 2023-04-28 16:24:43.118483125 +0200 @@ -25,7 +25,7 @@ License: BSD-3-Clause-LBNL Group: Productivity/Clustering/Computing Name: apptainer -Version: 1.1.7 +Version: 1.1.8 Release: 0 # https://spdx.org/licenses/BSD-3-Clause-LBNL.html URL: https://apptainer.org ++++++ apptainer-1.1.7.tar.gz -> apptainer-1.1.8.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/CHANGELOG.md new/apptainer-1.1.8/CHANGELOG.md --- old/apptainer-1.1.7/CHANGELOG.md 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/CHANGELOG.md 2023-04-25 17:50:20.000000000 +0200 @@ -5,6 +5,31 @@ and re-branded as Apptainer. For older changes see the [archived Singularity change log](https://github.com/apptainer/singularity/blob/release-3.8/CHANGELOG.md). +## v1.1.8 - \[2023-04-25\] + +### Security fix + +- Included a fix for [CVE-2023-30549](https://github.com/sylabs/scs-library-client/security/advisories/GHSA-7p8m-22h4-9pj7) + which is a vulnerability in setuid-root installations of Apptainer + and Singularity that causes an elevation in severity of an existing + ext4 filesystem driver vulnerability that is unpatched in several + older but still actively supported operating systems including RHEL7, + Debian 10, Ubuntu 18.04 and Ubuntu 20.04. + The fix adds `allow setuid-mount` configuration options `encrypted`, + `squashfs`, and `extfs`, and makes the default for `extfs` be "no". + That disables the use of extfs mounts including for overlays or + binds while in the setuid-root mode, while leaving it enabled for + unprivileged user namespace mode. + The default for `encrypted` and `squashfs` is "yes". + +### Other changes + +- Fix loop device 'no such device or address' spurious errors when using shared + loop devices. +- Remove unwanted colors to STDERR. +- Add `xino=on` mount option for writable kernel overlay mount points to fix + inode numbers consistency after kernel cache flush (not applicable to fuse-overlayfs). + ## v1.1.7 - \[2023-03-28\] ### Changes since last release diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/INSTALL.md new/apptainer-1.1.8/INSTALL.md --- old/apptainer-1.1.7/INSTALL.md 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/INSTALL.md 2023-04-25 17:50:20.000000000 +0200 @@ -137,7 +137,7 @@ for example: ```sh -git checkout v1.1.7 +git checkout v1.1.8 ``` ## Compiling Apptainer @@ -259,7 +259,7 @@ <!-- markdownlint-disable MD013 --> ```sh -VERSION=1.1.7 # this is the apptainer version, change as you need +VERSION=1.1.8 # this is the apptainer version, change as you need # Fetch the source wget https://github.com/apptainer/apptainer/releases/download/v${VERSION}/apptainer-${VERSION}.tar.gz ``` @@ -308,7 +308,7 @@ <!-- markdownlint-disable MD013 --> ```sh -VERSION=1.1.7 # this is the latest apptainer version, change as you need +VERSION=1.1.8 # this is the latest apptainer version, change as you need ./mconfig make -C builddir rpm sudo rpm -ivh ~/rpmbuild/RPMS/x86_64/apptainer-$(echo $VERSION|tr - \~)*.x86_64.rpm diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/cmd/internal/cli/apptainer.go new/apptainer-1.1.8/cmd/internal/cli/apptainer.go --- old/apptainer-1.1.7/cmd/internal/cli/apptainer.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/cmd/internal/cli/apptainer.go 2023-04-25 17:50:20.000000000 +0200 @@ -454,7 +454,6 @@ } func persistentPreRun(cmd *cobra.Command, args []string) error { - setSylogMessageLevel() sylog.Debugf("Apptainer version: %s", buildcfg.PACKAGE_VERSION) if cmd.CalledAs() == "confgen" { @@ -526,6 +525,8 @@ // set persistent pre run function here to avoid initialization loop error apptainerCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + // call configuring message level method earlier to solve this issue https://github.com/apptainer/apptainer/issues/1259 + setSylogMessageLevel() var err error foundKeys := make(map[string]string) for precedence := range env.ApptainerPrefixes { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/e2e/actions/actions.go new/apptainer-1.1.8/e2e/actions/actions.go --- old/apptainer-1.1.7/e2e/actions/actions.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/e2e/actions/actions.go 2023-04-25 17:50:20.000000000 +0200 @@ -790,6 +790,9 @@ e2e.ExpectExit(0), ) + e2e.SetDirective(t, c.env, "allow setuid-mount extfs", "yes") + defer e2e.ResetDirective(t, c.env, "allow setuid-mount extfs") + tests := []struct { name string argv []string @@ -1934,6 +1937,9 @@ e2e.ExpectExit(0), ) + e2e.SetDirective(t, c.env, "allow setuid-mount extfs", "yes") + defer e2e.ResetDirective(t, c.env, "allow setuid-mount extfs") + tests := []struct { name string profile e2e.Profile diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/e2e/config/config.go new/apptainer-1.1.8/e2e/config/config.go --- old/apptainer-1.1.7/e2e/config/config.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/e2e/config/config.go 2023-04-25 17:50:20.000000000 +0200 @@ -25,14 +25,15 @@ ) type configTests struct { - env e2e.TestEnv - sifImage string - encryptedImage string - squashfsImage string - ext3Image string - sandboxImage string - pemPublic string - pemPrivate string + env e2e.TestEnv + sifImage string + encryptedImage string + squashfsImage string + ext3Image string + ext3OverlayImage string + sandboxImage string + pemPublic string + pemPrivate string } // prepImages creates containers covering all image formats to test the @@ -96,6 +97,20 @@ } }) + // An ext3 overlay embedded in a SIF + c.ext3OverlayImage = filepath.Join(tmpDir, "ext3Overlay.img") + if err := fs.CopyFile(c.sifImage, c.ext3OverlayImage, 0o755); err != nil { + t.Fatalf("Could not copy test image file: %v", err) + } + c.env.RunApptainer( + t, + e2e.AsSubtest("PrepareExt3Overlay"), + e2e.WithProfile(e2e.UserProfile), + e2e.WithCommand("overlay"), + e2e.WithArgs("create", c.ext3OverlayImage), + e2e.ExpectExit(0), + ) + return cleanup } @@ -104,24 +119,8 @@ cleanup := c.prepImages(t) defer cleanup(t) - setDirective := func(t *testing.T, directive, value string) { - c.env.RunApptainer( - t, - e2e.WithProfile(e2e.RootProfile), - e2e.WithCommand("config global"), - e2e.WithArgs("--set", directive, value), - e2e.ExpectExit(0), - ) - } - resetDirective := func(t *testing.T, directive string) { - c.env.RunApptainer( - t, - e2e.WithProfile(e2e.RootProfile), - e2e.WithCommand("config global"), - e2e.WithArgs("--reset", directive), - e2e.ExpectExit(0), - ) - } + e2e.SetDirective(t, c.env, "allow setuid-mount extfs", "yes") + defer e2e.ResetDirective(t, c.env, "allow setuid-mount extfs") u := e2e.UserProfile.HostUser(t) g, err := user.GetGrGID(u.GID) @@ -504,6 +503,169 @@ directiveValue: "yes", exit: 0, }, + // NOTE: the "allow setuid-mount" tests have to stay after the + // "allow container" tests because they will be left in their + // default settings which can interfere with "allow container" tests. + { + name: "AllowSetuidMountEncryptedNo", + argv: []string{"--pem-path", c.pemPrivate, c.encryptedImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount encrypted", + directiveValue: "no", + exit: 255, + }, + { + name: "AllowSetuidMountEncryptedYes", + argv: []string{"--pem-path", c.pemPrivate, c.encryptedImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount encrypted", + directiveValue: "yes", + exit: 0, + }, + { + name: "AllowSetuidMountSquashfsNo", + argv: []string{c.squashfsImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "no", + exit: 255, + }, + { + name: "AllowSetuidMountSquashfsNoSif", + argv: []string{c.sifImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "no", + exit: 255, + }, + { + name: "AllowSetuidMountSquashfsNoBind", + argv: []string{"-B", c.squashfsImage + ":/sqsh:image-src=/", c.sifImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "no", + exit: 255, + }, + { + name: "AllowSetuidMountSquashfsNoUserns", + argv: []string{c.squashfsImage, "true"}, + profile: e2e.UserNamespaceProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "no", + exit: 0, + }, + { + name: "AllowSetuidMountSquashfsNoUsernsSif", + argv: []string{c.sifImage, "true"}, + profile: e2e.UserNamespaceProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "no", + exit: 0, + }, + { + name: "AllowSetuidMountSquashfsNoUsernsBind", + argv: []string{"-B", c.squashfsImage + ":/sqsh:image-src=/", c.sifImage, "true"}, + profile: e2e.UserNamespaceProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "no", + exit: 0, + }, + { + name: "AllowSetuidMountSquashfsYes", + argv: []string{c.squashfsImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "yes", + exit: 0, + }, + { + name: "AllowSetuidMountSquashfsYesSif", + argv: []string{c.sifImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "yes", + exit: 0, + }, + { + name: "AllowSetuidMountSquashfsYesBind", + argv: []string{"-B", c.squashfsImage + ":/sqsh:image-src=/", c.sifImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount squashfs", + directiveValue: "yes", + exit: 0, + }, + { + name: "AllowSetuidMountExtfsNo", + argv: []string{c.ext3Image, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount extfs", + directiveValue: "no", + exit: 255, + }, + { + name: "AllowSetuidMountExtfsNoSif", + argv: []string{c.ext3OverlayImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount extfs", + directiveValue: "no", + exit: 255, + }, + { + name: "AllowSetuidMountExtfsNoBind", + argv: []string{"-B", c.ext3Image + ":/ext3:image-src=/", c.sifImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount extfs", + directiveValue: "no", + exit: 255, + }, + { + name: "AllowSetuidMountExtfsNoUserns", + argv: []string{c.ext3Image, "true"}, + profile: e2e.UserNamespaceProfile, + directive: "allow setuid-mount extfs", + directiveValue: "no", + exit: 0, + }, + { + name: "AllowSetuidMountExtfsNoUsernsSif", + argv: []string{c.ext3OverlayImage, "true"}, + profile: e2e.UserNamespaceProfile, + directive: "allow setuid-mount extfs", + directiveValue: "no", + exit: 0, + }, + { + name: "AllowSetuidMountExtfsNoUsernsBind", + argv: []string{"-B", c.ext3Image + ":/ext3:image-src=/", c.sifImage, "true"}, + profile: e2e.UserNamespaceProfile, + directive: "allow setuid-mount extfs", + directiveValue: "no", + exit: 0, + }, + { + name: "AllowSetuidMountExtfsYes", + argv: []string{c.ext3Image, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount extfs", + directiveValue: "yes", + exit: 0, + }, + { + name: "AllowSetuidMountExtfsYesSif", + argv: []string{c.ext3OverlayImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount extfs", + directiveValue: "yes", + exit: 0, + }, + { + name: "AllowSetuidMountExtfsYesBind", + argv: []string{"-B", c.ext3Image + ":/ext3:image-src=/", c.sifImage, "true"}, + profile: e2e.UserProfile, + directive: "allow setuid-mount extfs", + directiveValue: "yes", + exit: 0, + }, // FIXME // The e2e tests currently run inside a PID namespace. // (see internal/init/init_linux.go) @@ -537,10 +699,10 @@ if tt.addRequirementsFn != nil { tt.addRequirementsFn(t) } - setDirective(t, tt.directive, tt.directiveValue) + e2e.SetDirective(t, c.env, tt.directive, tt.directiveValue) }), e2e.PostRun(func(t *testing.T) { - resetDirective(t, tt.directive) + e2e.ResetDirective(t, c.env, tt.directive) }), e2e.WithCommand("exec"), e2e.WithArgs(tt.argv...), @@ -553,26 +715,14 @@ func (c configTests) configGlobalCombination(t *testing.T) { e2e.EnsureImage(t, c.env) - setDirective := func(t *testing.T, directives map[string]string) { + setDirectives := func(t *testing.T, directives map[string]string) { for k, v := range directives { - c.env.RunApptainer( - t, - e2e.WithProfile(e2e.RootProfile), - e2e.WithCommand("config global"), - e2e.WithArgs("--set", k, v), - e2e.ExpectExit(0), - ) + e2e.SetDirective(t, c.env, k, v) } } - resetDirective := func(t *testing.T, directives map[string]string) { + resetDirectives := func(t *testing.T, directives map[string]string) { for k := range directives { - c.env.RunApptainer( - t, - e2e.WithProfile(e2e.RootProfile), - e2e.WithCommand("config global"), - e2e.WithArgs("--reset", k), - e2e.ExpectExit(0), - ) + e2e.ResetDirective(t, c.env, k) } } @@ -741,10 +891,10 @@ if tt.addRequirementsFn != nil { tt.addRequirementsFn(t) } - setDirective(t, tt.directives) + setDirectives(t, tt.directives) }), e2e.PostRun(func(t *testing.T) { - resetDirective(t, tt.directives) + resetDirectives(t, tt.directives) }), e2e.WithCommand("exec"), e2e.WithArgs(tt.argv...), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/e2e/internal/e2e/config.go new/apptainer-1.1.8/e2e/internal/e2e/config.go --- old/apptainer-1.1.7/e2e/internal/e2e/config.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/e2e/internal/e2e/config.go 2023-04-25 17:50:20.000000000 +0200 @@ -43,3 +43,23 @@ } })(t) } + +func SetDirective(t *testing.T, env TestEnv, directive, value string) { + env.RunApptainer( + t, + WithProfile(RootProfile), + WithCommand("config global"), + WithArgs("--set", directive, value), + ExpectExit(0), + ) +} + +func ResetDirective(t *testing.T, env TestEnv, directive string) { + env.RunApptainer( + t, + WithProfile(RootProfile), + WithCommand("config global"), + WithArgs("--reset", directive), + ExpectExit(0), + ) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/e2e/legacy/actions.go new/apptainer-1.1.8/e2e/legacy/actions.go --- old/apptainer-1.1.7/e2e/legacy/actions.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/e2e/legacy/actions.go 2023-04-25 17:50:20.000000000 +0200 @@ -266,9 +266,13 @@ env: env, } + // legacy tests run sequentially due to loop device issue, + // see https://github.com/apptainer/apptainer/issues/1272 + np := testhelper.NoParallel + return testhelper.Tests{ - "run legacy": c.runLegacy, - "shell legacy": c.shellLegacy, - "exec legacy": c.execLegacy, + "run legacy": np(c.runLegacy), + "shell legacy": np(c.shellLegacy), + "exec legacy": np(c.execLegacy), } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/e2e/overlay/overlay.go new/apptainer-1.1.8/e2e/overlay/overlay.go --- old/apptainer-1.1.7/e2e/overlay/overlay.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/e2e/overlay/overlay.go 2023-04-25 17:50:20.000000000 +0200 @@ -74,6 +74,9 @@ e2e.ExpectExit(0), ) + e2e.SetDirective(t, c.env, "allow setuid-mount extfs", "yes") + defer e2e.ResetDirective(t, c.env, "allow setuid-mount extfs") + type test struct { name string profile e2e.Profile diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/e2e/run/run.go new/apptainer-1.1.8/e2e/run/run.go --- old/apptainer-1.1.7/e2e/run/run.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/e2e/run/run.go 2023-04-25 17:50:20.000000000 +0200 @@ -286,6 +286,9 @@ t.Fatalf(err.Error()) } + e2e.SetDirective(t, c.env, "allow setuid-mount extfs", "yes") + defer e2e.ResetDirective(t, c.env, "allow setuid-mount extfs") + c.env.RunApptainer( t, e2e.WithProfile(e2e.UserProfile), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/e2e/testdata/Singularity_legacy.def new/apptainer-1.1.8/e2e/testdata/Singularity_legacy.def --- old/apptainer-1.1.7/e2e/testdata/Singularity_legacy.def 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/e2e/testdata/Singularity_legacy.def 2023-04-25 17:50:20.000000000 +0200 @@ -3,6 +3,6 @@ %post cd / - apt-get update -y && apt-get install -y wget cryptsetup-bin squashfs-tools + apt-get update -y && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install -y wget cryptsetup-bin squashfs-tools tzdata wget https://github.com/sylabs/singularity/releases/download/v3.9.2/singularity-ce_3.9.2-focal_amd64.deb dpkg -i singularity-ce_3.9.2-focal_amd64.deb && rm singularity-ce_3.9.2-focal_amd64.deb \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/internal/pkg/runtime/engine/apptainer/container_linux.go new/apptainer-1.1.8/internal/pkg/runtime/engine/apptainer/container_linux.go --- old/apptainer-1.1.7/internal/pkg/runtime/engine/apptainer/container_linux.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/internal/pkg/runtime/engine/apptainer/container_linux.go 2023-04-25 17:50:20.000000000 +0200 @@ -722,6 +722,10 @@ sylog.Verbosef("Overlay mount failed with %s, mounting with index=off", err) optsString = fmt.Sprintf("%s,index=off", optsString) goto mount + } else if mnt.Type == "overlay" && err == syscall.EINVAL { + sylog.Verbosef("Overlay mount failed with %s, mounting without xino option", err) + optsString = strings.Replace(optsString, ",xino=on", "", -1) + goto mount } else if mnt.Type == "overlay" && tag == mount.LayerTag { if imageDriver != nil && imageDriver.Features()&image.OverlayFeature != 0 { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/internal/pkg/runtime/engine/apptainer/prepare_linux.go new/apptainer-1.1.8/internal/pkg/runtime/engine/apptainer/prepare_linux.go --- old/apptainer-1.1.7/internal/pkg/runtime/engine/apptainer/prepare_linux.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/internal/pkg/runtime/engine/apptainer/prepare_linux.go 2023-04-25 17:50:20.000000000 +0200 @@ -150,7 +150,7 @@ if err := e.prepareContainerConfig(starterConfig); err != nil { return err } - if err := e.loadImages(starterConfig); err != nil { + if err := e.loadImages(starterConfig, userNS); err != nil { return err } } @@ -1131,12 +1131,12 @@ return nil } -func (e *EngineOperations) loadImages(starterConfig *starter.Config) error { +func (e *EngineOperations) loadImages(starterConfig *starter.Config, userNS bool) error { images := make([]image.Image, 0) // load rootfs image writable := e.EngineConfig.GetWritableImage() - img, err := e.loadImage(e.EngineConfig.GetImage(), writable) + img, err := e.loadImage(e.EngineConfig.GetImage(), writable, userNS) if err != nil { return err } @@ -1236,7 +1236,13 @@ return fmt.Errorf("while getting overlay partitions in %s: %s", img.Path, err) } for _, p := range overlays { - if img.Writable && p.Type == image.EXT3 { + if p.Type != image.EXT3 { + continue + } + if !userNS && !e.EngineConfig.File.AllowSetuidMountExtfs { + return fmt.Errorf("configuration disallows users from mounting SIF extfs partition in setuid mode, try --userns") + } + if img.Writable { writableOverlayPath = img.Path } } @@ -1252,7 +1258,7 @@ switch e.EngineConfig.GetSessionLayer() { case apptainerConfig.OverlayLayer: - overlayImages, err := e.loadOverlayImages(starterConfig, writableOverlayPath) + overlayImages, err := e.loadOverlayImages(starterConfig, writableOverlayPath, userNS) if err != nil { return fmt.Errorf("while loading overlay images: %s", err) } @@ -1264,7 +1270,7 @@ } } - bindImages, err := e.loadBindImages(starterConfig) + bindImages, err := e.loadBindImages(starterConfig, userNS) if err != nil { return fmt.Errorf("while loading data bind images: %s", err) } @@ -1276,7 +1282,7 @@ } // loadOverlayImages loads overlay images. -func (e *EngineOperations) loadOverlayImages(starterConfig *starter.Config, writableOverlayPath string) ([]image.Image, error) { +func (e *EngineOperations) loadOverlayImages(starterConfig *starter.Config, writableOverlayPath string, userNS bool) ([]image.Image, error) { images := make([]image.Image, 0) for _, overlayImg := range e.EngineConfig.GetOverlayImage() { @@ -1289,7 +1295,7 @@ } } - img, err := e.loadImage(splitted[0], writableOverlay) + img, err := e.loadImage(splitted[0], writableOverlay, userNS) if err != nil { if !image.IsReadOnlyFilesytem(err) { return nil, fmt.Errorf("failed to open overlay image %s: %s", splitted[0], err) @@ -1325,7 +1331,7 @@ } // loadBindImages load data bind images. -func (e *EngineOperations) loadBindImages(starterConfig *starter.Config) ([]image.Image, error) { +func (e *EngineOperations) loadBindImages(starterConfig *starter.Config, userNS bool) ([]image.Image, error) { images := make([]image.Image, 0) binds := e.EngineConfig.GetBindPath() @@ -1339,7 +1345,7 @@ sylog.Debugf("Loading data image %s", imagePath) - img, err := e.loadImage(imagePath, !binds[i].Readonly()) + img, err := e.loadImage(imagePath, !binds[i].Readonly(), userNS) if err != nil && !image.IsReadOnlyFilesytem(err) { return nil, fmt.Errorf("failed to load data image %s: %s", imagePath, err) } @@ -1355,7 +1361,7 @@ return images, nil } -func (e *EngineOperations) loadImage(path string, writable bool) (*image.Image, error) { +func (e *EngineOperations) loadImage(path string, writable bool, userNS bool) (*image.Image, error) { const delSuffix = " (deleted)" imgObject, imgErr := image.Init(path, writable) @@ -1413,11 +1419,17 @@ if !e.EngineConfig.File.AllowContainerSquashfs { return nil, fmt.Errorf("configuration disallows users from running squashFS containers") } + if !userNS && !e.EngineConfig.File.AllowSetuidMountSquashfs { + return nil, fmt.Errorf("configuration disallows users from mounting squashFS in setuid mode, try --userns") + } // Bare EXT3 case image.EXT3: if !e.EngineConfig.File.AllowContainerExtfs { return nil, fmt.Errorf("configuration disallows users from running extFS containers") } + if !userNS && !e.EngineConfig.File.AllowSetuidMountExtfs { + return nil, fmt.Errorf("configuration disallows users from mounting extfs in setuid mode, try --userns") + } // Bare sandbox directory case image.SANDBOX: if !e.EngineConfig.File.AllowContainerDir { @@ -1425,6 +1437,9 @@ } // SIF case image.SIF: + if !userNS && !e.EngineConfig.File.AllowSetuidMountSquashfs { + return nil, fmt.Errorf("configuration disallows users from mounting SIF squashFS partition in setuid mode, try --userns") + } // Check if SIF contains an encrypted rootfs partition. // We don't support encryption for other partitions at present. encrypted, err := imgObject.HasEncryptedRootFs() @@ -1435,6 +1450,9 @@ if encrypted && !e.EngineConfig.File.AllowContainerEncrypted { return nil, fmt.Errorf("configuration disallows users from running encrypted SIF containers") } + if encrypted && !userNS && !e.EngineConfig.File.AllowSetuidMountEncrypted { + return nil, fmt.Errorf("configuration disallows users from mounting encrypted files in setuid mode") + } // SIF without encryption - regardless of rootfs filesystem type if !encrypted && !e.EngineConfig.File.AllowContainerSIF { return nil, fmt.Errorf("configuration disallows users from running unencrypted SIF containers") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/internal/pkg/util/crypt/crypt_dev.go new/apptainer-1.1.8/internal/pkg/util/crypt/crypt_dev.go --- old/apptainer-1.1.7/internal/pkg/util/crypt/crypt_dev.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/internal/pkg/util/crypt/crypt_dev.go 2023-04-25 17:50:20.000000000 +0200 @@ -47,7 +47,6 @@ func createLoop(path string, offset, size uint64) (string, error) { loopDev := &loop.Device{ MaxLoopDevices: loop.GetMaxLoopDevices(), - Shared: true, Info: &loop.Info64{ SizeLimit: size, Offset: offset, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/internal/pkg/util/fs/mount/mount_linux.go new/apptainer-1.1.8/internal/pkg/util/fs/mount/mount_linux.go --- old/apptainer-1.1.7/internal/pkg/util/fs/mount/mount_linux.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/internal/pkg/util/fs/mount/mount_linux.go 2023-04-25 17:50:20.000000000 +0200 @@ -701,7 +701,7 @@ if !strings.HasPrefix(workdir, "/") { return fmt.Errorf("workdir must be an absolute path") } - options = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerdir, upperdir, workdir) + options = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,xino=on", lowerdir, upperdir, workdir) } else { options = fmt.Sprintf("lowerdir=%s", lowerdir) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/pkg/util/apptainerconf/config.go new/apptainer-1.1.8/pkg/util/apptainerconf/config.go --- old/apptainer-1.1.7/pkg/util/apptainerconf/config.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/pkg/util/apptainerconf/config.go 2023-04-25 17:50:20.000000000 +0200 @@ -76,46 +76,49 @@ // File describes the apptainer.conf file options type File struct { - AllowSetuid bool `default:"yes" authorized:"yes,no" directive:"allow setuid"` - AllowPidNs bool `default:"yes" authorized:"yes,no" directive:"allow pid ns"` - ConfigPasswd bool `default:"yes" authorized:"yes,no" directive:"config passwd"` - ConfigGroup bool `default:"yes" authorized:"yes,no" directive:"config group"` - ConfigResolvConf bool `default:"yes" authorized:"yes,no" directive:"config resolv_conf"` - MountProc bool `default:"yes" authorized:"yes,no" directive:"mount proc"` - MountSys bool `default:"yes" authorized:"yes,no" directive:"mount sys"` - MountDevPts bool `default:"yes" authorized:"yes,no" directive:"mount devpts"` - MountHome bool `default:"yes" authorized:"yes,no" directive:"mount home"` - MountTmp bool `default:"yes" authorized:"yes,no" directive:"mount tmp"` - MountHostfs bool `default:"no" authorized:"yes,no" directive:"mount hostfs"` - UserBindControl bool `default:"yes" authorized:"yes,no" directive:"user bind control"` - EnableFusemount bool `default:"yes" authorized:"yes,no" directive:"enable fusemount"` - EnableUnderlay bool `default:"yes" authorized:"yes,no" directive:"enable underlay"` - MountSlave bool `default:"yes" authorized:"yes,no" directive:"mount slave"` - AllowContainerSIF bool `default:"yes" authorized:"yes,no" directive:"allow container sif"` - AllowContainerEncrypted bool `default:"yes" authorized:"yes,no" directive:"allow container encrypted"` - AllowContainerSquashfs bool `default:"yes" authorized:"yes,no" directive:"allow container squashfs"` - AllowContainerExtfs bool `default:"yes" authorized:"yes,no" directive:"allow container extfs"` - AllowContainerDir bool `default:"yes" authorized:"yes,no" directive:"allow container dir"` - AlwaysUseNv bool `default:"no" authorized:"yes,no" directive:"always use nv"` - UseNvCCLI bool `default:"no" authorized:"yes,no" directive:"use nvidia-container-cli"` - AlwaysUseRocm bool `default:"no" authorized:"yes,no" directive:"always use rocm"` - SharedLoopDevices bool `default:"no" authorized:"yes,no" directive:"shared loop devices"` - MaxLoopDevices uint `default:"256" directive:"max loop devices"` - SessiondirMaxSize uint `default:"16" directive:"sessiondir max size"` - MountDev string `default:"yes" authorized:"yes,no,minimal" directive:"mount dev"` - EnableOverlay string `default:"try" authorized:"yes,no,try,driver" directive:"enable overlay"` - BindPath []string `default:"/etc/localtime,/etc/hosts" directive:"bind path"` - LimitContainerOwners []string `directive:"limit container owners"` - LimitContainerGroups []string `directive:"limit container groups"` - LimitContainerPaths []string `directive:"limit container paths"` - AllowNetUsers []string `directive:"allow net users"` - AllowNetGroups []string `directive:"allow net groups"` - AllowNetNetworks []string `directive:"allow net networks"` - RootDefaultCapabilities string `default:"full" authorized:"full,file,no" directive:"root default capabilities"` - MemoryFSType string `default:"tmpfs" authorized:"tmpfs,ramfs" directive:"memory fs type"` - CniConfPath string `directive:"cni configuration path"` - CniPluginPath string `directive:"cni plugin path"` - BinaryPath string `default:"$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" directive:"binary path"` + AllowSetuid bool `default:"yes" authorized:"yes,no" directive:"allow setuid"` + AllowPidNs bool `default:"yes" authorized:"yes,no" directive:"allow pid ns"` + ConfigPasswd bool `default:"yes" authorized:"yes,no" directive:"config passwd"` + ConfigGroup bool `default:"yes" authorized:"yes,no" directive:"config group"` + ConfigResolvConf bool `default:"yes" authorized:"yes,no" directive:"config resolv_conf"` + MountProc bool `default:"yes" authorized:"yes,no" directive:"mount proc"` + MountSys bool `default:"yes" authorized:"yes,no" directive:"mount sys"` + MountDevPts bool `default:"yes" authorized:"yes,no" directive:"mount devpts"` + MountHome bool `default:"yes" authorized:"yes,no" directive:"mount home"` + MountTmp bool `default:"yes" authorized:"yes,no" directive:"mount tmp"` + MountHostfs bool `default:"no" authorized:"yes,no" directive:"mount hostfs"` + UserBindControl bool `default:"yes" authorized:"yes,no" directive:"user bind control"` + EnableFusemount bool `default:"yes" authorized:"yes,no" directive:"enable fusemount"` + EnableUnderlay bool `default:"yes" authorized:"yes,no" directive:"enable underlay"` + MountSlave bool `default:"yes" authorized:"yes,no" directive:"mount slave"` + AllowContainerSIF bool `default:"yes" authorized:"yes,no" directive:"allow container sif"` + AllowContainerEncrypted bool `default:"yes" authorized:"yes,no" directive:"allow container encrypted"` + AllowContainerSquashfs bool `default:"yes" authorized:"yes,no" directive:"allow container squashfs"` + AllowContainerExtfs bool `default:"yes" authorized:"yes,no" directive:"allow container extfs"` + AllowContainerDir bool `default:"yes" authorized:"yes,no" directive:"allow container dir"` + AllowSetuidMountEncrypted bool `default:"yes" authorized:"yes,no" directive:"allow setuid-mount encrypted"` + AllowSetuidMountSquashfs bool `default:"yes" authorized:"yes,no" directive:"allow setuid-mount squashfs"` + AllowSetuidMountExtfs bool `default:"no" authorized:"yes,no" directive:"allow setuid-mount extfs"` + AlwaysUseNv bool `default:"no" authorized:"yes,no" directive:"always use nv"` + UseNvCCLI bool `default:"no" authorized:"yes,no" directive:"use nvidia-container-cli"` + AlwaysUseRocm bool `default:"no" authorized:"yes,no" directive:"always use rocm"` + SharedLoopDevices bool `default:"no" authorized:"yes,no" directive:"shared loop devices"` + MaxLoopDevices uint `default:"256" directive:"max loop devices"` + SessiondirMaxSize uint `default:"16" directive:"sessiondir max size"` + MountDev string `default:"yes" authorized:"yes,no,minimal" directive:"mount dev"` + EnableOverlay string `default:"try" authorized:"yes,no,try,driver" directive:"enable overlay"` + BindPath []string `default:"/etc/localtime,/etc/hosts" directive:"bind path"` + LimitContainerOwners []string `directive:"limit container owners"` + LimitContainerGroups []string `directive:"limit container groups"` + LimitContainerPaths []string `directive:"limit container paths"` + AllowNetUsers []string `directive:"allow net users"` + AllowNetGroups []string `directive:"allow net groups"` + AllowNetNetworks []string `directive:"allow net networks"` + RootDefaultCapabilities string `default:"full" authorized:"full,file,no" directive:"root default capabilities"` + MemoryFSType string `default:"tmpfs" authorized:"tmpfs,ramfs" directive:"memory fs type"` + CniConfPath string `directive:"cni configuration path"` + CniPluginPath string `directive:"cni plugin path"` + BinaryPath string `default:"$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" directive:"binary path"` // SuidBinaryPath is hidden; it is not referenced below, and overwritten SuidBinaryPath string `directive:"suidbinary path"` MksquashfsProcs uint `default:"0" directive:"mksquashfs procs"` @@ -127,6 +130,18 @@ SystemdCgroups bool `default:"yes" authorized:"yes,no" directive:"systemd cgroups"` } +// NOTE: if you think that we may want to change the default for any +// configuration parameter in the future, it is a good idea to conditionally +// insert a comment before the default setting when the setting is equal +// to the current default. That enables the defaults to get updated in +// a new release even if an administrator has changed one of the *other* +// settings. This gets around the problem of packagers such as rpm +// refusing to overwrite a configuration file if any change has been made. +// This technique is used for example in the "allow setuid-mount" options +// below. If a default is changed in a future release, both the default +// setting above and the expression for the conditional comment below need +// to change at the same time. + const TemplateAsset = `# APPTAINER.CONF # This is the global configuration file for Apptainer. This file controls # what the container is allowed to do on a particular host, and as a result @@ -321,7 +336,9 @@ # ALLOW CONTAINER ${TYPE}: [BOOL] # DEFAULT: yes # This feature limits what kind of containers that Apptainer will allow -# users to use (note this does not apply for root). +# users to use (note this does not apply for root). Note that some of the +# same operations can be limited in setuid mode by the ALLOW SETUID-MOUNT +# feature below; both types need to be "yes" to be allowed. # # Allow use of unencrypted SIF containers allow container sif = {{ if eq .AllowContainerSIF true}}yes{{ else }}no{{ end }} @@ -334,6 +351,32 @@ allow container extfs = {{ if eq .AllowContainerExtfs true }}yes{{ else }}no{{ end }} allow container dir = {{ if eq .AllowContainerDir true }}yes{{ else }}no{{ end }} +# ALLOW SETUID-MOUNT ${TYPE}: [BOOL] +# DEFAULT: yes, except no for extfs +# This feature limits what types of mounts that Apptainer will allow +# unprivileged users to use in setuid mode. Normally these operations +# require the elevated privileges of setuid mode, although Apptainer +# has unprivileged alternatives for squashfs and extfs. Note that some of +# the same operations can also be limited by the ALLOW CONTAINER feature +# above; both types need to be "yes" to be allowed. +# +# Allow mounting of SIF encryption (using the kernel device-mapper) in +# setuid mode +{{ if eq .AllowSetuidMountEncrypted true}}# {{ end }}allow setuid-mount encrypted = {{ if eq .AllowSetuidMountEncrypted true}}yes{{ else }}no{{ end }} +# +# Allow mounting of squashfs filesystem types in setuid mode, both inside and +# outside of SIF files +{{ if eq .AllowSetuidMountSquashfs true}}# {{ end }}allow setuid-mount squashfs = {{ if eq .AllowSetuidMountSquashfs true}}yes{{ else }}no{{ end }} +# +# Allow mounting of extfs filesystem types in setuid mode, both inside and +# outside of SIF files. WARNING: this filesystem type frequently has relevant +# CVEs that that take a very long time for vendors to patch because they are +# not considered to be High severity since normally unprivileged users do +# not have write access to the raw filesystem data. This is why this option +# defaults to "no". Change it at your own risk and consider using the +# LIMIT CONTAINER features above if you do. +{{ if eq .AllowSetuidMountExtfs false}}# {{ end }}allow setuid-mount extfs = {{ if eq .AllowSetuidMountExtfs true}}yes{{ else }}no{{ end }} + # ALLOW NET USERS: [STRING] # DEFAULT: NULL # Allow specified root administered CNI network configurations to be used by the diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/pkg/util/loop/loop.go new/apptainer-1.1.8/pkg/util/loop/loop.go --- old/apptainer-1.1.7/pkg/util/loop/loop.go 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/pkg/util/loop/loop.go 2023-04-25 17:50:20.000000000 +0200 @@ -150,8 +150,6 @@ imageDev := uint64(imageInfo.Dev) for device := 0; device < loop.MaxLoopDevices; device++ { - *number = device - // Try to open an existing loop device, but don't create a new one loopFd, releaseLock, err := openLoopDev(device, mode, true, nil) if err != nil { @@ -172,6 +170,7 @@ // be sure that the loop device won't be released between this // check and the mount of the filesystem sylog.Debugf("Sharing loop device %d", device) + *number = device loop.fd = &loopFd return true, nil } @@ -186,22 +185,24 @@ // When setting loop device status, some kernel may return EAGAIN, this function would sync // workaround this error. func (loop *Device) attachLoop(imageFd uintptr, imageInfo *syscall.Stat_t, mode int, number *int) error { - releaseDevice := func(fd int, clear bool) { + releaseDevice := func(fd int, clear bool, releaseLock func()) { if clear { syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), CmdClrFd, 0) } syscall.Close(fd) + releaseLock() } createFn := getCreateDeviceFn() retryFn := getRetryStatusFn(imageFd, imageInfo) for device := 0; device < loop.MaxLoopDevices; device++ { - *number = device - // Try to open the loop device, creating the device node if needed loopFd, releaseLock, err := openLoopDev(device, mode, loop.Shared, createFn) if err != nil { + if os.IsNotExist(err) { + continue + } sylog.Debugf("Couldn't open loop device %d: %v", device, err) return err } @@ -209,24 +210,22 @@ // On error, we'll move on to try the next loop device _, _, esys := syscall.Syscall(syscall.SYS_IOCTL, uintptr(loopFd), CmdSetFd, imageFd) if esys != 0 { - releaseDevice(loopFd, false) - releaseLock() + releaseDevice(loopFd, false, releaseLock) continue } if _, _, esys := syscall.Syscall(syscall.SYS_FCNTL, uintptr(loopFd), syscall.F_SETFD, syscall.FD_CLOEXEC); esys != 0 { - releaseDevice(loopFd, true) - releaseLock() + releaseDevice(loopFd, true, releaseLock) return fmt.Errorf("failed to set close-on-exec on loop device %s: error message=%s", getLoopPath(device), esys.Error()) } if err := setLoopStatus(loopFd, loop.Info, getLoopPath(device), retryFn); err != nil { - releaseDevice(loopFd, true) - releaseLock() + releaseDevice(loopFd, true, releaseLock) return fmt.Errorf("loop device status: %s", err) } releaseLock() + *number = device loop.fd = &loopFd return nil } @@ -239,21 +238,29 @@ // Returns the fd for the opened device, or -1 if it was not possible to openLoopDev it. func openLoopDev(device, mode int, sharedLoop bool, createFn createDeviceFn) (int, func(), error) { path := getLoopPath(device) - fi, err := os.Stat(path) + + // loop device can exist but without any device attached to it in kernel, + // a stat call couldn't catch ENXIO error in this case, use open + loopFd, err := syscall.Open(path, mode, 0o600) if err != nil { - if !os.IsNotExist(err) { - return -1, nil, fmt.Errorf("could not stat %s: %w", path, err) + if errno, ok := err.(syscall.Errno); ok && errno == unix.ENXIO { + if createFn == nil { + err = os.ErrNotExist + } + } else if !os.IsNotExist(err) { + return -1, nil, fmt.Errorf("could not open %s: %w", path, err) } // device doesn't exist but no create function passed ... done if createFn == nil { return -1, nil, err } // create the device node if we need to - if err := createFn(device); err != nil { + err := createFn(device) + if err != nil { return -1, nil, fmt.Errorf("could not create %s: %w", path, err) } - } else if fi.Mode()&os.ModeDevice == 0 { - return -1, nil, fmt.Errorf("%s is not a block device", path) + } else { + _ = syscall.Close(loopFd) } releaseLock := func() {} @@ -272,8 +279,7 @@ } } - // Now open the loop device - loopFd, err := syscall.Open(path, mode, 0o600) + loopFd, err = syscall.Open(path, mode, 0o600) if err != nil { releaseLock() return -1, nil, fmt.Errorf("could not open %s: %w", path, err) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/apptainer-1.1.7/scripts/should-e2e-run new/apptainer-1.1.8/scripts/should-e2e-run --- old/apptainer-1.1.7/scripts/should-e2e-run 2023-03-28 22:17:08.000000000 +0200 +++ new/apptainer-1.1.8/scripts/should-e2e-run 2023-04-25 17:50:20.000000000 +0200 @@ -83,6 +83,10 @@ require_e2e=true ;; + null) + # Failed to read api, could be private repo. Run tests. + require_e2e=true + ;; *) # The branch is not master or release, skip e2e require_e2e=false