VYPR
Moderate severityNVD Advisory· Published Dec 8, 2022· Updated Apr 22, 2025

CVE-2022-4122

CVE-2022-4122

Description

A vulnerability was found in buildah. Incorrect following of symlinks while reading .containerignore and .dockerignore results in information disclosure.

AI Insight

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

Buildah incorrectly follows symlinks when reading .containerignore or .dockerignore, allowing information disclosure.

Vulnerability

Description CVE-2022-4122 is a vulnerability in Buildah, a tool used for building container images. The issue arises when Buildah processes .containerignore or .dockerignore files; it incorrectly follows symbolic links (symlinks) that point outside the build context directory. This can lead to an information disclosure, as the contents of these symlinked files may be read and included in the build process [1][3].

Exploitation

Method The vulnerability can be exploited when an attacker can supply a symlink within the build context that points to an arbitrary file on the host filesystem. During the container build, Buildah reads the ignore files and, due to the lack of proper symlink validation, follows the symlink to read files outside the intended context. This attack requires the ability to influence the build context, typically in a shared or multi-tenant build environment [1][3][4].

Impact

Successful exploitation allows an attacker to read the contents of files outside the build context directory, leading to information disclosure. This could expose sensitive data such as configuration files, credentials, or other secrets stored on the host system. The vulnerability is considered to have a potential for significant impact, especially in environments where container builds are performed with user-provided content [2][4].

Mitigation

