VYPR
High severityNVD Advisory· Published Oct 14, 2020· Updated Aug 4, 2024

Path traversal and files overwrite with unsquashfs

CVE-2020-15229

Description

Singularity (an open source container platform) from version 3.1.1 through 3.6.3 has a vulnerability. Due to insecure handling of path traversal and the lack of path sanitization within unsquashfs, it is possible to overwrite/create any files on the host filesystem during the extraction with a crafted squashfs filesystem. The extraction occurs automatically for unprivileged (either installation or with allow setuid = no) run of Singularity when a user attempt to run an image which is a local SIF image or a single file containing a squashfs filesystem and is coming from remote sources library:// or shub://. Image build is also impacted in a more serious way as it can be used by a root user, allowing an attacker to overwrite/create files leading to a system compromise, so far bootstrap methods library, shub and localimage are triggering the squashfs extraction. This issue is addressed in Singularity 3.6.4. All users are advised to upgrade to 3.6.4 especially if they use Singularity mainly for building image as root user. There is no solid workaround except to temporary avoid to use unprivileged mode with single file images in favor of sandbox images instead. Regarding image build, temporary avoid to build from library and shub sources and as much as possible use --fakeroot or a VM for that.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Path traversal in Singularity's unsquashfs allows arbitrary file overwrite on host during container image extraction, patched in 3.6.4.

Vulnerability

Singularity versions 3.1.1 through 3.6.3 contain a vulnerability in the handling of squashfs filesystem extraction. The unsquashfs utility, used by Singularity, lacks proper path sanitization, allowing path traversal attacks. A crafted squashfs filesystem can cause files to be written to arbitrary locations on the host filesystem during extraction [1][2][4].

Exploitation

The extraction occurs automatically in two scenarios: (1) unprivileged execution (without setuid) when a user runs a local SIF image or a single-file squashfs image pulled from remote sources library:// or shub://; (2) during image builds (often performed as root) using bootstrap methods library, shub, or localimage [2][4]. No authentication is required beyond the ability to run or build such an image.

Impact

An attacker can overwrite or create any file on the host filesystem. When the extraction is performed by a root user (as in image builds), this can lead to full system compromise [2][4]. The CVSS score is 8.2 (High) with a vector of AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:H/A:N [4].

Mitigation

The issue is fixed in Singularity 3.6.4 [1][2][4]. Users are strongly advised to upgrade, especially those building images as root. As a workaround, avoid using unprivileged mode with single-file images; use sandbox images instead. For image builds, temporarily avoid library and shub sources and use --fakeroot or a virtual machine to limit impact [2][4].

AI Insight generated on May 21, 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.

PackageAffected versionsPatched versions
github.com/sylabs/singularityGo
>= 3.1.1, < 3.6.43.6.4

Affected products

6

Patches

1
eba3dea260b1

Merge pull request #5611 from dctrud/36-sec

