Skip to content

Reject symlinked Containerfile and ignore files escaping build context - #6886

Open
Honny1 wants to merge 1 commit into
podman-container-tools:mainfrom
Honny1:fix-symlinks
Open

Reject symlinked Containerfile and ignore files escaping build context#6886
Honny1 wants to merge 1 commit into
podman-container-tools:mainfrom
Honny1:fix-symlinks

Conversation

@Honny1

@Honny1 Honny1 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Fixes: #6861
Fixes: podman-container-tools/podman#28749

What type of PR is this?

/kind api-change

/kind bug

/kind cleanup
/kind deprecation
/kind design
/kind documentation
/kind failing-test
/kind feature
/kind flake
/kind other

What this PR does / why we need it:

How to verify it

Which issue(s) this PR fixes:

Special notes for your reviewer:

Does this PR introduce a user-facing change?

Buildah no longer follows symlinked Containerfile/Dockerfile or ignore files (e.g. Dockerfile.dockerignore) that point outside the build context directory, matching docker build behavior.

Comment thread pkg/parse/parse.go Outdated
Comment thread tests/bud.bats Outdated
@Honny1
Honny1 force-pushed the fix-symlinks branch 3 times, most recently from b845b72 to e462928 Compare June 5, 2026 12:46
@Honny1
Honny1 marked this pull request as ready for review June 5, 2026 13:02
@Honny1

Honny1 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

PTAL @podman-container-tools/buildah-maintainers @podman-container-tools/buildah-reviewers

@Honny1
Honny1 requested a review from nalind June 5, 2026 13:03
Comment thread pkg/util/util.go Outdated
return foundCtrFile, nil
// isRegularFileInContext returns true if path is a regular file (or a symlink
// to one) whose real target is inside contextDir.
func isRegularFileInContext(contextDir, path string) bool {

@mtrmac mtrmac Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(A drive-by, I have really only reviewed this part:) I think this is fine — works on Unix systems, and on Windows filepath.Rel rejects inputs which differ in the volume letter.


In principle, I think building an explicit os.Root and working within that would be structurally much safer, but looking at just this diff, that would probably be an invasive change and affect public API. (I didn’t investigate how difficult that would be.)

@nalind nalind left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A subtle bug in how the symlinks get resolved, and I don't think I understand why some of these tests are using a subshell to run ln -s.

Comment thread pkg/util/util.go
Comment thread tests/bud.bats Outdated
Comment thread tests/bud.bats Outdated
Comment thread tests/bud.bats Outdated
@Honny1
Honny1 requested review from mtrmac and nalind June 8, 2026 15:29

@mtrmac mtrmac left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick look: This is getting increasingly convoluted.

If the goal is to solve https://github.com/podman-container-tools/buildah/pull/6886/changes#r3363568131, I think refactoring to build around os.Root would ultimately be safer and much easier to prove correct. But it might be very invasive, I didn’t check – I’ll let Buildah experts make the decision on whether this should happen.

Comment thread pkg/util/util.go Outdated
return false
}
clean := filepath.Clean(target)
if !filepath.IsAbs(target) && (clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn’t absolute symlinks be treated as a clear attempt to break out?

Comment thread pkg/util/util.go Outdated
if err != nil {
return false
}
info, err := os.Lstat(path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn’t path have symlinks pointing outside of the target directory in the middle? ./symlink-to-etc/password. where symlink-to-etc is ../../../../../…/etc?

Comment thread pkg/util/util.go Outdated
return false
}
clean := filepath.Clean(target)
if !filepath.IsAbs(target) && (clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator))) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path being dir1/dir2/symlink with symlink pointing to ../Containerfile does not actually escape.

@Honny1

Honny1 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

I used os.Root in the parts that I touched. I think that refactoring makes sense to me, but not in this PR. Maybe we should create an issue for this.

@Honny1
Honny1 requested a review from mtrmac June 9, 2026 15:36
@Honny1

Honny1 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

PTAL @nalind @mtrmac

@nalind nalind left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're going to want to rebase to pick up missing packages that are causing those "pivot_root: command not found" test failures, added to the VM images that we updated to in #6911.

