CVE-2026-33997
Description
Moby is an open source container framework. Prior to version 29.3.1, a security vulnerability has been detected that allows plugins privilege validation to be bypassed during docker plugin install. Due to an error in the daemon's privilege comparison logic, the daemon may incorrectly accept a privilege set that differs from the one approved by the user. Plugins that request exactly one privilege are also affected, because no comparison is performed at all. This issue has been patched in version 29.3.1.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/docker/dockerGo | >= 0 | — |
github.com/moby/moby/v2Go | < 2.0.0-beta.8 | 2.0.0-beta.8 |
github.com/moby/mobyGo | >= 0 | — |
Affected products
1Patches
1f4d6f25bf0c3plugin: Fix off-by-one in privilege validation
2 files changed · +69 −25
daemon/pkg/plugin/manager.go+29 −21 modified@@ -1,13 +1,13 @@ package plugin import ( + "cmp" "context" "encoding/json" "io" "os" "path/filepath" - "reflect" - "sort" + "slices" "strings" "sync" "syscall" @@ -337,34 +337,42 @@ func makeLoggerStreams(id string) (stdout, stderr io.WriteCloser) { } func validatePrivileges(requiredPrivileges, privileges plugin.Privileges) error { - if !isEqual(requiredPrivileges, privileges, isEqualPrivilege) { + if len(requiredPrivileges) != len(privileges) { return errors.New("incorrect privileges") } - return nil -} - -func isEqual(arrOne, arrOther plugin.Privileges, compare func(x, y plugin.Privilege) 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 plugin.Privilege) 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 plugin.Privileges) plugin.Privileges { + normalized := make(plugin.Privileges, len(privileges)) + for i, privilege := range privileges { + normalized[i] = plugin.Privilege{ + 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 plugin.Privilege) int { + return cmp.Compare(a.Name, b.Name) + }) + + return normalized }
daemon/pkg/plugin/manager_test.go+40 −4 modified@@ -44,12 +44,48 @@ func TestValidatePrivileges(t *testing.T) { }, result: true, }, + "single-element-same": { + requiredPrivileges: []plugin.Privilege{ + {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, + }, + privileges: []plugin.Privilege{ + {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, + }, + result: true, + }, + "single-element-diff-value": { + requiredPrivileges: []plugin.Privilege{ + {Name: "allow-all-devices", Description: "Description", Value: []string{"false"}}, + }, + privileges: []plugin.Privilege{ + {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, + }, + result: false, + }, + "first-sorted-element-diff-value": { + requiredPrivileges: []plugin.Privilege{ + {Name: "allow-all-devices", Description: "Description", Value: []string{"false"}}, + {Name: "network", Description: "Description", Value: []string{"host"}}, + }, + privileges: []plugin.Privilege{ + {Name: "allow-all-devices", Description: "Description", Value: []string{"true"}}, + {Name: "network", Description: "Description", Value: []string{"host"}}, + }, + result: false, + }, + "empty-privileges": { + requiredPrivileges: []plugin.Privilege{}, + privileges: []plugin.Privilege{}, + 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)) + } + }) } }
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-pxq6-2prw-chj9ghsaADVISORY
- github.com/moby/moby/security/advisories/GHSA-pxq6-2prw-chj9nvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-33997ghsaADVISORY
- docs.docker.com/engine/extend/legacy_pluginsghsaWEB
- github.com/moby/moby/commit/f4d6f25bf0c3fa12d4968320a45685947756a22aghsaWEB
- github.com/moby/moby/releases/tag/docker-v29.3.1nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.