https://github.com/hpcng/singularityDavid TrudgianOct 13, 2020via ghsa
6 files changed · +304 13
  • CHANGELOG.md+14 2 modified
    @@ -9,11 +9,23 @@ _With the release of `v3.0.0`, we're introducing a new changelog format in an at
     
     _The old changelog can be found in the `release-2.6` branch_
     
    -# Changes since v3.6.3
    +# v3.6.4 - [2020-10-13]
    +
    +## Security related fixes
    +
    +Singularity 3.6.4 addresses the following security issue.
    +
    +  - [CVE-2020-15229](https://github.com/hpcng/singularity/security/advisories/GHSA-7gcp-w6ww-2xv9):
    +    Due to insecure handling of path traversal and the lack of path
    +    sanitization within unsquashfs (a distribution provided utility
    +    used by Singularity), it is possible to overwrite/create files on
    +    the host filesystem during the extraction of a crafted squashfs
    +    filesystem. Affects unprivileged execution of SIF / SquashFS
    +    images, and image builds from SIF / SquashFS images.
     
     ## Bug Fixes
     
    -  - Update scs-library-client to support library:// backends using an
    +  - Update scs-library-client to support `library://` backends using an
         3rd party S3 object store that does not strictly conform to v4
         signature spec.
     
    
  • INSTALL.md+3 3 modified
    @@ -89,7 +89,7 @@ $ mkdir -p ${GOPATH}/src/github.com/sylabs && \
     To build a stable version of Singularity, check out a [release tag](https://github.com/sylabs/singularity/tags) before compiling:
     
     ```
    -$ git checkout v3.6.3
    +$ git checkout v3.6.4
     ```
     
     ## Compiling Singularity
    @@ -132,7 +132,7 @@ as shown above.  Then download the latest
     and use it to install the RPM like this: 
     
     ```
    -$ export VERSION=3.6.3  # this is the singularity version, change as you need
    +$ export VERSION=3.6.4  # this is the singularity version, change as you need
     
     $ wget https://github.com/sylabs/singularity/releases/download/v${VERSION}/singularity-${VERSION}.tar.gz && \
         rpmbuild -tb singularity-${VERSION}.tar.gz && \
    @@ -148,7 +148,7 @@ tarball and use it to install Singularity:
     $ cd $GOPATH/src/github.com/sylabs/singularity && \
       ./mconfig && \
       make -C builddir rpm && \
    -  sudo rpm -ivh ~/rpmbuild/RPMS/x86_64/singularity-3.6.2*.x86_64.rpm # or whatever version you built
    +  sudo rpm -ivh ~/rpmbuild/RPMS/x86_64/singularity-3.6.4*.x86_64.rpm # or whatever version you built
     ```
     
     To build an rpm with an alternative install prefix set RPMPREFIX on the
    
  • pkg/image/unpacker/squashfs.go+41 8 modified
    @@ -1,3 +1,4 @@
    +// Copyright (c) 2020, Control Command Inc. All rights reserved.
     // Copyright (c) 2019, 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
    @@ -17,6 +18,33 @@ import (
     	"github.com/sylabs/singularity/pkg/sylog"
     )
     
    +const (
    +	stdinFile = "/proc/self/fd/0"
    +)
    +
    +var cmdFunc func(unsquashfs string, dest string, filename string, rootless bool) (*exec.Cmd, error)
    +
    +// unsquashfsCmd is the command instance for executing unsquashfs command
    +// in a non sandboxed environment when this package is used for unit tests.
    +func unsquashfsCmd(unsquashfs string, dest string, filename string, rootless bool) (*exec.Cmd, error) {
    +	args := make([]string, 0)
    +	if rootless {
    +		args = append(args, "-user-xattrs")
    +	}
    +	// remove the destination directory if any, if the directory is
    +	// not empty (typically during image build), the unsafe option -f is
    +	// set, this is unfortunately required by image build
    +	if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
    +		if !os.IsExist(err) {
    +			return nil, fmt.Errorf("failed to remove %s: %s", dest, err)
    +		}
    +		// unsafe mode
    +		args = append(args, "-f")
    +	}
    +	args = append(args, "-d", dest, filename)
    +	return exec.Command(unsquashfs, args...), nil
    +}
    +
     // Squashfs represents a squashfs unpacker.
     type Squashfs struct {
     	UnsquashfsPath string
    @@ -41,7 +69,7 @@ func (s *Squashfs) extract(files []string, reader io.Reader, dest string) error
     
     	// pipe over stdin by default
     	stdin := true
    -	filename := "/proc/self/fd/0"
    +	filename := stdinFile
     
     	if _, ok := reader.(*os.File); !ok {
     		// use the destination parent directory to store the
    @@ -71,9 +99,12 @@ func (s *Squashfs) extract(files []string, reader io.Reader, dest string) error
     	//  have to fall back to not using that option on failure.
     	if os.Geteuid() != 0 {
     		sylog.Debugf("Rootless extraction. Trying -user-xattrs for unsquashfs")
    -		args := []string{"-user-xattrs", "-f", "-d", dest, filename}
    -		args = append(args, files...)
    -		cmd := exec.Command(s.UnsquashfsPath, args...)
    +
    +		cmd, err := cmdFunc(s.UnsquashfsPath, dest, filename, true)
    +		if err != nil {
    +			return fmt.Errorf("command error: %s", err)
    +		}
    +		cmd.Args = append(cmd.Args, files...)
     		if stdin {
     			cmd.Stdin = reader
     		}
    @@ -85,17 +116,19 @@ func (s *Squashfs) extract(files []string, reader io.Reader, dest string) error
     
     		// Invalid options give output...
     		// SYNTAX: unsquashfs [options] filesystem [directories or files to extract]
    -		if bytes.HasPrefix(o, []byte("SYNTAX")) {
    +		if bytes.Contains(o, []byte("SYNTAX")) {
     			sylog.Warningf("unsquashfs does not support -user-xattrs. Images with system xattrs may fail to extract")
     		} else {
     			// A different error is fatal
     			return fmt.Errorf("extract command failed: %s: %s", string(o), err)
     		}
     	}
     
    -	args := []string{"-f", "-d", dest, filename}
    -	args = append(args, files...)
    -	cmd := exec.Command(s.UnsquashfsPath, args...)
    +	cmd, err := cmdFunc(s.UnsquashfsPath, dest, filename, false)
    +	if err != nil {
    +		return fmt.Errorf("command error: %s", err)
    +	}
    +	cmd.Args = append(cmd.Args, files...)
     	if stdin {
     		cmd.Stdin = reader
     	}
    
  • pkg/image/unpacker/squashfs_no_singularity.go+12 0 added
    @@ -0,0 +1,12 @@
    +// Copyright (c) 2020, Control Command 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.
    +
    +// +build !singularity_engine
    +
    +package unpacker
    +
    +func init() {
    +	cmdFunc = unsquashfsCmd
    +}
    
  • pkg/image/unpacker/squashfs_singularity.go+228 0 added
    @@ -0,0 +1,228 @@
    +// Copyright (c) 2020, Control Command 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.
    +
    +// +build singularity_engine
    +
    +package unpacker
    +
    +import (
    +	"bytes"
    +	"debug/elf"
    +	"fmt"
    +	"io"
    +	"io/ioutil"
    +	"os"
    +	"os/exec"
    +	"path/filepath"
    +	"regexp"
    +	"strings"
    +
    +	"github.com/sylabs/singularity/internal/pkg/buildcfg"
    +)
    +
    +func init() {
    +	cmdFunc = unsquashfsSandboxCmd
    +}
    +
    +// getLibraries returns the libraries required by the elf binary,
    +// the binary path must be absolute.
    +func getLibraries(binary string) ([]string, error) {
    +	libs := make([]string, 0)
    +
    +	exe, err := elf.Open(binary)
    +	if err != nil {
    +		return nil, err
    +	}
    +	defer exe.Close()
    +
    +	interp := ""
    +
    +	// look for the interpreter
    +	for _, p := range exe.Progs {
    +		if p.Type != elf.PT_INTERP {
    +			continue
    +		}
    +		buf := make([]byte, 4096)
    +		n, err := p.ReadAt(buf, 0)
    +		if err != nil && err != io.EOF {
    +			return nil, err
    +		} else if n > cap(buf) {
    +			return nil, fmt.Errorf("buffer too small to store interpreter")
    +		}
    +		// trim null byte to avoid an execution failure with
    +		// an invalid argument error
    +		interp = string(bytes.Trim(buf, "\x00"))
    +	}
    +
    +	// this is a static binary, nothing to do
    +	if interp == "" {
    +		return libs, nil
    +	}
    +
    +	// run interpreter to list library dependencies for the
    +	// corresponding binary, eg:
    +	// /lib64/ld-linux-x86-64.so.2 --list <program>
    +	// /lib/ld-musl-x86_64.so.1 --list <program>
    +	errBuf := new(bytes.Buffer)
    +	buf := new(bytes.Buffer)
    +
    +	cmd := exec.Command(interp, "--list", binary)
    +	cmd.Stdout = buf
    +	cmd.Stderr = errBuf
    +
    +	if err := cmd.Run(); err != nil {
    +		return nil, fmt.Errorf("while getting library dependencies: %s\n%s", err, errBuf.String())
    +	}
    +
    +	// parse the output to get matches for ' /an/absolute/path ('
    +	re := regexp.MustCompile(`[[:blank:]]?(\/.*)[[:blank:]]\(`)
    +
    +	match := re.FindAllStringSubmatch(buf.String(), -1)
    +	for _, m := range match {
    +		if len(m) < 2 {
    +			continue
    +		}
    +		lib := m[1]
    +		has := false
    +		for _, l := range libs {
    +			if l == lib {
    +				has = true
    +				break
    +			}
    +		}
    +		if !has {
    +			libs = append(libs, lib)
    +		}
    +	}
    +
    +	return libs, nil
    +}
    +
    +// unsquashfsSandboxCmd is the command instance for executing unsquashfs command
    +// in a sandboxed environment with singularity.
    +func unsquashfsSandboxCmd(unsquashfs string, dest string, filename string, rootless bool) (*exec.Cmd, error) {
    +	const (
    +		// will contain both dest and filename inside the sandbox
    +		rootfsImageDir = "/image"
    +	)
    +
    +	// create the sandbox temporary directory
    +	tmpdir := filepath.Dir(dest)
    +	rootfs, err := ioutil.TempDir(tmpdir, "tmp-rootfs-")
    +	if err != nil {
    +		return nil, fmt.Errorf("failed to create chroot directory: %s", err)
    +	}
    +
    +	overwrite := false
    +
    +	// remove the destination directory if any, if the directory is
    +	// not empty (typically during image build), the unsafe option -f is
    +	// set, this is unfortunately required by image build
    +	if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
    +		if !os.IsExist(err) {
    +			return nil, fmt.Errorf("failed to remove %s: %s", dest, err)
    +		}
    +		overwrite = true
    +	}
    +
    +	// map destination into the sandbox
    +	rootfsDest := filepath.Join(rootfsImageDir, filepath.Base(dest))
    +
    +	// sandbox required directories
    +	rootfsDirs := []string{
    +		// unsquashfs get available CPU from /sys/devices/system/cpu/online
    +		filepath.Join(rootfs, "/sys"),
    +		filepath.Join(rootfs, "/dev"),
    +		filepath.Join(rootfs, rootfsImageDir),
    +	}
    +
    +	for _, d := range rootfsDirs {
    +		if err := os.Mkdir(d, 0700); err != nil {
    +			return nil, fmt.Errorf("while creating %s: %s", d, err)
    +		}
    +	}
    +
    +	// the decision to use user namespace is left to singularity
    +	// which will detect automatically depending of the configuration
    +	// what workflow it could use
    +	args := []string{
    +		"-q",
    +		"exec",
    +		"--no-home",
    +		"--no-nv",
    +		"--no-rocm",
    +		"-C",
    +		"--no-init",
    +		"--writable",
    +		"-B", fmt.Sprintf("%s:%s", tmpdir, rootfsImageDir),
    +	}
    +
    +	if filename != stdinFile {
    +		filename = filepath.Join(rootfsImageDir, filepath.Base(filename))
    +	}
    +
    +	// get the library dependencies of unsquashfs
    +	libs, err := getLibraries(unsquashfs)
    +	if err != nil {
    +		return nil, err
    +	}
    +	libraryPath := make([]string, 0)
    +
    +	roFiles := []string{
    +		unsquashfs,
    +	}
    +
    +	// add libraries for bind mount and also generate
    +	// LD_LIBRARY_PATH
    +	for _, l := range libs {
    +		dir := filepath.Dir(l)
    +		roFiles = append(roFiles, l)
    +		has := false
    +		for _, lp := range libraryPath {
    +			if lp == dir {
    +				has = true
    +				break
    +			}
    +		}
    +		if !has {
    +			libraryPath = append(libraryPath, dir)
    +		}
    +	}
    +
    +	// create files and directories in the sandbox and
    +	// add singularity bind mount options
    +	for _, b := range roFiles {
    +		file := filepath.Join(rootfs, b)
    +		dir := filepath.Dir(file)
    +		if err := os.MkdirAll(dir, 0700); err != nil {
    +			return nil, fmt.Errorf("while creating %s: %s", dir, err)
    +		}
    +		if err := ioutil.WriteFile(file, []byte(""), 0600); err != nil {
    +			return nil, fmt.Errorf("while creating %s: %s", file, err)
    +		}
    +		args = append(args, "-B", fmt.Sprintf("%s:%s:ro", b, b))
    +	}
    +
    +	// singularity sandbox
    +	args = append(args, rootfs)
    +
    +	// unsquashfs execution arguments
    +	args = append(args, unsquashfs)
    +	if rootless {
    +		args = append(args, "-user-xattrs")
    +	}
    +	if overwrite {
    +		args = append(args, "-f")
    +	}
    +	args = append(args, "-d", rootfsDest, filename)
    +
    +	cmd := exec.Command(filepath.Join(buildcfg.BINDIR, "singularity"), args...)
    +	cmd.Dir = "/"
    +	cmd.Env = []string{
    +		fmt.Sprintf("LD_LIBRARY_PATH=%s", strings.Join(libraryPath, string(os.PathListSeparator))),
    +	}
    +
    +	return cmd, nil
    +}
    
  • pkg/image/unpacker/squashfs_test.go+6 0 modified
    @@ -1,3 +1,4 @@
    +// Copyright (c) 2020, Control Command Inc. All rights reserved.
     // Copyright (c) 2019, 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
    @@ -106,3 +107,8 @@ func TestSquashfs(t *testing.T) {
     		t.Errorf("file extraction failed, %s is missing", path)
     	}
     }
    +
    +func TestMain(m *testing.M) {
    +	cmdFunc = unsquashfsCmd
    +	os.Exit(m.Run())
    +}
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.