Reject symlinked Containerfile and ignore files escaping build context - #6886
Reject symlinked Containerfile and ignore files escaping build context#6886Honny1 wants to merge 1 commit into
Conversation
b845b72 to
e462928
Compare
|
PTAL @podman-container-tools/buildah-maintainers @podman-container-tools/buildah-reviewers |
| 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 { |
There was a problem hiding this comment.
(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
left a comment
There was a problem hiding this comment.
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.
mtrmac
left a comment
There was a problem hiding this comment.
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.
| return false | ||
| } | ||
| clean := filepath.Clean(target) | ||
| if !filepath.IsAbs(target) && (clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator))) { |
There was a problem hiding this comment.
Shouldn’t absolute symlinks be treated as a clear attempt to break out?
| if err != nil { | ||
| return false | ||
| } | ||
| info, err := os.Lstat(path) |
There was a problem hiding this comment.
Couldn’t path have symlinks pointing outside of the target directory in the middle? ./symlink-to-etc/password. where symlink-to-etc is ../../../../../…/etc?
| return false | ||
| } | ||
| clean := filepath.Clean(target) | ||
| if !filepath.IsAbs(target) && (clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator))) { |
There was a problem hiding this comment.
path being dir1/dir2/symlink with symlink pointing to ../Containerfile does not actually escape.
|
I used |
|
|
||
| run_buildah 125 build $WITH_POLICY_JSON ${contextdir} | ||
| expect_output --substring "cannot find Containerfile or Dockerfile" | ||
| assert "$output" !~ "SHOULD-NOT-RUN" |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Docker also rejects symlinks that escape and return. I prefer matching Docker behavior.
There was a problem hiding this comment.
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|
PTAL @podman-container-tools/buildah-maintainers @podman-container-tools/buildah-reviewers |
|
to me looks like all the comments are addressed in the code, but a confirmation from @nalind would be nice. LGTM from my side |
|
I did rebase on upstream main. |
|
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-treeTo fix that, I suppose |
|
@simonbrauner I think Docker is behaving incorrectly. What if the symlink points to |
The |
I think this fix came from an upstream report where the case was exactly this "what if".
yeah but preventing it from outside |
| return ctrfile, nil | ||
| } | ||
| } | ||
| return "", fmt.Errorf("cannot find Containerfile or Dockerfile in context directory") |
There was a problem hiding this comment.
Is this supposed to be wrapping syscall.ENOENT?
|
|
||
| run_buildah 125 build $WITH_POLICY_JSON ${contextdir} | ||
| expect_output --substring "cannot find Containerfile or Dockerfile" | ||
| assert "$output" !~ "SHOULD-NOT-RUN" |
There was a problem hiding this comment.
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|
I updated the code to consistently use |
simonbrauner
left a comment
There was a problem hiding this comment.
Added one comment.
And my comment at #6886 (comment) is addressed.
| if err != nil { | ||
| // See if we have a Dockerfile within it | ||
| ctrfile = filepath.Join(path, "Dockerfile") | ||
| target, err := os.Lstat(path) |
There was a problem hiding this comment.
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 fileI 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?
There was a problem hiding this comment.
Yes, that seems right. I will take a look.
Fixes: podman-container-tools#6861 Fixes: podman-container-tools/podman#28749 Signed-off-by: Jan Rodák <hony.com@seznam.cz>
|
PTAL @nalind |
|
My comments were addressed |
| case target.Mode()&os.ModeSymlink != 0: | ||
| if resolved, ok := isRegularFileInContext(filepath.Dir(path), path); ok { | ||
| return resolved, nil | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Fixes: #6861
Fixes: podman-container-tools/podman#28749
What type of PR is this?
/kind bug
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?