Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions build/opt.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,11 +876,7 @@ func CreateExports(entries []*buildflags.ExportEntry) ([]client.ExportEntry, []s
if err == nil && fi.IsDir() {
return nil, nil, errors.Errorf("destination file %s is a directory", entry.Destination)
}
f, err := os.Create(entry.Destination)
if err != nil {
return nil, nil, errors.Errorf("failed to open %s", err)
}
out.Output = wrapWriteCloser(f)
out.Output = wrapWriteCloserLazy(entry.Destination)
localPaths = append(localPaths, entry.Destination)
}
}
Expand All @@ -896,6 +892,38 @@ func wrapWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser,
}
}

type lazyFileWriter struct {
path string
file *os.File
}

func (w *lazyFileWriter) Write(p []byte) (int, error) {
if w.file == nil {
if err := os.MkdirAll(filepath.Dir(w.path), 0755); err != nil {
return 0, err
}
f, err := os.Create(w.path)
if err != nil {
return 0, err
}
w.file = f
}
return w.file.Write(p)
}

func (w *lazyFileWriter) Close() error {
if w.file != nil {
return w.file.Close()
}
return nil
}

func wrapWriteCloserLazy(path string) func(map[string]string) (io.WriteCloser, error) {
return func(map[string]string) (io.WriteCloser, error) {
return &lazyFileWriter{path: path}, nil
}
}

func CreateCaches(entries []*buildflags.CacheOptionsEntry) []client.CacheOptionsEntry {
var outs []client.CacheOptionsEntry
if len(entries) == 0 {
Expand Down
4 changes: 3 additions & 1 deletion tests/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -1359,17 +1359,19 @@ target "default" {

dirDest := t.TempDir()

envs := []string{"BUILDX_METADATA_PROVENANCE=" + metadataMode}
outFlag := "default.output=type=docker"
if sb.DockerAddress() == "" {
// there is no Docker atm to load the image
outFlag += ",dest=" + dirDest + "/image.tar"
envs = append(envs, "BUILDX_BAKE_ENTITLEMENTS_FS=0")
Copy link
Member Author

Choose a reason for hiding this comment

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

Needs to make changes in this test to disable fs entitlements otherwise it fails: https://github.com/docker/buildx/actions/runs/18711570130/job/53361047820?pr=3478#step:7:481

=== RUN   TestIntegration/TestBuildMetadataProvenance/worker=remote/default
    bake.go:1375: 
        	Error Trace:	/src/tests/bake.go:1375
        	            				/src/tests/bake.go:1334
        	Error:      	Received unexpected error:
        	            	exit status 1
        	Test:       	TestIntegration/TestBakeMetadataProvenance/worker=remote/max
        	Messages:   	#0 building with "integration-remote-44bdm3ecqo4ofzttczc4bg3yy" instance using remote driver
        	            	
        	            	#1 [internal] load local bake definitions
        	            	#1 reading docker-bake.hcl 22B / 22B done
        	            	#1 DONE 0.0s
        	            	Your build is requesting privileges for following possibly insecure capabilities:
        	            	
        	            	 - Write access to path ../002
        	            	
        	            	Pass "--allow=fs.write=../002" to grant requested privileges.
        	            	
        	            	Your full command with requested privileges:
        	            	
        	            	buildx bake --allow=fs.write=../002 --metadata-file /tmp/TestIntegrationTestBakeMetadataProvenanceworker=remotemax18445594/002/md.json --set default.output=type=docker,dest=/tmp/TestIntegrationTestBakeMetadataProvenanceworker=remotemax18445594/002/image.tar
        	            	
        	            	To disable filesystem entitlements checks, you can set BUILDX_BAKE_ENTITLEMENTS_FS=0 .
        	            	
        	            	ERROR: additional privileges requested

Seems fs entitlements were not correctly applied for tar output 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.

Seems fs entitlements were not correctly applied for tar output before.

As we lazily create directories now for tar output then it goes through

buildx/bake/entitlements.go

Lines 595 to 603 in abf6ab4

// If the component doesn't exist, return the last valid path
if os.IsNotExist(err) {
for r := len(dest) - 1; r >= volLen; r-- {
if os.IsPathSeparator(dest[r]) {
return dest[:r], in[start:], nil
}
}
return vol, in[start:], nil
}
and returns the last valid path which triggers the privileges request.

Before the tar output file was already created before entitlement check:

f, err := os.Create(entry.Destination)
bo.Exports, bo.ExportsLocalPathsTemporary, err = build.CreateExports(t.Outputs)
so didn't go through

buildx/bake/entitlements.go

Lines 595 to 603 in abf6ab4

// If the component doesn't exist, return the last valid path
if os.IsNotExist(err) {
for r := len(dest) - 1; r >= volLen; r-- {
if os.IsPathSeparator(dest[r]) {
return dest[:r], in[start:], nil
}
}
return vol, in[start:], nil
}
anymore.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hum actually this is not correct, this test should have failed before. I'm taking another look.

Copy link
Member Author

@crazy-max crazy-max Oct 23, 2025

Choose a reason for hiding this comment

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

Oh I think I get it now, it seems it does not collect overrides for checking entitlements:

target "default" {}
$ docker buildx bake --set *.output=type=docker,dest=../dist/result.tar
#0 building with "default" instance using docker driver

#1 [internal] load local bake definitions
#1 reading docker-bake.hcl 21B / 21B done
#1 DONE 0.0s

#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 2B done
#2 DONE 0.1s

Which makes sense as override is a user input but don't think we support this atm. cc @tonistiigi

So before my change the tar file was already created before the entitlement check and didn't request privilege which is wrong.

Now with my change we create the tar file lazily and therefore entitlement checks the parent dir and requests privilege which is good but if I create the file before:

$ touch ../dist/result.tar
$ docker buildx bake --set *.output=type=docker,dest=../dist/result.tar
#0 building with "default" instance using docker driver

#1 [internal] load local bake definitions
#1 reading docker-bake.hcl 21B / 21B done
#1 DONE 0.0s

#2 [internal] load build definition from Dockerfile
#2 DONE 0.0s

Then it builds and does not request privilege which is wrong. So we need to fix this.

}

cmd := buildxCmd(
sb,
withDir(dir),
withArgs("bake", "--metadata-file", filepath.Join(dirDest, "md.json"), "--set", outFlag),
withEnv("BUILDX_METADATA_PROVENANCE="+metadataMode),
withEnv(envs...),
)
out, err := cmd.CombinedOutput()
require.NoError(t, err, string(out))
Expand Down
5 changes: 3 additions & 2 deletions tests/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,10 +417,11 @@ func testBuildLocalExport(t *testing.T, sb integration.Sandbox) {

func testBuildTarExport(t *testing.T, sb integration.Sandbox) {
dir := createTestProject(t)
out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=tar,dest=%s/result.tar", dir), dir))
outdir := path.Join(dir, "out")
out, err := buildCmd(sb, withArgs(fmt.Sprintf("--output=type=tar,dest=%s/result.tar", outdir), dir))
require.NoError(t, err, string(out))

dt, err := os.ReadFile(fmt.Sprintf("%s/result.tar", dir))
dt, err := os.ReadFile(fmt.Sprintf("%s/result.tar", outdir))
require.NoError(t, err)
m, err := testutil.ReadTarToMap(dt, false)
require.NoError(t, err)
Expand Down