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
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 Outdated
}
if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
return false
}

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 appears to run into the same issue @eriksjolund pointed out in #6885 (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.

If you mean "../../correctly-guessed/directory/name/Containerfile" pointing us back at "./Containerfile": I think to reject that we would truly need os.Root, or an equivalent manual use of openat2 RESOLVE_BENEATH (+ some implementation on non-Linux?) — or an entirely new manual symlink resolving code.

(filepath.SecureJoin is an ~equivalent of RESOLVE_IN_ROOT, it would not reject such links but interpret them differently.)

Comment thread tests/bud.bats Outdated
RUN echo traversal-back-inside-works
_EOF
# Symlink path goes ../context/file -- traverses outside but real target resolves inside.
(cd ${contextdir} && ln -s ../context/file Dockerfile)

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.

Suggested change
(cd ${contextdir} && ln -s ../context/file Dockerfile)
ln -s ../context/file ${contextdir}/Dockerfile

Comment thread tests/bud.bats Outdated
(cd ${contextdir} && ln -s ../context/file Dockerfile)

run_buildah build $WITH_POLICY_JSON ${contextdir}
assert "$output" =~ "traversal-back-inside-works"

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 think this is expected to fail while attempting to read ${contextdir}/context/file.

Comment thread tests/bud.bats Outdated
RUN test -f /dir/file
_EOF
touch ${contextdir}/file
(cd ${contextdir} && ln -s ../ign Dockerfile.dockerignore)

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.

Suggested change
(cd ${contextdir} && ln -s ../ign Dockerfile.dockerignore)
ln -s ../ign ${contextdir}/Dockerfile.dockerignore

@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

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).

Comment thread tests/bud.bats
mkdir -p ${tarsrc}
ln -s ${targetfile} ${tarsrc}/Dockerfile
local context_tar=${TEST_SCRATCH_DIR}/context.tar
tar -cf ${context_tar} -C ${tarsrc} Dockerfile

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.

Might want a --owner=0:0 --numeric-owner here.

Comment thread tests/bud.bats
ln -s ${targetfile} ${tarsrc}/Dockerfile
local contentdir=${TEST_SCRATCH_DIR}/content
mkdir -p ${contentdir}
tar -cf ${contentdir}/context.tar -C ${tarsrc} Dockerfile

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.

Might want a --owner=0:0 --numeric-owner here.

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

Labels

None yet

Projects

None yet

3 participants