The vulnerability has been patched in upstream releases of Buildah and Podman. The fix ensures that symlinks pointing outside the build context are detected and either rejected or handled securely. Users are advised to update to the latest versions of these tools to prevent exploitation [1][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.

PackageAffected versionsPatched versions
github.com/containers/podman/v4Go
< 4.5.04.5.0

Affected products

2

Patches

1
c8eeab21cf0a

Merge pull request #16315 from flouthoc/remote-ignore-symlink

https://github.com/containers/podmanOpenShift Merge RobotMar 28, 2023via ghsa
9 files changed · +126 29
  • pkg/api/handlers/compat/images_build.go+8 0 modified
    @@ -24,6 +24,7 @@ import (
     	"github.com/containers/podman/v4/pkg/auth"
     	"github.com/containers/podman/v4/pkg/channel"
     	"github.com/containers/podman/v4/pkg/rootless"
    +	"github.com/containers/podman/v4/pkg/util"
     	"github.com/containers/storage/pkg/archive"
     	"github.com/docker/docker/pkg/jsonmessage"
     	"github.com/gorilla/schema"
    @@ -625,6 +626,12 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
     	reporter := channel.NewWriter(make(chan []byte))
     	defer reporter.Close()
     
    +	_, ignoreFile, err := util.ParseDockerignore(containerFiles, contextDirectory)
    +	if err != nil {
    +		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("processing ignore file: %w", err))
    +		return
    +	}
    +
     	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime)
     	buildOptions := buildahDefine.BuildOptions{
     		AddCapabilities:         addCaps,
    @@ -674,6 +681,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
     		From:                           fromImage,
     		IDMappingOptions:               &idMappingOptions,
     		IgnoreUnrecognizedInstructions: query.Ignore,
    +		IgnoreFile:                     ignoreFile,
     		Isolation:                      isolation,
     		Jobs:                           &jobs,
     		Labels:                         labels,
    
  • pkg/bindings/images/build.go+15 28 modified
    @@ -22,6 +22,7 @@ import (
     	"github.com/containers/podman/v4/pkg/auth"
     	"github.com/containers/podman/v4/pkg/bindings"
     	"github.com/containers/podman/v4/pkg/domain/entities"
    +	"github.com/containers/podman/v4/pkg/util"
     	"github.com/containers/storage/pkg/fileutils"
     	"github.com/containers/storage/pkg/ioutils"
     	"github.com/containers/storage/pkg/regexp"
    @@ -405,14 +406,6 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
     		stdout = options.Out
     	}
     
    -	excludes := options.Excludes
    -	if len(excludes) == 0 {
    -		excludes, err = parseDockerignore(options.ContextDirectory)
    -		if err != nil {
    -			return nil, err
    -		}
    -	}
    -
     	contextDir, err = filepath.Abs(options.ContextDirectory)
     	if err != nil {
     		logrus.Errorf("Cannot find absolute path of %v: %v", options.ContextDirectory, err)
    @@ -458,20 +451,26 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
     		if strings.HasPrefix(containerfile, contextDir+string(filepath.Separator)) {
     			containerfile = strings.TrimPrefix(containerfile, contextDir+string(filepath.Separator))
     			dontexcludes = append(dontexcludes, "!"+containerfile)
    +			dontexcludes = append(dontexcludes, "!"+containerfile+".dockerignore")
    +			dontexcludes = append(dontexcludes, "!"+containerfile+".containerignore")
     		} else {
     			// If Containerfile does not exist, assume it is in context directory and do Not add to tarfile
     			if _, err := os.Lstat(containerfile); err != nil {
     				if !os.IsNotExist(err) {
     					return nil, err
     				}
     				containerfile = c
    +				dontexcludes = append(dontexcludes, "!"+containerfile)
    +				dontexcludes = append(dontexcludes, "!"+containerfile+".dockerignore")
    +				dontexcludes = append(dontexcludes, "!"+containerfile+".containerignore")
     			} else {
     				// If Containerfile does exist and not in the context directory, add it to the tarfile
     				tarContent = append(tarContent, containerfile)
     			}
     		}
     		newContainerFiles = append(newContainerFiles, filepath.ToSlash(containerfile))
     	}
    +
     	if len(newContainerFiles) > 0 {
     		cFileJSON, err := json.Marshal(newContainerFiles)
     		if err != nil {
    @@ -480,6 +479,14 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO
     		params.Set("dockerfile", string(cFileJSON))
     	}
     
    +	excludes := options.Excludes
    +	if len(excludes) == 0 {
    +		excludes, _, err = util.ParseDockerignore(newContainerFiles, options.ContextDirectory)
    +		if err != nil {
    +			return nil, err
    +		}
    +	}
    +
     	// build secrets are usually absolute host path or relative to context dir on host
     	// in any case move secret to current context and ship the tar.
     	if secrets := options.CommonBuildOpts.Secrets; len(secrets) > 0 {
    @@ -769,23 +776,3 @@ func nTar(excludes []string, sources ...string) (io.ReadCloser, error) {
     	})
     	return rc, nil
     }
    -
    -func parseDockerignore(root string) ([]string, error) {
    -	ignore, err := os.ReadFile(filepath.Join(root, ".containerignore"))
    -	if err != nil {
    -		var dockerIgnoreErr error
    -		ignore, dockerIgnoreErr = os.ReadFile(filepath.Join(root, ".dockerignore"))
    -		if dockerIgnoreErr != nil && !os.IsNotExist(dockerIgnoreErr) {
    -			return nil, err
    -		}
    -	}
    -	rawexcludes := strings.Split(string(ignore), "\n")
    -	excludes := make([]string, 0, len(rawexcludes))
    -	for _, e := range rawexcludes {
    -		if len(e) == 0 || e[0] == '#' {
    -			continue
    -		}
    -		excludes = append(excludes, e)
    -	}
    -	return excludes, nil
    -}
    
  • pkg/util/utils.go+71 0 modified
    @@ -27,6 +27,7 @@ import (
     	"github.com/containers/podman/v4/pkg/signal"
     	"github.com/containers/storage/pkg/idtools"
     	stypes "github.com/containers/storage/types"
    +	securejoin "github.com/cyphar/filepath-securejoin"
     	"github.com/opencontainers/runtime-spec/specs-go"
     	"github.com/sirupsen/logrus"
     	"golang.org/x/term"
    @@ -56,6 +57,76 @@ func parseCreds(creds string) (string, string) {
     	return up[0], up[1]
     }
     
    +// Takes build context and validates `.containerignore` or `.dockerignore`
    +// if they are symlink outside of buildcontext. Returns list of files to be
    +// excluded and resolved path to the ignore files inside build context or error
    +func ParseDockerignore(containerfiles []string, root string) ([]string, string, error) {
    +	ignoreFile := ""
    +	path, err := securejoin.SecureJoin(root, ".containerignore")
    +	if err != nil {
    +		return nil, ignoreFile, err
    +	}
    +	// set resolved ignore file so imagebuildah
    +	// does not attempts to re-resolve it
    +	ignoreFile = path
    +	ignore, err := os.ReadFile(path)
    +	if err != nil {
    +		var dockerIgnoreErr error
    +		path, symlinkErr := securejoin.SecureJoin(root, ".dockerignore")
    +		if symlinkErr != nil {
    +			return nil, ignoreFile, symlinkErr
    +		}
    +		// set resolved ignore file so imagebuildah
    +		// does not attempts to re-resolve it
    +		ignoreFile = path
    +		ignore, dockerIgnoreErr = os.ReadFile(path)
    +		if os.IsNotExist(dockerIgnoreErr) {
    +			// In this case either ignorefile was not found
    +			// or it is a symlink to unexpected file in such
    +			// case manually set ignorefile to `/dev/null` so
    +			// internally imagebuildah does not attempts to re-resolve
    +			// this invalid symlink and instead reads a blank file.
    +			ignoreFile = "/dev/null"
    +		}
    +		// after https://github.com/containers/buildah/pull/4239 build supports
    +		// <Containerfile>.containerignore or <Containerfile>.dockerignore as ignore file
    +		// so remote must support parsing that.
    +		if dockerIgnoreErr != nil {
    +			for _, containerfile := range containerfiles {
    +				if _, err := os.Stat(filepath.Join(root, containerfile+".containerignore")); err == nil {
    +					path, symlinkErr = securejoin.SecureJoin(root, containerfile+".containerignore")
    +					if symlinkErr == nil {
    +						ignoreFile = path
    +						ignore, dockerIgnoreErr = os.ReadFile(path)
    +					}
    +				}
    +				if _, err := os.Stat(filepath.Join(root, containerfile+".dockerignore")); err == nil {
    +					path, symlinkErr = securejoin.SecureJoin(root, containerfile+".dockerignore")
    +					if symlinkErr == nil {
    +						ignoreFile = path
    +						ignore, dockerIgnoreErr = os.ReadFile(path)
    +					}
    +				}
    +				if dockerIgnoreErr == nil {
    +					break
    +				}
    +			}
    +		}
    +		if dockerIgnoreErr != nil && !os.IsNotExist(dockerIgnoreErr) {
    +			return nil, ignoreFile, err
    +		}
    +	}
    +	rawexcludes := strings.Split(string(ignore), "\n")
    +	excludes := make([]string, 0, len(rawexcludes))
    +	for _, e := range rawexcludes {
    +		if len(e) == 0 || e[0] == '#' {
    +			continue
    +		}
    +		excludes = append(excludes, e)
    +	}
    +	return excludes, ignoreFile, nil
    +}
    +
     // ParseRegistryCreds takes a credentials string in the form USERNAME:PASSWORD
     // and returns a DockerAuthConfig
     func ParseRegistryCreds(creds string) (*types.DockerAuthConfig, error) {
    
  • test/buildah-bud/apply-podman-deltas+2 1 modified
    @@ -245,7 +245,8 @@ skip_if_remote "Explicit request in buildah PR 4190 to skip this on remote" \
     # BEGIN tests which are skipped due to actual podman or podman-remote bugs.
     
     skip_if_remote "different error messages between podman & podman-remote" \
    -               "bud with .dockerignore #2"
    +               "bud with .dockerignore #2" \
    +               "bud with .dockerignore #4"
     
     # END   tests which are skipped due to actual podman or podman-remote bugs.
     ###############################################################################
    
  • test/e2e/build/containerignore-symlink/Dockerfile+2 0 added
    @@ -0,0 +1,2 @@
    +FROM alpine
    +COPY / /dir
    
  • test/e2e/build/containerignore-symlink/.dockerignore+1 0 added
    @@ -0,0 +1 @@
    +/tmp/private_file
    \ No newline at end of file
    
  • test/e2e/build/containerignore-symlink/hello+0 0 added
  • test/e2e/build/containerignore-symlink/world+0 0 added
  • test/e2e/build_test.go+27 0 modified
    @@ -461,6 +461,33 @@ RUN find /test`, ALPINE)
     		Expect(session.OutputToString()).To(ContainSubstring("/test/dummy"))
     	})
     
    +	It("podman remote build must not allow symlink for ignore files", func() {
    +		// Create a random file where symlink must be resolved
    +		// but build should not be able to access it.
    +		f, err := os.Create(filepath.Join("/tmp", "private_file"))
    +		Expect(err).ToNot(HaveOccurred())
    +		// Mark hello to be ignored in outerfile, but it should not be ignored.
    +		_, err = f.WriteString("hello\n")
    +		Expect(err).ToNot(HaveOccurred())
    +		defer f.Close()
    +
    +		if IsRemote() {
    +			podmanTest.StopRemoteService()
    +			podmanTest.StartRemoteService()
    +		} else {
    +			Skip("Only valid at remote test")
    +		}
    +
    +		session := podmanTest.Podman([]string{"build", "--pull-never", "-t", "test", "build/containerignore-symlink/"})
    +		session.WaitWithDefaultTimeout()
    +		Expect(session).Should(Exit(0))
    +
    +		session = podmanTest.Podman([]string{"run", "--rm", "test", "ls", "/dir"})
    +		session.WaitWithDefaultTimeout()
    +		Expect(session).Should(Exit(0))
    +		Expect(session.OutputToString()).To(ContainSubstring("hello"))
    +	})
    +
     	It("podman remote test container/docker file is not at root of context dir", func() {
     		if IsRemote() {
     			podmanTest.StopRemoteService()
    

Vulnerability mechanics

Generated on May 9, 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.