VYPR
Medium severity4.8NVD Advisory· Published Jun 4, 2026· Updated Jun 4, 2026

Singluarity: Incorrect path matching for 'limit container paths' directive

CVE-2026-47215

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

Patches

1
c08791793e84

fix: limit container paths using path prefix, not string prefix

https://github.com/sylabs/singularityDavid TrudgianMay 18, 2026via ghsa-ref
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

News mentions

0

No linked articles in our index yet.