Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buildah build: use the same overlay for the context directory for the whole build #5975

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

nalind
Copy link
Member

@nalind nalind commented Feb 5, 2025

What type of PR is this?

/kind feature

What this PR does / why we need it:

Change how we handle bind mounts of the build context directory from using a different upper for each read-write mount into it, to using one that lasts the duration of the full build.

We broke workflows which wrote content to the build context in one stage and then attempted to use archive or layout locations in the build context in a subsequent stage when we fixed CVE-2024-11218, and this should get them working again. Because that content was often addressed using relative path names which are no longer correct for the written location, we try to compensate by rewriting some types of references and straight-up rejecting some others.

How to verify it

New integration test! And some updated ones, too!

Which issue(s) this PR fixes:

Proposed fix for #5952.

Special notes for your reviewer:

Does this PR introduce a user-facing change?

Changes "written" to the build context directory during `buildah build` `RUN --mount=type=bind` instructions are no longer discarded between instructions, but when the build completes, whether it completes successfully or not.

Copy link

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

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

Thanks so much for working on this! I just skimmed the code, will try testing it soon

@@ -63,6 +63,13 @@ func MountWithOptions(contentDir, source, dest string, opts *Options) (mount spe
if err := os.Chown(upperDir, int(stat.Uid), int(stat.Gid)); err != nil {
return mount, err
}
times := []syscall.Timeval{

Choose a reason for hiding this comment

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

Why out of curiosity? Is this helping ensure reproducibility? We aren't serializing anything related to the timestamps of this directory to e.g. a tar stream are we?

Copy link
Member Author

Choose a reason for hiding this comment

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

Without it, two different runs would appear to have different timestamps on the top level of the build context directory, breaking one of the tests added by #5691.

// indicates whether we did, in fact, mount an overlay; a cleanup function
// which should be called when the location is no longer needed (on success);
// and a non-nil fatal error if any of that failed.
func platformSetupContextDirectoryOverlay(store storage.Store, options *define.BuildOptions) (string, string, string, bool, func(), error) {

Choose a reason for hiding this comment

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

Return values are a bit unwieldy, maybe a struct instead? Although I guess the single caller unpacks them anyways, so doesn't matter. Yeah, one caller, so makes sense as is.

pkg/cli/build.go Outdated
@@ -113,7 +113,7 @@ func GenBuildOptions(c *cobra.Command, inputArgs []string, iopts BuildOptions) (
if c.Flag("build-context").Changed {
for _, contextString := range iopts.BuildContext {
av := strings.SplitN(contextString, "=", 2)
if len(av) > 1 {
if len(av) > 1 && av[0] != "" && av[1] != "" {

Choose a reason for hiding this comment

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

This is basically so we skip over -v : or something? This could use a comment, maybe even we have a const DefaultBuildContextKey = "" and then reference it here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Pushing the check on av[1] down into GetAdditionalBuildContext() makes it a little easier to follow, added a comment here and put more checks in there.

@@ -673,7 +673,7 @@ symlink(subdir)"
_prefetch busybox
run_buildah 125 build -t testbud3 $WITH_POLICY_JSON $BUDFILES/dockerignore3
expect_output --substring 'building.*"COPY test1.txt /upload/test1.txt".*no such file or directory'
expect_output --substring $(realpath "$BUDFILES/dockerignore3/.dockerignore")
expect_output --substring 'filtered out using /[^ ]*/.dockerignore'

Choose a reason for hiding this comment

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

It's not obvious to me how this test change is related...did the build there try to modify the context directory?

Copy link
Member Author

Choose a reason for hiding this comment

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

The name of the directory which contains the .dockerignore file and which is included in the error message is no longer the one passed on the command line, so the exact string comparison began failing. It was either change the test to not care about the specific path, or lie about the file's location to get the test to pass as it was, so I changed the test.

@nalind nalind force-pushed the overlay-build-context branch 2 times, most recently from 1e2d39c to 82ee991 Compare February 6, 2025 21:11
@@ -916,6 +929,58 @@ func (s *StageExecutor) UnrecognizedInstruction(step *imagebuilder.Step) error {
return errors.New(err)
}

// do our best to ensure that image specifiers that include a transport that
// uses path names are scoped to the build context directory

Choose a reason for hiding this comment

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

It'd be secure and reliable to get a directory fd for the context overlay mount, and then use openat(..., RESOLVE_IN_ROOT) to access the file, then...the ugly part is that the containers-storage APIs want path strings still, but we could pass e.g. oci:/proc/self/fd/N right? I've done that elsewhere I think.

Copy link
Member Author

Choose a reason for hiding this comment

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

Proper safety for such references is a much larger patch.

@cgwalters
Copy link

Trying to test this, but unrelated to the contents of this PR, I get the same problem trying git main:

$ ./bin/buildah from busybox
busybox-working-container
$ ./bin/buildah run busybox-working-container echo true
error running container: did not get container start message from parent: EOF
Error: setup network: pasta failed with exit code 1:
Couldn't open network namespace /proc/126984/ns/net: Permission denied
DEBU[0000] Running ["/usr/bin/crun" "create" "--bundle" "/var/tmp/buildah1984752272" "--pid-file" "/var/tmp/buildah1984752272/pid" "--no-new-keyring" "buildah-buildah1984752272"] 
DEBU[0000] waiting for parent start message             
DEBU[0000] pasta arguments: --config-net --dns-forward 169.254.1.1 -t none -u none -T none -U none --no-map-gw --quiet --netns /proc/126381/ns/net --map-guest-addr 169.254.1.2 
DEBU[0000] "/var/tmp/buildah1984752272/mnt/buildah-bind-target-10" is apparently not really mounted, skipping 
DEBU[0000] "/var/tmp/buildah1984752272/mnt/rootfs" is apparently not really mounted, skipping 
DEBU[0000] "/var/tmp/buildah1984752272/mnt" is apparently not really mounted, skipping 
error running container: did not get container start message from parent: EOF
DEBU[0000] Error building at step {Env:[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin] Command:run Args:[touch /blah] Flags:[] Attrs:map[] Message:RUN touch /blah Heredocs:[] Original:RUN touch /blah}: setup network: pasta failed with exit code 1:
Couldn't open network namespace /proc/126381/ns/net: Permission denied 
Error: building at STEP "RUN touch /blah": setup network: pasta failed with exit code 1:
Couldn't open network namespace /proc/126381/ns/net: Permission denied

Is there something I need to do here to sync up the buildah binary with the default network config? Odd...

@cgwalters
Copy link

Nevermind of course after I posted my "wait these are weird denials" mental flag tripped and of course it's SELinux; a chcon --reference /usr/bin/buildah bin/buildah fixes it.

@cgwalters
Copy link

OK so this works for me:

FROM registry.access.redhat.com/ubi9/ubi:latest  as builder
RUN --mount=type=bind,src=.,rw,dst=/buildcontext \
  dnf -y install skopeo && skopeo copy docker://busybox oci:/buildcontext/out.oci

FROM oci:./out.oci
# Need to reference builder here to force ordering.
RUN --mount=type=bind,from=builder,src=.,target=/var/tmp true

So that's already a lot cleaner, thanks!


There is an important note here that this does break compatibility with the previous dockerfiles, I get:

Error: building at STEP "RUN --mount=type=bind,rw=true,src=.,dst=/buildcontext,bind-propagation=shared dnf -y install skopeo && skopeo copy docker://busybox oci:/buildcontext/out.oci": resolving mountpoints for container "7f7333013ba83fbe05c78aad0ef4edd17663317f4daa775e549ac11d28412a90": rw: must not provide an argument for option

Looks like changing rw=true to just rw fixes things. Not sure if that's intentional or not? It does look like there's no mention of rw=true in the upstream Dockerfile syntax.


Now that I dig in a bit more, one unfortunate thing here is that the need to use the RUN to force ordering...has meant this whole time we're subject to all the "injected content" bugs like #4242 and #5950 etc.

When I unpack and inspect the resulting image, we have this final layer with

drwxr-xr-x 0/0               0 2025-02-06 16:44 etc/
-rwx------ 0/0               0 2025-02-06 16:44 etc/hostname
-rwx------ 0/0               0 2025-02-06 16:44 etc/hosts
-rwx------ 0/0               0 2025-02-06 16:44 etc/resolv.conf
drwxr-xr-x 0/0               0 2025-02-06 16:44 proc/
drwxr-xr-x 0/0               0 2025-02-06 16:44 run/
drwxr-xr-x 0/0               0 2025-02-06 16:44 sys/
drwxr-xr-x 0/0               0 2025-02-06 16:44 var/
drwxr-xr-t 0/0               0 2025-02-06 16:44 var/tmp/

with floating timestamps. That said hmm, actually in this case actually it works to do buildah build --timestamp=<pinned> and that won't also change all the timestamps on the inherited layers. So that's a good mitigation.

But the sad thing is that these things just keep piling on...now for me we have a new one of these in Konflux which is injecting content_sets which also has no attempt made to generate timestamp-reproducible data right now either (and it uses buildah too, so it inherits that, but we still need to ensure the timestamps of the files that we actually write and aren't synthetic container runtime artifacts are canonicalized too)

So in the end, I think we'll definitely try to

@nalind
Copy link
Member Author

nalind commented Feb 6, 2025

There is an important note here that this does break compatibility with the previous dockerfiles, I get:

Error: building at STEP "RUN --mount=type=bind,rw=true,src=.,dst=/buildcontext,bind-propagation=shared dnf -y install skopeo && skopeo copy docker://busybox oci:/buildcontext/out.oci": resolving mountpoints for container "7f7333013ba83fbe05c78aad0ef4edd17663317f4daa775e549ac11d28412a90": rw: must not provide an argument for option

Looks like changing rw=true to just rw fixes things. Not sure if that's intentional or not? It does look like there's no mention of rw=true in the upstream Dockerfile syntax.

Not being strict about the arguments for --mount contributed to CVE-2024-9407. I added more checks in that area in #5925, and they would have landed in v1.39.0.

@nalind nalind force-pushed the overlay-build-context branch 3 times, most recently from 63f0cda to 5b05378 Compare February 13, 2025 19:26
@nalind nalind marked this pull request as ready for review February 13, 2025 20:17
@nalind nalind force-pushed the overlay-build-context branch 5 times, most recently from e980b6c to c7c4667 Compare February 20, 2025 15:50
@nalind nalind force-pushed the overlay-build-context branch 2 times, most recently from 53b3831 to 30a0864 Compare February 28, 2025 15:48
@@ -546,7 +546,7 @@ func (s *StageExecutor) performCopy(excludes []string, copies ...imagebuilder.Co
}

Choose a reason for hiding this comment

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

(not commenting on the content of this commit) there's a typo in the commit subject

Copy link
Member Author

Choose a reason for hiding this comment

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

Dang it, that's embarrassing. Fixing.

Comment on lines 961 to 962
// names are evaluated relative to "contextDir", it will create a copy of the
// original image, under "tmpdir", which contains no symbolic links.

Choose a reason for hiding this comment

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

Not a blocker but there's already a lot of copying involved in this flow (needing to make a physically separate oci directory as we can't directly write to c/storage in this flow)...forcing another copy is unfortunate. Especially because I don't believe it's strictly necessary since as far as I understand, there shouldn't be any untrusted processes running at this point, so we don't need to worry about concurrent mutation.

And for symlink following again I think the right fix is to have the underlying code use openat2(RESOLVE_IN_ROOT, ...) or so. I know you said there are use cases that rely on symlink following, but it seems to me we could make the "resolve in root" an option when opening the transport at least.

And if we do that there'd be a lot less code here...it'd just be calling transport.ResolveInRoot() or so.

Copy link
Member Author

Choose a reason for hiding this comment

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

Not a blocker but there's already a lot of copying involved in this flow (needing to make a physically separate oci directory as we can't directly write to c/storage in this flow)...forcing another copy is unfortunate. Especially because I don't believe it's strictly necessary since as far as I understand, there shouldn't be any untrusted processes running at this point, so we don't need to worry about concurrent mutation.

We have a --jobs CLI flag and equivalent API setting, so I must disagree.

@nalind nalind force-pushed the overlay-build-context branch 2 times, most recently from 5acfff7 to cd22688 Compare March 3, 2025 20:58
@nalind
Copy link
Member Author

nalind commented Mar 3, 2025

@containers/buildah-maintainers PTAL

@nalind nalind force-pushed the overlay-build-context branch from cd22688 to 18a5886 Compare March 5, 2025 16:17
@@ -980,7 +1286,7 @@ func (s *StageExecutor) prepare(ctx context.Context, from string, initializeIBCo

builderOptions := buildah.BuilderOptions{
Args: ib.Args,
FromImage: from,
FromImage: sanitizedFrom,
Copy link
Collaborator

Choose a reason for hiding this comment

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

A small question since I did not understand it completely, why are are we creating copy of entire image for every stage ? Is it just to make sure no symbolic links are present ?

Choose a reason for hiding this comment

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

Discussed here #5975 (comment) and again here #5975 (comment)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be fair to ask and see about how much overhead does this patch adds both disk usage wise and speed time.

I could be wrong but I could think of use-case where a stage copied lot of external artifacts but only parts of it are being used in further stages, this will end up creating copies for every future stage even though those parts are not being used by future stages.

Should we consider making this behavior optional ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Would it be fair to ask and see about how much overhead does this patch adds both disk usage wise and speed time.

The disk cost is a second copy of the base image being copied to a temporary location.
The speed cost is the cost of copying that content, which is unavoidably higher if we're using fuse-overlayfs, and likewise reading contents from the build context through that overlay to avoid inconsistencies.

I could be wrong but I could think of use-case where a stage copied lot of external artifacts but only parts of it are being used in further stages, this will end up creating copies for every future stage even though those parts are not being used by future stages.

I don't understand what you're describing here. Can you elaborate on that? Initializing a stage using a previous stage's result as its base doesn't involve the build context directory, so I don't follow where additional copies would appear.

Should we consider making this behavior optional ?

Using an overlay over the build context directory is not something we should turn off. Turning off sharing like this PR introduces doesn't reduce any of the costs that are incurred when they're not shared, so I don't really see the benefit of adding complexity there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't understand what you're describing here. Can you elaborate on that? Initializing a stage using a previous stage's result as its base doesn't involve the build context directory, so I don't follow where additional copies would appear.

I was thinking for use-case something like this

FROM alpine as first
COPY /large-fileA-from-host .
COPY /large-fileB-from-host .
COPY /large-fileC-from-host .


FROM first as second 
# Use only large-file-A in this stage
# but this will create another copy of `first` containing all three files

FROM first as third 
# Use only large-file-B in this stage
# # but this will create another copy of `first` containing all three files

FROM first as fourth 
# Use only large-file-C in this stage
# but this will create another copy of `first` containing all three files

@nalind If i understand correctly for all the three stages second, third and fourth a separate copy of first stage will be created , am i correct ? and I am not sure if buildah was doing this before.

Copy link
Member Author

Choose a reason for hiding this comment

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

The stages nicknamed "second", "third", and "fourth" would each start with their own working container based on the image that was committed at the end of the stage nicknamed "first", as before. This PR doesn't affect how we handle the root filesystems of working containers, or committing images.

The COPY instructions have to read contents through the mounted overlay for consistency's sake, but I don't see additional copies of them being made here. Since they're not being modified, they'd be read directly from the build context directory that's being used as the "lowerdir" for the overlay, without being "copied up".

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah i see sounds fair to me.

// those it accepts which refer to filesystem objects, where relative path
// names are evaluated relative to "contextDir", it will create a copy of the
// original image, under "tmpdir", which contains no symbolic links.
func (s *StageExecutor) sanitizeFrom(from, tmpdir string) (newFrom string, err error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we document what is value contained in newFrom string ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Adding.

// sanitizeImageReference limits which image transports we'll accept. For
// those it accepts which refer to filesystem objects, where relative path
// names are evaluated relative to "contextDir", it will create a copy of the
// original image, under "tmpdir", which contains no symbolic links.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we document what is value contained in newFrom string ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Adding.

@@ -394,7 +403,7 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
return id, ref, nil
}

func buildDockerfilesOnce(ctx context.Context, store storage.Store, logger *logrus.Logger, logPrefix string, options define.BuildOptions, containerFiles []string, dockerfilecontents [][]byte) (string, reference.Canonical, error) {
func buildDockerfilesOnce(ctx context.Context, store storage.Store, logger *logrus.Logger, logPrefix string, options define.BuildOptions, containerFiles []string, dockerfilecontents [][]byte, processLabel, mountLabel string, usingContextOverlay bool) (string, reference.Canonical, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit and not a blocker probably a todo for future: probably buildDockerfilesOnce should start consuming a struct.

)

// platformSetupContextDirectoryOverlay() may set up an overlay _over_ the
// build context directory, and sorts out labeling. Returns either the new
Copy link
Collaborator

Choose a reason for hiding this comment

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

Comment says returns base build context or the old location I can't see in which case it returns old location is it relevant still ? Maybe worth adding a comment when does it returns old location.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right, it never returns the original location. Updating the godoc.

// those it accepts which refer to filesystem objects, where relative path
// names are evaluated relative to "contextDir", it will create a copy of the
// original image, under "tmpdir", which contains no symbolic links.
func sanitizeImageName(transportName, restOfImageName, contextDir, tmpdir string) (newFrom string, err error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe as a refactor we can break this function into several helper function for each transport it will help with the readability and maintenance.


// platformSetupContextDirectoryOverlay() may set up an overlay _over_ the
// build context directory, and sorts out labeling. Returns either the new
// location which should be used as the base build context or the old location;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as https://github.com/containers/buildah/pull/5975/files#r1981811503 I think comment needs to be fixed.

@nalind nalind force-pushed the overlay-build-context branch 2 times, most recently from 019c57c to 97eca26 Compare March 5, 2025 22:43
@cevich
Copy link
Member

cevich commented Mar 6, 2025

Would this address #5988 also or is that something different?

Copy link
Contributor

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

A few misc. comments, but I’m generally unfamiliar with the codebase and structure, so I didn’t really check lifetimes / interactions / … .

The StageMountPoints[""] case worries me. Would adding an extra field (StageContextMount *StageMountDetails // nil if N/A) work?

(Generally the functions have too many unnamed parameters and unnamed return values for my personal taste, but this is totally up to maintainers of this project.)

// this is a normal-looking image-in-a-registry-or-named-in-storage name
return from, nil
}
if img, err := s.executor.store.Image(from); img != nil && err == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

… is it expected that Containerfiles can refer to image IDs? That can potentially allow builds on a shared host to refer to images which the invoking user has no right to pull, if the invoking user can somehow determine the config digest.

OTOH… I can imagine that there might be uses.

Copy link
Member Author

Choose a reason for hiding this comment

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

Internally, we rewrite references to nicknames of previous stages to their IDs before we get to this point, since they don't get names assigned to them.

if linkname != "/" {
linkname = strings.TrimPrefix(linkname, "/")
}
if _, validTarget := seenEntries[linkname]; !validTarget {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it generally guaranteed that symlink destinations exist in tar files before the symlinks that refer to them?

Generally, symlinks can be dangling and refer to nothing…

Copy link
Member Author

Choose a reason for hiding this comment

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

Not generally, but in the very specific case of docker-archives, that does appear to be something we can safely expect, at least from containers/image.

// names are evaluated relative to "contextDir", it will create a copy of the
// original image, under "tmpdir", which contains no symbolic links. It it
// returns a parseable reference to the image which should be used.
func sanitizeImageName(transportName, restOfImageName, contextDir, tmpdir string) (newFrom string, err error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

(Absolutely non-blocking: I’d personally be trying to structure this as an object with methods, in a separate file, or maybe even a separate internal subpackage. E.g. the nested functions are impossible to unit-test.)

Comment on lines 1019 to 1021
if err := tw.WriteHeader(hdr); err != nil {
return fmt.Errorf("rewriting archive of base image: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

(Nit: I think this can be moved after the switch. It works fine as is.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Moving it.


// write this header/content combination to a tar writer, making sure
// that it doesn't include any symbolic links that point to something
// outside of the archive
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// outside of the archive
// outside of the archive. Overwrites contents of `hdr`.

Comment on lines 1191 to 1197
defer func() {
if !succeeded {
if err := os.RemoveAll(newImageDestination); err != nil {
logrus.Warn(err)
}
}
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

This already exists before the switch.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, a relic from an earlier revision. Dropping.

// start reading the archived content
defer func() {
if err := imageArchive.Close(); err != nil {
logrus.Warn(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

In various similar situations: either the error can be ignored, or the user should be given enough context to actually do something about it (at least the path).

// if the archive we're parsing is expected to have an archive as its only item,
// assume it's the first (and hopefully, only) item, and switch to stepping through
// it as the archive
if isEmbeddedArchive {
Copy link
Contributor

Choose a reason for hiding this comment

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

Non-blocking: Why does all this logic exist? For the -archive: transports, I’d expect an unmodified copy of the single tarball file, as a first guess.

Is the goal here to provide a seekable uncompressed file so that the -archive: transports don’t make a second on-disk copy? That would be an improvement for docker-archive, but oci-archive currently just decompresses the whole thing to disk, so having the intermediate copy uncompressed increases the size without a benefit I can see.

(Also, this code exposes writeToArchive/writeToDirectory to more opportunities for the attacker to prepare unexpected tar metadata, compared to the cases where we are creating the tar files ourselves.)

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't know that these locations contain contents we can trust, so the goal is to filter out things that we know would be problematic or even out of the ordinary. I can't claim it's perfect, though.

@@ -248,6 +249,7 @@ func GetBindMount(sys *types.SystemContext, args []string, contextDir string, st
if additionalMountPoints != nil {
if val, ok := additionalMountPoints[fromWhere]; ok {
mountPoint = val.MountPoint
skipOverlay = val.IsWritesDiscardedOverlay
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn’t this ~always false because fromWhere != ""? OTOH maybe the consistency is nice.

Copy link
Member Author

Choose a reason for hiding this comment

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

It should almost always be true, except for the default build context on non-Linux platforms, where we don't have the ability to use read-write overlays yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

IsWritesDiscardedOverlay is only ever mentioned in a “write” position once, when setting stageMountPoints[""]; on this code path, fromWhere != "", so we should see the default field value of false; am I misreading this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, right, we haven't mounted an overlay there yet when it's something else. You're reading it right.

@@ -208,19 +213,34 @@ func CommonBuildOptionsFromFlagSet(flags *pflag.FlagSet, findFlagFunc func(name

// GetAdditionalBuildContext consumes raw string and returns parsed AdditionalBuildContext
func GetAdditionalBuildContext(value string) (define.AdditionalBuildContext, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Locally, it’s not obvious that the checks for "" are important; probably worth a more detailed comment.

Copy link
Member Author

Choose a reason for hiding this comment

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

Adding a note there.

@nalind nalind force-pushed the overlay-build-context branch from 97eca26 to b41e78a Compare March 11, 2025 19:17
Copy link
Collaborator

@flouthoc flouthoc left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Contributor

openshift-ci bot commented Mar 11, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: flouthoc, nalind

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Copy link
Contributor

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

The resolution of my earlier comments LGTM.

I don’t know whether that should count as a review… If necessary, ping me and I’ll return to this and actually study the imagebuildah structure as a whole.

@nalind nalind force-pushed the overlay-build-context branch from b41e78a to 7e6b859 Compare March 12, 2025 21:18
nalind added 5 commits March 13, 2025 15:14
In addition to setting the (usually recently-created) upper directory's
ownership and permissions to match those of the lower (the location of
which wasn't known when the upper was created), set the timestamps to
match, too.

Signed-off-by: Nalin Dahyabhai <[email protected]>
When chown()ing the upper directory to match the lower directory, if the
ownership of the lower directory is the overflow UID:GID, ignore EINVAL.

Signed-off-by: Nalin Dahyabhai <[email protected]>
Mount a read-write overlay directory over the build context directory to
restore the ability to use it as a covert cache of sorts during the
lifetime of the build, but in a way that still ensures that we don't
modify the real build context directory.

N.B.: builds where FROM in one stage referenced a relative path which
had been written to a bind-mounted default build context directory by an
earlier stage broke when we started making those bind mounts into
overlays to prevent/discard modifications to that directory, and while
this extends the lifetime of that overlay so that it's consistent
throughout the build, those relative path names are still going to point
to the wrong location.

Since we need to determine SELinux labeling before mounting the overlay,
go ahead and calculate the labels to use before creating the first
builder, and remove the logic that had whichever stage thought it was
the first one set them in its parent object for use by other stages, in
what was probably a racey way.

Signed-off-by: Nalin Dahyabhai <[email protected]>
Try to limit which image transports we accept in stages, and scope the
ones that use path names to the context directory.  At some point
anything that isn't an image ID or pullable spec should start being
rejected.

Signed-off-by: Nalin Dahyabhai <[email protected]>
Add a missing "not" to an error message.

Signed-off-by: Nalin Dahyabhai <[email protected]>
@nalind nalind force-pushed the overlay-build-context branch from 7e6b859 to 2d7ce30 Compare March 13, 2025 19:14
@TomSweeneyRedHat
Copy link
Member

LGTM
@nalind Looks like the branch needs an update.

@nalind
Copy link
Member Author

nalind commented Mar 21, 2025

This is going to exacerbate #5988 for people who are running into it.
/hold

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved do-not-merge/hold kind/feature Categorizes issue or PR as related to a new feature.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants