Singluarity: Incorrect path matching for 'limit container paths' directive
Description
SingularityCE allows containers to be run from sibling directories not matching the 'limit container paths' directive, potentially leading to unauthorized execution.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
SingularityCE allows containers to be run from sibling directories not matching the 'limit container paths' directive, potentially leading to unauthorized execution.
Vulnerability
The limit container paths directive in singularity.conf incorrectly matches path strings, allowing containers to be run from sibling directories with similar names. For example, a configuration limiting paths to /data/safe would also permit execution from /data/safe-but-unsafe. This vulnerability affects versions prior to SingularityCE 4.4.2 and SingularityPRO 4.3.9 / 4.1.14 [4].
Exploitation
An attacker with the ability to configure the limit container paths directive or control directory structures could exploit this vulnerability. By placing a container in a sibling directory that shares a prefix with an allowed path, the container could be executed, bypassing the intended restriction [4].
Impact
Successful exploitation allows an attacker to run containers from paths that should have been disallowed by the limit container paths directive. This could lead to unauthorized execution of containerized applications or code, potentially compromising the system depending on the container's privileges and contents [4].
Mitigation
This issue is fixed in SingularityCE 4.4.2 and SingularityPRO 4.3.9 / 4.1.14 [3]. If the limit container paths functionality is not used, the installation is not affected. If it is used, updating to a patched version is required. Reviewing documented limitations when user namespaces are enabled is also recommended [1].
AI Insight generated on Jun 4, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1- Range: 4.4.2
Patches
1c08791793e84fix: limit container paths using path prefix, not string prefix
3 files changed · +77 −16
CHANGELOG.md+6 −0 modified@@ -2,6 +2,12 @@ ## Unreleased +### Security Related Fixes + +- Fix for [CVE-2026-47215 / + GHSA-wqcr-7rf3-f64m](https://github.com/sylabs/singularity/security/advisories/GHSA-wqcr-7rf3-f64m) + Incorrect path matching for 'limit container paths' directive + ## Change Defaults / Behaviours Although SingularityCE does not aim to contain execution / prevent host
pkg/image/image.go+15 −11 modified@@ -10,7 +10,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "syscall" "github.com/ccoveille/go-safecast/v2" @@ -143,26 +142,31 @@ type Image struct { Usage Usage `json:"usage"` } -// AuthorizedPath checks if image is in a path supplied in paths +// AuthorizedPath checks if the image is in a directory tree rooted at any of +// the supplied paths. Paths must be absolute. Symlinks are resolved. func (i *Image) AuthorizedPath(paths []string) (bool, error) { if err := i.initFile(); err != nil { return false, err } - authorized := false - dirname := i.Path - for _, path := range paths { - match, err := filepath.EvalSymlinks(filepath.Clean(path)) + abs, err := filepath.Abs(filepath.Clean(path)) + if err != nil { + return false, fmt.Errorf("failed to resolve path %s: %w", path, err) + } + match, err := filepath.EvalSymlinks(abs) if err != nil { - return authorized, fmt.Errorf("failed to resolve path %s: %s", path, err) + return false, fmt.Errorf("failed to resolve path %s: %w", path, err) } - if strings.HasPrefix(dirname, match) { - authorized = true - break + rel, err := filepath.Rel(match, i.Path) + if err != nil { + return false, fmt.Errorf("failed to compare path %s against %s: %w", i.Path, match, err) + } + if rel == "." || filepath.IsLocal(rel) { + return true, nil } } - return authorized, nil + return false, nil } // AuthorizedOwner checks whether the image is owned by any user from the supplied users list.
pkg/image/image_test.go+56 −5 modified@@ -1,4 +1,4 @@ -// Copyright (c) 2019-2025, Sylabs Inc. All rights reserved. +// Copyright (c) 2019-2026, Sylabs Inc. All rights reserved. // This software is licensed under a 3-clause BSD license. Please consult the // LICENSE.md file distributed with the sources of this project regarding your // rights to use or distribute this software. @@ -174,6 +174,34 @@ func TestAuthorizedPath(t *testing.T) { test.DropPrivilege(t) defer test.ResetPrivilege(t) + // Test image is in xxx/foobar/test.sif + testDir := t.TempDir() + imageDir := filepath.Join(testDir, "foobar") + if err := os.Mkdir(imageDir, 0o755); err != nil { + t.Fatal(err) + } + // Test image is not in xxx/foo ... which is a string prefix of xxx/foobar + stringPrefixPath := filepath.Join(testDir, "foo") + if err := os.Mkdir(stringPrefixPath, 0o755); err != nil { + t.Fatal(err) + } + // Symlink xxx/link -> xxx/foobar, so the limit path resolves into the + // image directory via the symlink. + symlinkToImageDir := filepath.Join(testDir, "link") + if err := os.Symlink(imageDir, symlinkToImageDir); err != nil { + t.Fatal(err) + } + + // XXX(mem): This is what makes this test slow + path := filepath.Join(imageDir, "test.sif") + if err := fs.CopyFileAtomic(busyboxSIF, path, 0o755); err != nil { + t.Fatalf("Could not copy test image: %v", err) + } + img, err := Init(path, true) + if err != nil { + t.Fatal(err) + } + tests := []struct { name string path []string @@ -194,12 +222,35 @@ func TestAuthorizedPath(t *testing.T) { path: []string{"/"}, shouldPass: true, }, + { + name: "parent path", + path: []string{imageDir}, + shouldPass: true, + }, + { + name: "parent path trailing slash", + path: []string{imageDir + string(os.PathSeparator)}, + shouldPass: true, + }, + { + name: "image file as limit path", + path: []string{path}, + shouldPass: true, + }, + { + name: "symlink to image dir", + path: []string{symlinkToImageDir}, + shouldPass: true, + }, + // See GHSA-wqcr-7rf3-f64m + // Image in xxx/foobar must not be authorized for xxx/foo. + { + name: "string prefix", + path: []string{stringPrefixPath}, + shouldPass: false, + }, } - // XXX(mem): This is what makes this test slow - img, path := createImage(t) - defer os.Remove(path) - for _, test := range tests { t.Run(test.name, func(t *testing.T) { auth, err := img.AuthorizedPath(test.path)
Vulnerability mechanics
Root cause
"The `limit container paths` directive incorrectly used string prefix matching instead of path prefix matching."
Attack vector
An attacker configures a container path that is a sibling directory to a path specified in the `limit container paths` directive. For example, if `limit container paths` is set to `/data/safe`, an attacker could place a container in `/data/safe-but-unsafe` and it would be incorrectly allowed to run. This bypasses intended restrictions on where containers can be executed [ref_id=1].
Affected code
The vulnerability exists in the `AuthorizedPath` function within `pkg/image/image.go`. This function is responsible for checking if an image's path conforms to the `limit container paths` directive in `singularity.conf`. The fix is implemented in the same function, changing the logic from `strings.HasPrefix` to a relative path comparison [patch_id=4826339].
What the fix does
The patch modifies the `AuthorizedPath` function to correctly evaluate the relative path of the image from each limit path. Instead of a simple string prefix check, it now assesses if the image path is a subdirectory of the specified limit path. This ensures that only containers within the explicitly allowed directory trees are permitted, closing the bypass vulnerability [patch_id=4826339].
Preconditions
- configThe `limit container paths` directive in `singularity.conf` must be configured with a specific path.
- configThe system must be running in setuid mode, and unprivileged user namespaces must be disabled for the `limit container paths` directive to be effective [ref_id=1].
Generated on Jun 4, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-wqcr-7rf3-f64mghsaADVISORY
- docs.sylabs.io/guides/latest/admin-guide/configfiles.htmlghsa
- github.com/sylabs/singularity/commit/c08791793e843d4c9c1f2fc1d9d12abef747378fghsa
- github.com/sylabs/singularity/releases/tag/v4.4.2ghsa
- github.com/sylabs/singularity/security/advisories/GHSA-wqcr-7rf3-f64mghsa
News mentions
0No linked articles in our index yet.