diff --git a/0001-backport-and-adapt-upstream-patches-to-fix-the-CVE-2.patch b/0001-backport-and-adapt-upstream-patches-to-fix-the-CVE-2.patch new file mode 100644 index 0000000000000000000000000000000000000000..dac40bfea86eedc01f49937a34fe4848a3f87cbc --- /dev/null +++ b/0001-backport-and-adapt-upstream-patches-to-fix-the-CVE-2.patch @@ -0,0 +1,297 @@ +From e747a006136530be6b67925f1b53fbf672fc16cf Mon Sep 17 00:00:00 2001 +From: clarehkli +Date: Sun, 23 Nov 2025 11:11:50 +0800 +Subject: [PATCH] backport and adapt upstream patches to fix the CVE-2025-62725 + +the adapted patches and the reasons for adaptation are as follows: +840288895e673fcccd56a7830dee30d8a75523ef the prerequisite patch for fixing the CVE +69bcb962bfb2ea53b41aa925333d356b577d6176 fix the CVE issue +6a90742ef29c392b6626b0ff3f50c6f28f944321 fix test errors in ocii_test.go + +Original-URL: https://github.com/docker/compose/commit/840288895e673fcccd56a7830dee30d8a75523ef.patch +Original-URL: https://github.com/docker/compose/commit/69bcb962bfb2ea53b41aa925333d356b577d6176.patch +Original-URL: https://github.com/docker/compose/commit/6a90742ef29c392b6626b0ff3f50c6f28f944321.patch + +--- + internal/ocipush/push.go | 2 + + pkg/remote/oci.go | 83 ++++++++++++++++++++--- + pkg/remote/oci_test.go | 139 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 214 insertions(+), 10 deletions(-) + create mode 100644 pkg/remote/oci_test.go + +diff --git a/internal/ocipush/push.go b/internal/ocipush/push.go +index 0296f67..48e8ad1 100644 +--- a/internal/ocipush/push.go ++++ b/internal/ocipush/push.go +@@ -54,6 +54,8 @@ const ( + // > an artifactType field, and tooling to work with artifacts should + // > fallback to the config.mediaType value. + ComposeEmptyConfigMediaType = "application/vnd.docker.compose.config.empty.v1+json" ++ // ComposeEnvFileMediaType is the media type for each Env File layer in the image manifest. ++ ComposeEnvFileMediaType = "application/vnd.docker.compose.envfile" + ) + + // clientAuthStatusCodes are client (4xx) errors that are authentication +diff --git a/pkg/remote/oci.go b/pkg/remote/oci.go +index a9d0e09..0c98ad3 100644 +--- a/pkg/remote/oci.go ++++ b/pkg/remote/oci.go +@@ -63,6 +63,32 @@ type ociRemoteLoader struct { + + const prefix = "oci://" + ++// validatePathInBase ensures a file path is contained within the base directory, ++// as OCI artifacts resources must all live within the same folder. ++func validatePathInBase(base, unsafePath string) error { ++ // Reject paths with path separators regardless of OS ++ if strings.ContainsAny(unsafePath, "\\/") { ++ return fmt.Errorf("invalid OCI artifact") ++ } ++ ++ // Join the base with the untrusted path ++ targetPath := filepath.Join(base, unsafePath) ++ ++ // Get the directory of the target path ++ targetDir := filepath.Dir(targetPath) ++ ++ // Clean both paths to resolve any .. or . components ++ cleanBase := filepath.Clean(base) ++ cleanTargetDir := filepath.Clean(targetDir) ++ ++ // Check if the target directory is the same as base directory ++ if cleanTargetDir != cleanBase { ++ return fmt.Errorf("invalid OCI artifact") ++ } ++ ++ return nil ++} ++ + func (g ociRemoteLoader) Accept(path string) bool { + return strings.HasPrefix(path, prefix) + } +@@ -135,11 +161,6 @@ func (g ociRemoteLoader) pullComposeFiles(ctx context.Context, local string, com + return err + } + +- f, err := os.Create(composeFile) +- if err != nil { +- return err +- } +- defer f.Close() //nolint:errcheck + if (manifest.ArtifactType != "" && manifest.ArtifactType != ocipush.ComposeProjectArtifactType) || + (manifest.ArtifactType == "" && manifest.Config.MediaType != ocipush.ComposeEmptyConfigMediaType) { + return fmt.Errorf("%s is not a compose project OCI artifact, but %s", ref.String(), manifest.ArtifactType) +@@ -154,18 +175,60 @@ func (g ociRemoteLoader) pullComposeFiles(ctx context.Context, local string, com + if err != nil { + return err + } +- if i > 0 { +- _, err = f.Write([]byte("\n---\n")) +- if err != nil { ++ ++ switch layer.MediaType { ++ case ocipush.ComposeYAMLMediaType: ++ if err := writeComposeFile(layer, i, local, content); err != nil { + return err + } ++ case ocipush.ComposeEnvFileMediaType: ++ if err := writeEnvFile(layer, local, content); err != nil { ++ return err ++ } ++ case ocipush.ComposeEmptyConfigMediaType: + } +- _, err = f.Write(content) ++ } ++ return nil ++} ++ ++func writeComposeFile(layer v1.Descriptor, i int, local string, content []byte) error { ++ file := "compose.yaml" ++ if _, ok := layer.Annotations["com.docker.compose.extends"]; ok { ++ file = layer.Annotations["com.docker.compose.file"] ++ if err := validatePathInBase(local, file); err != nil { ++ return err ++ } ++ } ++ f, err := os.Create(filepath.Join(local, file)) ++ if err != nil { ++ return err ++ } ++ defer func() { _ = f.Close() }() ++ if _, ok := layer.Annotations["com.docker.compose.file"]; i > 0 && ok { ++ _, err := f.Write([]byte("\n---\n")) + if err != nil { + return err + } + } +- return nil ++ _, err = f.Write(content) ++ return err ++} ++ ++func writeEnvFile(layer v1.Descriptor, local string, content []byte) error { ++ envfilePath, ok := layer.Annotations["com.docker.compose.envfile"] ++ if !ok { ++ return fmt.Errorf("missing annotation com.docker.compose.envfile in layer %q", layer.Digest) ++ } ++ if err := validatePathInBase(local, envfilePath); err != nil { ++ return err ++ } ++ otherFile, err := os.Create(filepath.Join(local, envfilePath)) ++ if err != nil { ++ return err ++ } ++ defer func() { _ = otherFile.Close() }() ++ _, err = otherFile.Write(content) ++ return err + } + + var _ loader.ResourceLoader = ociRemoteLoader{} +diff --git a/pkg/remote/oci_test.go b/pkg/remote/oci_test.go +new file mode 100644 +index 0000000..28a4dbd +--- /dev/null ++++ b/pkg/remote/oci_test.go +@@ -0,0 +1,139 @@ ++/* ++ Copyright 2020 Docker Compose CLI authors ++ ++ Licensed under the Apache License, Version 2.0 (the "License"); ++ you may not use this file except in compliance with the License. ++ You may obtain a copy of the License at ++ ++ http://www.apache.org/licenses/LICENSE-2.0 ++ ++ Unless required by applicable law or agreed to in writing, software ++ distributed under the License is distributed on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ See the License for the specific language governing permissions and ++ limitations under the License. ++*/ ++ ++package remote ++ ++import ( ++ "path/filepath" ++ "testing" ++ ++ spec "github.com/opencontainers/image-spec/specs-go/v1" ++ "gotest.tools/v3/assert" ++) ++ ++func TestValidatePathInBase(t *testing.T) { ++ base := "/tmp/cache/compose" ++ ++ tests := []struct { ++ name string ++ unsafePath string ++ wantErr bool ++ }{ ++ { ++ name: "valid simple filename", ++ unsafePath: "compose.yaml", ++ wantErr: false, ++ }, ++ { ++ name: "valid hashed filename", ++ unsafePath: "f8f9ede3d201ec37d5a5e3a77bbadab79af26035e53135e19571f50d541d390c.yaml", ++ wantErr: false, ++ }, ++ { ++ name: "valid env file", ++ unsafePath: ".env", ++ wantErr: false, ++ }, ++ { ++ name: "valid env file with suffix", ++ unsafePath: ".env.prod", ++ wantErr: false, ++ }, ++ { ++ name: "unix path traversal", ++ unsafePath: "../../../etc/passwd", ++ wantErr: true, ++ }, ++ { ++ name: "windows path traversal", ++ unsafePath: "..\\..\\..\\windows\\system32\\config\\sam", ++ wantErr: true, ++ }, ++ { ++ name: "subdirectory unix", ++ unsafePath: "config/base.yaml", ++ wantErr: true, ++ }, ++ { ++ name: "subdirectory windows", ++ unsafePath: "config\\base.yaml", ++ wantErr: true, ++ }, ++ { ++ name: "absolute unix path", ++ unsafePath: "/etc/passwd", ++ wantErr: true, ++ }, ++ { ++ name: "absolute windows path", ++ unsafePath: "C:\\windows\\system32\\config\\sam", ++ wantErr: true, ++ }, ++ { ++ name: "parent reference only", ++ unsafePath: "..", ++ wantErr: true, ++ }, ++ { ++ name: "mixed separators", ++ unsafePath: "config/sub\\file.yaml", ++ wantErr: true, ++ }, ++ { ++ name: "filename with spaces", ++ unsafePath: "my file.yaml", ++ wantErr: false, ++ }, ++ { ++ name: "filename with special chars", ++ unsafePath: "file-name_v1.2.3.yaml", ++ wantErr: false, ++ }, ++ } ++ ++ for _, tt := range tests { ++ t.Run(tt.name, func(t *testing.T) { ++ err := validatePathInBase(base, tt.unsafePath) ++ if (err != nil) != tt.wantErr { ++ targetPath := filepath.Join(base, tt.unsafePath) ++ targetDir := filepath.Dir(targetPath) ++ t.Errorf("validatePathInBase(%q, %q) error = %v, wantErr %v\ntargetDir=%q base=%q", ++ base, tt.unsafePath, err, tt.wantErr, targetDir, base) ++ } ++ }) ++ } ++} ++ ++func TestWriteComposeFileWithExtendsPathTraversal(t *testing.T) { ++ tmpDir := t.TempDir() ++ ++ // Create a layer with com.docker.compose.extends=true and a path traversal attempt ++ layer := spec.Descriptor{ ++ MediaType: "application/vnd.docker.compose.file.v1+yaml", ++ Digest: "sha256:test123", ++ Size: 100, ++ Annotations: map[string]string{ ++ "com.docker.compose.extends": "true", ++ "com.docker.compose.file": "../other", ++ }, ++ } ++ ++ content := []byte("services:\n test:\n image: nginx\n") ++ ++ // writeComposeFile should return an error due to path traversal ++ err := writeComposeFile(layer, 0, tmpDir, content) ++ assert.Error(t, err, "invalid OCI artifact") ++} +-- +2.43.7 + diff --git a/docker-compose.spec b/docker-compose.spec index bd068fe75e20b524d42eae2ddbb7c0a9bae48dc5..940c75bf1388d7ec523dbe2509df301088d959d8 100644 --- a/docker-compose.spec +++ b/docker-compose.spec @@ -4,13 +4,15 @@ Summary: Define and run multi-container applications with Docker Name: docker-compose Version: 2.30.3 -Release: 3%{?dist} +Release: 4%{?dist} License: Apache-2.0 URL: https://github.com/docker/compose Source0: %{url}/archive/refs/tags/v2.30.3.tar.gz #/%{name}-%{version}.tar.gz # execute 'go mod vendor' under the source code path can generator vendor package Source1: %{name}-vendor.tar.gz +Patch0001: 0001-backport-and-adapt-upstream-patches-to-fix-the-CVE-2.patch + BuildRequires: golang go-rpm-macros Requires: docker docker-compose-switch @@ -51,6 +53,10 @@ install -D -m 0755 bin/%{name} "%{buildroot}/usr/lib/docker/cli-plugins/%{name}" /usr/lib/docker/cli-plugins/%{name} %changelog +* Sun Nov 23 2025 clarehkli - 2.30.3-4 +- [Type] security +- [DESC] backport and adapt upstream patches to fix the CVE-2025-62725 + * Wed Sep 24 2025 Huang Yang - 2.30.3-3 - [Type] bugfix - [DESC] enable cgo and modify GO_LDFLAGS to align with go-rpm-config