Unpatched extfs vulnerabilities are exploitable through suid-mode Apptainer
Description
Apptainer is an open source container platform for Linux. There is an ext4 use-after-free flaw that is exploitable through versions of Apptainer < 1.1.0 and installations that include apptainer-suid < 1.1.8 on older operating systems where that CVE has not been patched. That includes Red Hat Enterprise Linux 7, Debian 10 buster (unless the linux-5.10 package is installed), Ubuntu 18.04 bionic and Ubuntu 20.04 focal. Use-after-free flaws in the kernel can be used to attack the kernel for denial of service and potentially for privilege escalation.
Apptainer 1.1.8 includes a patch that by default disables mounting of extfs filesystem types in setuid-root mode, while continuing to allow mounting of extfs filesystems in non-setuid "rootless" mode using fuse2fs.
Some workarounds are possible. Either do not install apptainer-suid (for versions 1.1.0 through 1.1.7) or set allow setuid = no in apptainer.conf. This requires having unprivileged user namespaces enabled and except for apptainer 1.1.x versions will disallow mounting of sif files, extfs files, and squashfs files in addition to other, less significant impacts. (Encrypted sif files are also not supported unprivileged in apptainer 1.1.x.). Alternatively, use the limit containers options in apptainer.conf/singularity.conf to limit sif files to trusted users, groups, and/or paths, and set allow container extfs = no to disallow mounting of extfs overlay files. The latter option by itself does not disallow mounting of extfs overlay partitions inside SIF files, so that's why the former options are also needed.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apptainer versions prior to 1.1.8 can trigger a kernel use-after-free in ext4 during filesystem mounting, enabling local denial of service or privilege escalation.
Vulnerability
Description
CVE-2023-30549 describes a use-after-free vulnerability in the Linux kernel's ext4 filesystem that can be triggered through Apptainer, a container platform [1]. The flaw exists in the ext4 code and can be exploited when Apptainer mounts ext4 filesystem images. The vulnerability affects Apptainer versions before 1.1.0, and even in versions 1.1.0 through 1.1.7 when the apptainer-suid package is installed on older operating systems that have not patched the underlying kernel CVE-2022-1184 [2]. Impacted distributions include Red Hat Enterprise Linux 7, Debian 10 buster (unless linux-5.10 is installed), Ubuntu 18.04 bionic, and Ubuntu 20.04 focal.
Exploitation
Prerequisites and Mechanism
An attacker with local user privileges can exploit this vulnerability by using Apptainer to mount a specially crafted ext4 filesystem [1]. The attack requires that the kernel have the unpatched ext4 use-after-free bug (CVE-2022-1184) [2]. The vulnerability is triggered when Apptainer mounts an extfs filesystem in setuid-root mode, which allows a normal user to escalate privileges. The Apptainer project has mitigated the issue by disabling extfs mounting in setuid-root mode as of version 1.1.8, while permitting it in non-setuid "rootless" mode via fuse2fs [1].
Impact
Successful exploitation of the kernel use-after-free can lead to local denial of service or potential privilege escalation [1]. Because the flaw is in the kernel, an attacker gaining code execution could achieve arbitrary read/write on kernel memory, which often leads to full system compromise. The official description confirms that use-after-free bugs in the kernel can be used for privilege escalation [1].
Mitigation
Status
The Apptainer project has released version 1.1.8 which by default disables mounting of extfs filesystem types in setuid-root mode [1]. For users who cannot upgrade, workarounds include not installing apptainer-suid (for versions 1.1.0–1.1.7) or setting allow setuid = no in apptainer.conf [1]. Additionally, administrators can restrict containers to trusted users and paths using limit containers options and disable extfs overlay mounting with allow container extfs = no [1]. Users of affected old distributions should also apply kernel updates that address CVE-2022-1184, such as those found in Linux 5.4.243 and 4.19.283 [3][4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/apptainer/apptainerGo | < 1.1.8 | 1.1.8 |
Affected products
7- ghsa-coords6 versionspkg:golang/github.com/apptainer/apptainerpkg:rpm/opensuse/apptainer&distro=openSUSE%20Leap%2015.5pkg:rpm/opensuse/apptainer&distro=openSUSE%20Tumbleweedpkg:rpm/opensuse/squashfuse&distro=openSUSE%20Leap%2015.5pkg:rpm/suse/apptainer&distro=SUSE%20Package%20Hub%2015%20SP5pkg:rpm/suse/squashfuse&distro=SUSE%20Package%20Hub%2015%20SP5
< 1.1.8+ 5 more
- (no CPE)range: < 1.1.8
- (no CPE)range: < 1.3.0-bp155.3.3.2
- (no CPE)range: < 1.1.8-1.1
- (no CPE)range: < 0.5.0-bp155.2.1
- (no CPE)range: < 1.3.0-bp155.3.3.2
- (no CPE)range: < 0.5.0-bp155.2.1
Patches
52220eaf90992ext4: add bounds checking in get_max_inline_xattr_value_size()
1 file changed · +11 −1
fs/ext4/inline.c+11 −1 modified@@ -34,6 +34,7 @@ static int get_max_inline_xattr_value_size(struct inode *inode, struct ext4_xattr_ibody_header *header; struct ext4_xattr_entry *entry; struct ext4_inode *raw_inode; + void *end; int free, min_offs; if (!EXT4_INODE_HAS_XATTR_SPACE(inode)) @@ -57,14 +58,23 @@ static int get_max_inline_xattr_value_size(struct inode *inode, raw_inode = ext4_raw_inode(iloc); header = IHDR(inode, raw_inode); entry = IFIRST(header); + end = (void *)raw_inode + EXT4_SB(inode->i_sb)->s_inode_size; /* Compute min_offs. */ - for (; !IS_LAST_ENTRY(entry); entry = EXT4_XATTR_NEXT(entry)) { + while (!IS_LAST_ENTRY(entry)) { + void *next = EXT4_XATTR_NEXT(entry); + + if (next >= end) { + EXT4_ERROR_INODE(inode, + "corrupt xattr in inline inode"); + return 0; + } if (!entry->e_value_inum && entry->e_value_size) { size_t offs = le16_to_cpu(entry->e_value_offs); if (offs < min_offs) min_offs = offs; } + entry = next; } free = min_offs - ((void *)entry - (void *)IFIRST(header)) - sizeof(__u32);
4f04351888a8ext4: avoid a potential slab-out-of-bounds in ext4_group_desc_csum
1 file changed · +2 −4
fs/ext4/super.c+2 −4 modified@@ -3240,11 +3240,9 @@ static __le16 ext4_group_desc_csum(struct super_block *sb, __u32 block_group, crc = crc16(crc, (__u8 *)gdp, offset); offset += sizeof(gdp->bg_checksum); /* skip checksum */ /* for checksum of struct ext4_group_desc do the rest...*/ - if (ext4_has_feature_64bit(sb) && - offset < le16_to_cpu(sbi->s_es->s_desc_size)) + if (ext4_has_feature_64bit(sb) && offset < sbi->s_desc_size) crc = crc16(crc, (__u8 *)gdp + offset, - le16_to_cpu(sbi->s_es->s_desc_size) - - offset); + sbi->s_desc_size - offset); out: return cpu_to_le16(crc);
5a4964f5ba9cMerge pull request from GHSA-j4rf-7357-f4cg
8 files changed · +345 −98
e2e/actions/actions.go+6 −0 modified@@ -790,6 +790,9 @@ func (c actionTests) PersistentOverlay(t *testing.T) { 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 @@ func (c actionTests) bindImage(t *testing.T) { 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
e2e/config/config.go+196 −46 modified@@ -25,14 +25,15 @@ import ( ) 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 @@ func (c *configTests) prepImages(t *testing.T) (cleanup func(t *testing.T)) { } }) + // 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 @@ func (c configTests) configGlobal(t *testing.T) { 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 @@ func (c configTests) configGlobal(t *testing.T) { 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 @@ func (c configTests) configGlobal(t *testing.T) { 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) configGlobal(t *testing.T) { 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 @@ func (c configTests) configGlobalCombination(t *testing.T) { 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...),
e2e/internal/e2e/config.go+20 −0 modified@@ -43,3 +43,23 @@ func SetupDefaultConfig(t *testing.T, path string) { } })(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), + ) +}
e2e/overlay/overlay.go+3 −0 modified@@ -74,6 +74,9 @@ func (c ctx) testOverlayCreate(t *testing.T) { 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
e2e/run/run.go+3 −0 modified@@ -286,6 +286,9 @@ func (c ctx) testFuseExt3Mount(t *testing.T) { 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),
internal/pkg/runtime/engine/apptainer/prepare_linux.go+29 −11 modified@@ -150,7 +150,7 @@ func (e *EngineOperations) PrepareConfig(starterConfig *starter.Config) error { 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 @@ func (e *EngineOperations) setSessionLayer(img *image.Image) error { 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 @@ func (e *EngineOperations) loadImages(starterConfig *starter.Config) error { 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 @@ func (e *EngineOperations) loadImages(starterConfig *starter.Config) error { 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 @@ func (e *EngineOperations) loadImages(starterConfig *starter.Config) error { } } - 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 @@ func (e *EngineOperations) loadImages(starterConfig *starter.Config) error { } // 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 @@ func (e *EngineOperations) loadOverlayImages(starterConfig *starter.Config, writ } } - 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 @@ func (e *EngineOperations) loadOverlayImages(starterConfig *starter.Config, writ } // 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 @@ func (e *EngineOperations) loadBindImages(starterConfig *starter.Config) ([]imag 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 @@ func (e *EngineOperations) loadBindImages(starterConfig *starter.Config) ([]imag 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,18 +1419,27 @@ func (e *EngineOperations) loadImage(path string, writable bool) (*image.Image, 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 { return nil, fmt.Errorf("configuration disallows users from running sandbox containers") } // 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 @@ func (e *EngineOperations) loadImage(path string, writable bool) (*image.Image, 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")
pkg/util/apptainerconf/config.go+84 −41 modified@@ -76,46 +76,49 @@ func SetBinaryPath(libexecDir string, nonSuid bool) { // 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 @@ type File struct { 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 @@ sessiondir max size = {{ .SessiondirMaxSize }} # 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 squashfs = {{ if eq .AllowContainerSquashfs true }}yes{{ else }} 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
scripts/should-e2e-run+4 −0 modified@@ -83,6 +83,10 @@ case "${TARGET_BRANCH}" in 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
61a1d87a324aext4: fix check for block being out of directory size
1 file changed · +1 −1
fs/ext4/namei.c+1 −1 modified@@ -126,7 +126,7 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode, struct ext4_dir_entry *dirent; int is_dx_block = 0; - if (block >= inode->i_size) { + if (block >= inode->i_size >> inode->i_blkbits) { ext4_error_inode(inode, func, line, block, "Attempting to read directory block (%u) that is past i_size (%llu)", block, inode->i_size);
65f8ea4cd57dext4: check if directory block is within i_size
1 file changed · +7 −0
fs/ext4/namei.c+7 −0 modified@@ -110,6 +110,13 @@ static struct buffer_head *__ext4_read_dirblock(struct inode *inode, struct ext4_dir_entry *dirent; int is_dx_block = 0; + if (block >= inode->i_size) { + ext4_error_inode(inode, func, line, block, + "Attempting to read directory block (%u) that is past i_size (%llu)", + block, inode->i_size); + return ERR_PTR(-EFSCORRUPTED); + } + if (ext4_simulate_fail(inode->i_sb, EXT4_SIM_DIRBLOCK_EIO)) bh = ERR_PTR(-EIO); else
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
21- github.com/advisories/GHSA-j4rf-7357-f4cgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-1184ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-30549ghsaADVISORY
- access.redhat.com/security/cve/cve-2022-1184ghsax_refsource_MISCWEB
- github.com/apptainer/apptainer/commit/5a4964f5ba9c8d89a0e353b97f51fd607670a9f7ghsax_refsource_MISCWEB
- github.com/apptainer/apptainer/releases/tag/v1.1.8ghsax_refsource_MISCWEB
- github.com/apptainer/apptainer/security/advisories/GHSA-j4rf-7357-f4cgghsax_refsource_CONFIRMWEB
- github.com/torvalds/linux/commit/2220eaf90992c11d888fe771055d4de3303ghsax_refsource_MISCWEB
- github.com/torvalds/linux/commit/4f04351888a83e595571de672e0a4a8b74fghsax_refsource_MISCWEB
- github.com/torvalds/linux/commit/61a1d87a324ad5e3ed27c6699dfc93218fcf3201ghsaWEB
- github.com/torvalds/linux/commit/65f8ea4cd57dbd46ea13b41dc8bac03176b04233ghsaWEB
- lwn.net/Articles/932136ghsaWEB
- lwn.net/Articles/932136/mitrex_refsource_MISC
- lwn.net/Articles/932137ghsaWEB
- lwn.net/Articles/932137/mitrex_refsource_MISC
- security-tracker.debian.org/tracker/CVE-2022-1184ghsax_refsource_MISCWEB
- security.gentoo.org/glsa/202311-13ghsaWEB
- sylabs.io/2023/04/response-to-cve-2023-30549ghsaWEB
- sylabs.io/2023/04/response-to-cve-2023-30549/mitrex_refsource_MISC
- ubuntu.com/security/CVE-2022-1184ghsax_refsource_MISCWEB
- www.suse.com/security/cve/CVE-2022-1184.htmlghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.