Comment thread tests/bud.bats Outdated

run_buildah 125 build $WITH_POLICY_JSON ${contextdir}
expect_output --substring "cannot find Containerfile or Dockerfile"
assert "$output" !~ "SHOULD-NOT-RUN"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're treating ${contextdir} like the root of a container rootfs (which I think approximates the desired behavior for resolving symlinks found inside of a context directory), I would have expected the target of the symlink to be readable. I believe this is an example from #6861 (comment).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docker also rejects symlinks that escape and return. I prefer matching Docker behavior.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This succeeds for me:

t=$(mktemp -d)
cat > $t/file << EOF
FROM busybox
RUN echo should run
EOF
ln -s ../file $t/Dockerfile
env DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain $t

Comment thread tests/bud.bats Outdated
Comment thread tests/bud.bats Outdated
@Honny1

Honny1 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

PTAL @podman-container-tools/buildah-maintainers @podman-container-tools/buildah-reviewers

@Honny1
Honny1 requested a review from nalind June 25, 2026 08:22
@inknos

inknos commented Jul 1, 2026

Copy link
Copy Markdown

to me looks like all the comments are addressed in the code, but a confirmation from @nalind would be nice. LGTM from my side

@Honny1

Honny1 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

I did rebase on upstream main.

@simonbrauner

Copy link
Copy Markdown

I found another incompatibility with Docker. If Docker sees an absolute symlink, it seems to assume that it is relative to the context directory.

Example:

$ tree
.
├── Dockerfile -> /subdirectory/remoteContainerfile
├── subdirectory
│   └── remoteContainerfile

$ buildah build -t demo .
Error: cannot find Containerfile or Dockerfile in context directory

$ docker build -t demo .
[+] Building 0.6s (6/6) FINISHED                                                                                             docker:default
 => [internal] load build definition from Dockerfile                                                                                   0.0s
 => => transferring dockerfile: 369B                                                                                                   0.0s
 => [internal] load metadata for docker.io/library/alpine:3.20                                                                         0.2s
 => [internal] load .dockerignore                                                                                                      0.0s
 => => transferring context: 2B                                                                                                        0.0s
 => [1/2] FROM docker.io/library/alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc                   0.0s
 => => resolve docker.io/library/alpine:3.20@sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc                   0.0s
 => CACHED [2/2] RUN echo test                                                                                                         0.0s
 => exporting to image                                                                                                                 0.2s
 => => exporting layers                                                                                                                0.0s
 => => exporting manifest sha256:e1411423e0df8c2b0076af6f325c2eda5c2ba1265f25a44e872894761079b209                                      0.0s
 => => exporting config sha256:e0e31f31aa3e5a3b9948bac3fa41774ba71ee2fc6f388137fbf3ca6f330a4d87                                        0.0s
 => => exporting attestation manifest sha256:b5718437215f4920ed86f4b2d0bacbe9f3e9c1a48db51a2c033ee6b2111a5241                          0.0s
 => => exporting manifest list sha256:95ea0c2f6f8be79fa4fbcd3ce7052e35c2e61f5f4695ec9ee4a3573ed638482a                                 0.0s
 => => naming to docker.io/library/demo:latest                                                                                         0.0s
 => => unpacking to docker.io/library/demo:latest                                                                                      0.0s
WARNING: current commit information was not captured by the build: failed to read current commit information with git rev-parse --is-inside-work-tree

To fix that, I suppose securejoin.SecureJoin could be used to join the context directory and the symlink. And then checking if the result points to a file or not.

@Honny1

Honny1 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

@simonbrauner I think Docker is behaving incorrectly. What if the symlink points to /etc/passwd?

@simonbrauner

Copy link
Copy Markdown

@simonbrauner I think Docker is behaving incorrectly. What if the symlink points to /etc/passwd?

The /etc/passwd of the host is safe as long as the build does not happen at /. It is quirky to have a symlink that does not work on its own but works in build, but technically we cannot say that anything escaped build context in that behavior, right?

@inknos

