Repository: moby/buildkit

Issue:
[dockerfile/1.15.0] BOM attestation: no scan targets for linux/arm64/v8
### Contributing guidelines and issue reporting guide

- [x] I've read the [contributing guidelines](https://github.com/moby/buildkit/blob/master/.github/CONTRIBUTING.md) and wholeheartedly agree. I've also read the [issue reporting guide](https://github.com/moby/buildkit/blob/master/.github/issue_reporting_guide.md).

### Well-formed report checklist

- [x] I have found a bug that the documentation does not mention anything about my problem
- [x] I have found a bug that there are no open or closed issues that are related to my problem
- [x] I have provided version/information about my environment and done my best to provide a reproducer

### Description of bug

## Bug description

Image builds that include the CLI option `--attest type=sbom` fail on linux/arm64 platforms as soon as I update the syntax directive in my Dockerfiles to `# syntax=docker/dockerfile:1.15.0` (or `# syntax=docker.io/docker/dockerfile-upstream:1.15.0` as mentioned in the [release notes](https://github.com/moby/buildkit/releases/tag/dockerfile%2F1.15.0)). The error message is:
> ERROR: failed to solve: no scan targets for linux/arm64/v8

## Reproduction

The issue is reproducible with a Dockerfile as simple as the following:

~~~Dockerfile
# syntax=docker.io/docker/dockerfile-upstream:1.15.0

FROM ubuntu:24.04
~~~

With this Dockerfile, the build on a MacBook Pro (M2 Max CPU) produces:
~~~shell
$ docker buildx build --attest type=sbom --no-cache --progress plain .
#0 building with "desktop-linux" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 109B done
#1 DONE 0.0s

#2 resolve image config for docker-image://docker.io/docker/dockerfile-upstream:1.15.0
#2 DONE 0.1s

#3 docker-image://docker.io/docker/dockerfile-upstream:1.15.0@sha256:05e0ad437efefcf144bfbf9d7f728c17818408e6d01432d9e264ef958bbd52f3
#3 resolve docker.io/docker/dockerfile-upstream:1.15.0@sha256:05e0ad437efefcf144bfbf9d7f728c17818408e6d01432d9e264ef958bbd52f3 done
#3 DONE 0.0s

#3 docker-image://docker.io/docker/dockerfile-upstream:1.15.0@sha256:05e0ad437efefcf144bfbf9d7f728c17818408e6d01432d9e264ef958bbd52f3
#3 CACHED

#4 resolve image config for docker-image://docker.io/docker/buildkit-syft-scanner:stable-1
#4 DONE 0.0s

#5 [internal] load metadata for docker.io/library/ubuntu:24.04
#5 DONE 0.0s

#6 [internal] load .dockerignore
#6 transferring context: 2B done
#6 DONE 0.0s
Dockerfile:1
--------------------
   1 | >>> # syntax=docker.io/docker/dockerfile-upstream:1.15.0
   2 |     
   3 |     FROM ubuntu:24.04
--------------------
ERROR: failed to solve: no scan targets for linux/arm64/v8

View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/0zvhngjfngbchgqh6t8wlquvc
~~~

I observed the same error on a "native" linux/arm64 build runner on AWS.

If i drop the option `--attest type=sbom `, the build succeeds. Similarly, everything is fine with `# syntax=docker/dockerfile:1.14.0`

## Version information

On the MacBook Pro:

```bash
 $ docker buildx version
github.com/docker/buildx v0.22.0-desktop.1 b45d7a53aa26a5e76aae051acdb15775c0e178da
$ docker version       
Client:
 Version:           28.0.4
 API version:       1.48
 Go version:        go1.23.7
 Git commit:        b8034c0
 Built:             Tue Mar 25 15:06:09 2025
 OS/Arch:           darwin/arm64
 Context:           desktop-linux

Server: Docker Desktop 4.40.0 (187762)
 Engine:
  Version:          28.0.4
  API version:      1.48 (minimum version 1.24)
  Go version:       go1.23.7
  Git commit:       6430e49
  Built:            Tue Mar 25 15:07:18 2025
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.7.26
  GitCommit:        753481ec61c7c8955a23d6ff7bc8e4daed455734
 runc:
  Version:          1.2.5
  GitCommit:        v1.2.5-0-g59923ef
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

On the GitLab build runner:
```bash
$ docker buildx version
github.com/docker/buildx v0.22.0 18ccba0
$ docker version
Client: Docker Engine - Community
 Version:           27.5.0
 API version:       1.47
 Go version:        go1.[22](https://gitlab.haufedev.systems/LudwigC/tag-pipeline/-/jobs/17834135#L22).10
 Git commit:        a187fa5
 Built:             Mon Jan 13 15:25:12 2025
 OS/Arch:           linux/arm64
 Context:           default
Server: Docker Engine - Community
 Engine:
  Version:          28.0.4
  API version:      1.48 (minimum version 1.24)
  Go version:       go1.[23](https://gitlab.haufedev.systems/LudwigC/tag-pipeline/-/jobs/17834135#L23).7
  Git commit:       6430e49
  Built:            Tue Mar [25](https://gitlab.haufedev.systems/LudwigC/tag-pipeline/-/jobs/17834135#L25) 15:07:18 2025
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          v1.7.[27](https://gitlab.haufedev.systems/LudwigC/tag-pipeline/-/jobs/17834135#L27)
  GitCommit:        05044ec0a9a75232cad458027ca83437aae3f4da
 runc:
  Version:          1.2.6
  GitCommit:        v1.2.6-0-ge89a[29](https://gitlab.haufedev.systems/LudwigC/tag-pipeline/-/jobs/17834135#L29)9
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```

Discussion:
@AkihiroSuda: Regression in 25bba3166c754ed614a2750f344507888847afc1
- https://github.com/moby/buildkit/pull/5837@AkihiroSuda: PR
- https://github.com/moby/buildkit/pull/5941

Solution patch:
diff --git a/frontend/dockerui/build.go b/frontend/dockerui/build.go
index fe2dbd05d..6ced71d67 100644
--- a/frontend/dockerui/build.go
+++ b/frontend/dockerui/build.go
@@ -61,16 +61,12 @@ func (bc *Client) Build(ctx context.Context, fn BuildFunc) (*ResultBuilder, erro
 			} else {
 				p = platforms.DefaultSpec()
 			}
-
-			k := platforms.FormatAll(p)
-			p = extendWindowsPlatform(p, img.Platform)
-			p = platforms.Normalize(p)
-
+			expPlat := makeExportPlatform(p, img.Platform)
 			if bc.MultiPlatformRequested {
-				res.AddRef(k, ref)
-				res.AddMeta(fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, k), config)
+				res.AddRef(expPlat.ID, ref)
+				res.AddMeta(fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, expPlat.ID), config)
 				if len(baseConfig) > 0 {
-					res.AddMeta(fmt.Sprintf("%s/%s", exptypes.ExporterImageBaseConfigKey, k), baseConfig)
+					res.AddMeta(fmt.Sprintf("%s/%s", exptypes.ExporterImageBaseConfigKey, expPlat.ID), baseConfig)
 				}
 			} else {
 				res.SetRef(ref)
@@ -79,10 +75,7 @@ func (bc *Client) Build(ctx context.Context, fn BuildFunc) (*ResultBuilder, erro
 					res.AddMeta(exptypes.ExporterImageBaseConfigKey, baseConfig)
 				}
 			}
-			expPlatforms.Platforms[i] = exptypes.Platform{
-				ID:       k,
-				Platform: p,
-			}
+			expPlatforms.Platforms[i] = expPlat
 			return nil
 		})
 	}
@@ -133,3 +126,16 @@ func extendWindowsPlatform(p, imgP ocispecs.Platform) ocispecs.Platform {
 	}
 	return p
 }
+
+func makeExportPlatform(p, imgP ocispecs.Platform) exptypes.Platform {
+	p = platforms.Normalize(p)
+	exp := exptypes.Platform{
+		ID: platforms.FormatAll(p),
+	}
+	if p.OS == "windows" {
+		p = extendWindowsPlatform(p, imgP)
+		p = platforms.Normalize(p)
+	}
+	exp.Platform = p
+	return exp
+}



Test patch:
diff --git a/frontend/dockerui/build_test.go b/frontend/dockerui/build_test.go
new file mode 100644
index 000000000..378f209b2
--- /dev/null
+++ b/frontend/dockerui/build_test.go
@@ -0,0 +1,100 @@
+package dockerui
+
+import (
+	"testing"
+
+	"github.com/containerd/platforms"
+	"github.com/moby/buildkit/exporter/containerimage/exptypes"
+	ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/stretchr/testify/require"
+)
+
+func TestNormalizePlatform(t *testing.T) {
+	testCases := []struct {
+		p, imgP  ocispecs.Platform
+		expected exptypes.Platform
+	}{
+		{
+			p: ocispecs.Platform{
+				Architecture: "arm64",
+				OS:           "linux",
+				Variant:      "v8",
+			},
+			imgP: ocispecs.Platform{
+				Architecture: "arm64",
+				OS:           "linux",
+			},
+			expected: exptypes.Platform{
+				ID: "linux/arm64", // Not "linux/arm64/v8" https://github.com/moby/buildkit/issues/5915
+				Platform: ocispecs.Platform{
+					Architecture: "arm64",
+					OS:           "linux",
+				},
+			},
+		},
+		{
+			p: ocispecs.Platform{
+				Architecture: "arm64",
+				OS:           "linux",
+				Variant:      "v8",
+			},
+			imgP: ocispecs.Platform{
+				Architecture: "arm64",
+				OS:           "linux",
+				Variant:      "v8",
+			},
+			expected: exptypes.Platform{
+				ID: "linux/arm64",
+				Platform: ocispecs.Platform{
+					Architecture: "arm64",
+					OS:           "linux",
+				},
+			},
+		},
+		{
+			p: ocispecs.Platform{
+				Architecture: "amd64",
+				OS:           "windows",
+			},
+			imgP: ocispecs.Platform{
+				Architecture: "amd64",
+				OS:           "windows",
+				OSVersion:    "10.0.19041.0",
+			},
+			expected: exptypes.Platform{
+				ID: "windows/amd64",
+				Platform: ocispecs.Platform{
+					Architecture: "amd64",
+					OS:           "windows",
+					OSVersion:    "10.0.19041.0",
+				},
+			},
+		},
+		{
+			p: ocispecs.Platform{
+				Architecture: "amd64",
+				OS:           "windows",
+				OSVersion:    "10.0.19041.0",
+			},
+			imgP: ocispecs.Platform{
+				Architecture: "amd64",
+				OS:           "windows",
+				OSVersion:    "11.0.22000.0",
+			},
+			expected: exptypes.Platform{
+				ID: "windows(10.0.19041.0)/amd64",
+				Platform: ocispecs.Platform{
+					Architecture: "amd64",
+					OS:           "windows",
+					OSVersion:    "10.0.19041.0",
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		require.Equal(t, tc.expected, makeExportPlatform(tc.p, tc.imgP))
+		// the ID needs to always be formatall(normalize(p))
+		require.Equal(t, platforms.FormatAll(platforms.Normalize(tc.p)), tc.expected.ID)
+	}
+}