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].
- Merge pull request #16315 from flouthoc/remote-ignore-symlink · containers/podman@c8eeab2
- NVD - CVE-2022-4122
- remote,build: ignore if `.containerignore` or `.dockerignore` is a symlink outside of buildContext by flouthoc · Pull Request #16315 · containers/podman
- Symlink error leads to information disclosure
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/containers/podman/v4Go | < 4.5.0 | 4.5.0 |
Affected products
2- buildah/buildahdescription
Patches
1c8eeab21cf0aMerge pull request #16315 from flouthoc/remote-ignore-symlink
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 addedtest/e2e/build/containerignore-symlink/world+0 −0 addedtest/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
5News mentions
0No linked articles in our index yet.