inknos commented Jul 13, 2026

Copy link
Copy Markdown

What if the symlink points to /etc/passwd?

I think this fix came from an upstream report where the case was exactly this "what if".

but technically we cannot say that anything escaped build context in that behavior, right?

yeah but preventing it from outside build is better imho

Comment thread pkg/util/util.go Outdated
return ctrfile, nil
}
}
return "", fmt.Errorf("cannot find Containerfile or Dockerfile in context directory")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be wrapping syscall.ENOENT?

Comment thread tests/bud.bats Outdated

run_buildah 125 build $WITH_POLICY_JSON ${contextdir}
expect_output --substring "cannot find Containerfile or Dockerfile"
assert "$output" !~ "SHOULD-NOT-RUN"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This succeeds for me:

t=$(mktemp -d)
cat > $t/file << EOF
FROM busybox
RUN echo should run
EOF
ln -s ../file $t/Dockerfile
env DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain $t

Comment thread pkg/util/util.go
@Honny1

Honny1 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

I updated the code to consistently use securejoin.SecureJoin (RESOLVE_IN_ROOT) for both Containerfile discovery and ignore file resolution, replacing the previous use of os.Root (RESOLVE_BENEATH). This should match Docker BuildKit's behavior: .. components are clamped to the context root, and absolute symlink targets are re-rooted rather than being rejected outright.

PTAL @nalind @inknos @simonbrauner @mtrmac

@Honny1
Honny1 requested review from inknos and nalind July 14, 2026 12:00

@simonbrauner simonbrauner left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added one comment.

And my comment at #6886 (comment) is addressed.

Comment thread pkg/util/util.go
if err != nil {
// See if we have a Dockerfile within it
ctrfile = filepath.Join(path, "Dockerfile")
target, err := os.Lstat(path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If lstat is ran there, it seems to cause trouble when the symlink is to a directory.

$ ls -l
lrwxrwxrwx. 1 sbrauner sbrauner   16 Jul 14 14:36 test-repository5 -> test-repository3

$ cd test-repository5

$ ls Dockerfile 
Dockerfile

$ docker build -t demo .
[+] Building 1.3s (6/6) FINISHED                                     docker:default

$ buildah build -t demo .
Error: assumed Containerfile "test-repository5" is not a file

I would propose either a special case for directories, or delaying the lstat call after we know that it is not a directory. Because building in a directory which is a symlink is a valid use case, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that seems right. I will take a look.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good now

Comment thread pkg/util/util_test.go Outdated
Comment thread pkg/util/util_test.go
Comment thread pkg/util/util_test.go Outdated
@Honny1

Honny1 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

PTAL @nalind

@simonbrauner

Copy link
Copy Markdown

My comments were addressed

Comment thread pkg/util/util.go
case target.Mode()&os.ModeSymlink != 0:
if resolved, ok := isRegularFileInContext(filepath.Dir(path), path); ok {
return resolved, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what we gain by ensuring that the last component in a pathname is not a symbolic link to somewhere outside of its parent directory when that directory can already be outside of any known build context directory.
I would be fine with having this function reject outright context locations that are neither directories nor symlinks to directories.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be fine with having this function reject outright context locations that are neither directories nor symlinks to directories.

But that would be a breaking change, no? Currently we do support file as an argument in the place of [context].

buildah build -t demo test-repository6/Dockerfile seems to do the same thing as buildah build -t demo test-repository6.

But it does not seem to work together with -f:

$ buildah build -t demo -f test-repository6/Dockerfile test-repository6/Dockerfile
Error: mounting an overlay over build context directory: creating overlay scaffolding for build context directory: mount overlay:/var/tmp/buildah-context-231279313/overlay/388852530/merge, data: lowerdir=/home/sbrauner/Desktop/cve-work/buildah-build/test-repository6/Dockerfile,upperdir=/var/tmp/buildah-context-231279313/overlay/388852530/upper,workdir=/var/tmp/buildah-context-231279313/overlay/388852530/work,context="system_u:object_r:container_file_t:s0:c231,c966",userxattr: invalid argument

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants