[meta-virtualization][scarthgap][PATCH 1/2] docker-moby: Fix CVE-2026-33997
"Deepak Rathore -X (deeratho - E INFOCHIPS PRIVATE LIMITED at Cisco)" <[email protected]> Wed, 13 May 2026 08:09:12 -0700
| Newsgroups | org.yoctoproject.lists.meta-virtualization |
|---|---|
| Message-ID | <[email protected]> |
From: Deepak Rathore <[email protected]> Pick the version-specific cherry-pick [1] of the upstream fix [2] mentioned in [3]. [1] https://github.com/moby/moby/commit/d5986c0430124d82ce87b439638d8ebb16916e40 [2] https://github.com/moby/moby/commit/99a095ecf04e8849318f2811bb3f687905eab09b [3] https://security-tracker.debian.org/tracker/CVE-2026-33997 Signed-off-by: Deepak Rathore <[email protected]> diff --git a/recipes-containers/docker/docker-moby_git.bb b/recipes-containers/docker/docker-moby_git.bb index e66416db..26dce276 100644 --- a/recipes-containers/docker/docker-moby_git.bb +++ b/recipes-containers/docker/docker-moby_git.bb @@ -56,6 +56,7 @@ SRC_URI = "\ file://0001-libnetwork-use-GO-instead-of-go.patch \ file://0001-cli-use-external-GO111MODULE-and-cross-compiler.patch \ file://0001-dynbinary-use-go-cross-compiler.patch;patchdir=src/import \ + file://CVE-2026-33997.patch;patchdir=src/import \ " DOCKER_COMMIT = "${SRCREV_moby}" diff --git a/recipes-containers/docker/files/CVE-2026-33997.patch b/recipes-containers/docker/files/CVE-2026-33997.patch new file mode 100644 index 00000000..9da4b3cf --- /dev/null +++ b/recipes-containers/docker/files/CVE-2026-33997.patch @@ -0,0 +1,179 @@ +From 7e35ad066ee0c89e15fca9e9b95933b79ba315b7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= <[email protected]> +Date: Thu, 19 Mar 2026 19:18:23 +0100 +Subject: [PATCH] plugin: Fix off-by-one in privilege validation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fix an off-by-one error in isEqual() where the comparison loop started +at index 1 instead of 0, causing the first privilege (after sorting +alphabetically by name) to never be validated. + +This allowed a malicious plugin to request different values for +whichever privilege sorts first — most notably "allow-all-devices", +which grants unrestricted rwm access to all host devices. + +The bug also meant that plugins requesting exactly one privilege had +zero iterations of the comparison loop, bypassing validation entirely. + +Also fix an existing test case ("diff-order-but-same-value") that only +passed due to the off-by-one bug, and add test cases for single-element +and first-sorted-element mismatches. + +CVE: CVE-2026-33997 +Upstream-Status: Backport [https://github.com/moby/moby/commit/d5986c0430124d82ce87b439638d8ebb16916e40] + +Signed-off-by: Paweł Gronowski <[email protected]> +(cherry picked from commit 99a095ecf04e8849318f2811bb3f687905eab09b) +Signed-off-by: Paweł Gronowski <[email protected]> +(cherry picked from commit d5986c0430124d82ce87b439638d8ebb16916e40) +Signed-off-by: Deepak Rathore <[email protected]> +--- + plugin/manager.go | 50 ++++++++++++++++++++++++------------------ + plugin/manager_test.go | 44 +++++++++++++++++++++++++++++++++---- + 2 files changed, 69 insertions(+), 25 deletions(-) + +diff --git a/plugin/manager.go b/plugin/manager.go +index 81f3d67b80..d98b968c7d 100644 +--- a/plugin/manager.go ++++ b/plugin/manager.go +@@ -1,14 +1,14 @@ + package plugin // import "github.com/docker/docker/plugin" + + import ( ++ "cmp" + "context" + "encoding/json" + "io" + "os" + "path/filepath" +- "reflect" + "regexp" +- "sort" ++ "slices" + "strings" + "sync" + "syscall" +@@ -331,34 +331,42 @@ func makeLoggerStreams(id string) (stdout, stderr io.WriteCloser) { + } + + func validatePrivileges(requiredPrivileges, privileges types.PluginPrivileges) error { +- if !isEqual(requiredPrivileges, privileges, isEqualPrivilege) { ++ if len(requiredPrivileges) != len(privileges) { + return errors.New("incorrect privileges") + } + +- return nil +-} +- +-func isEqual(arrOne, arrOther types.PluginPrivileges, compare func(x, y types.PluginPrivilege) bool) bool { +- if len(arrOne) != len(arrOther) { +- return false +- } +- +- sort.Sort(arrOne) +- sort.Sort(arrOther) ++ a := normalizePrivileges(requiredPrivileges) ++ b := normalizePrivileges(privileges) + +- for i := 1; i < arrOne.Len(); i++ { +- if !compare(arrOne[i], arrOther[i]) { +- return false ++ for i := range a { ++ if a[i].Name != b[i].Name { ++ return errors.New("incorrect privileges") ++ } ++ if !slices.Equal(a[i].Value, b[i].Value) { ++ return errors.New("incorrect privileges") + } + } + +- return true ++ return nil + } + +-func isEqualPrivilege(a, b types.PluginPrivilege) bool { +- if a.Name != b.Name { +- return false ++// normalizePrivileges returns a normalized copy of privileges with privilege names ++// and each privilege's values sorted for order-insensitive comparison. ++// The input is not mutated. ++func normalizePrivileges(privileges types.PluginPrivileges) types.PluginPrivileges { ++ normalized := make(types.PluginPrivileges, len(privileges)) ++ for i, privilege := range privileges { ++ normalized[i] = types.PluginPrivilege{ ++ Name: privilege.Name, ++ Description: privilege.Description, ++ Value: slices.Clone(privilege.Value), ++ } ++ slices.Sort(normalized[i].Value) + } + +- return reflect.DeepEqual(a.Value, b.Value) ++ slices.SortFunc(normalized, func(a, b types.PluginPrivilege) int { ++ return cmp.Compare(a.Name, b.Name) ++ }) ++ ++ return normalized + } +diff --git a/plugin/manager_test.go b/plugin/manager_test.go +index 62ccf2149d..6d58865044 100644 +--- a/plugin/manager_test.go ++++ b/plugin/manager_test.go +@@ -44,12 +44,48 @@ func TestValidatePrivileges(t *testing.T) { + }, + result: true, + }, ++ "single-element-same": { ++ requiredPrivileges: []types.PluginPrivilege{ ++ {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, ++ }, ++ privileges: []types.PluginPrivilege{ ++ {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, ++ }, ++ result: true, ++ }, ++ "single-element-diff-value": { ++ requiredPrivileges: []types.PluginPrivilege{ ++ {Name: "allow-all-devices", Description: "Description", Value: []string{"false"}}, ++ }, ++ privileges: []types.PluginPrivilege{ ++ {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, ++ }, ++ result: false, ++ }, ++ "first-sorted-element-diff-value": { ++ requiredPrivileges: []types.PluginPrivilege{ ++ {Name: "allow-all-devices", Description: "Description", Value: []string{"false"}}, ++ {Name: "network", Description: "Description", Value: []string{"host"}}, ++ }, ++ privileges: []types.PluginPrivilege{ ++ {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, ++ {Name: "network", Description: "Description", Value: []string{"host"}}, ++ }, ++ result: false, ++ }, ++ "empty-privileges": { ++ requiredPrivileges: []types.PluginPrivilege{}, ++ privileges: []types.PluginPrivilege{}, ++ result: true, ++ }, + } + + for key, data := range testData { +- err := validatePrivileges(data.requiredPrivileges, data.privileges) +- if (err == nil) != data.result { +- t.Fatalf("Test item %s expected result to be %t, got %t", key, data.result, (err == nil)) +- } ++ t.Run(key, func(t *testing.T) { ++ err := validatePrivileges(data.requiredPrivileges, data.privileges) ++ if (err == nil) != data.result { ++ t.Fatalf("expected result to be %t, got %t", data.result, (err == nil)) ++ } ++ }) + } + } +-- +2.44.4 + -- 2.35.6