VYPR
Medium severity6.8NVD Advisory· Published Mar 31, 2026· Updated Apr 3, 2026

CVE-2026-33997

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.

PackageAffected versionsPatched versions
github.com/docker/dockerGo
>= 0
github.com/moby/moby/v2Go
< 2.0.0-beta.82.0.0-beta.8
github.com/moby/mobyGo
>= 0

Affected products

1

Patches

1
f4d6f25bf0c3

plugin: Fix off-by-one in privilege validation

https://github.com/moby/mobyPaweł GronowskiMar 19, 2026via ghsa
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

News mentions

0

No linked articles in our index yet.