VYPR
High severity7.3GHSA Advisory· Published May 21, 2026

containerd user ID handling bypass allows runAsNonRoot evasion

CVE-2026-46680

Description

Impact

A bug was found in containerd where containers launched with a numeric User directive that cannot be parsed as a 32-bit integer are incorrectly treated as a username. If a crafted image provides an /etc/passwd file mapping this large numeric string to root, the container ultimately runs as root (UID 0). This allows the Kubernetes runAsNonRoot restriction to be bypassed, causing unexpected behavior for environments that require containers to run as a non-root user.

Patches

This bug has been fixed in the following containerd versions:

  • 2.3.1
  • 2.2.4
  • 2.0.9
  • 1.7.32

Note: The containerd 2.1 release has reached its end of life and a fixed version is not provided.

Users should update to these versions to resolve the issue.

Workarounds

Ensure that only trusted images are used and that only trusted users have permissions to import images. Alternatively, enforcing a specific numeric runAsUser in the Kubernetes Pod securityContext overrides the USER directive in the image and prevents the bypass. Newer versions of Kubernetes, starting with 1.34, also appear to enforce runAsNonRoot properly regardless of this bug.

Credits

The containerd project would like to thank Lei Wang (@ssst0n3) for responsibly disclosing this issue in accordance with the containerd security policy.

### Resources * https://github.com/advisories/GHSA-265r-hfxg-fhmg (CVE-2024-40635)

For more information

If there are any questions or comments about this advisory:

To report a security issue in containerd: * Report a new vulnerability * Send an email to security@containerd.io

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A flaw in containerd allows numeric User directives exceeding 32-bit to be treated as usernames, enabling runAsNonRoot bypass via crafted /etc/passwd.

Containerd incorrectly handles numeric User directives in container images that cannot be parsed as a 32-bit integer, treating them as usernames. This allows a crafted image with an /etc/passwd mapping the large number to root to bypass the runAsNonRoot restriction [1][2].

An attacker who can deploy a malicious container image can set the Dockerfile USER directive to a value like 4294967296 (which exceeds 32-bit range). The container runtime then looks up this value as a username in /etc/passwd, and if found, uses the mapped UID. By including a passwd entry mapping that string to root, the container runs as root despite runAsNonRoot [1][2].

The primary impact is bypassing Kubernetes' runAsNonRoot security constraint, causing containers that should run as non-root to run as root. This violates the security expectations of multi-tenant environments and can lead to privilege escalation within a pod or cluster [1][2].

The bug is fixed in containerd 2.3.1, 2.2.4, 2.0.9, and 1.7.32. Workarounds include using only trusted images, enforcing a numeric runAsUser in the Kubernetes securityContext, or using Kubernetes 1.34+ which enforces runAsNonRoot correctly. The containerd 2.1 release is end-of-life and no fix is provided [1][2].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Containerd/ContainerdGHSA2 versions
    >= 2.3.0-beta.0, < 2.3.1+ 1 more
    • (no CPE)range: >= 2.3.0-beta.0, < 2.3.1
    • (no CPE)range: <1.7.32, <2.0.9, <2.2.4, <2.3.1

Patches

7
9f8f4538dea6

Merge pull request #13447 from samuelkarp/oci-withuser-errrange-2.3

https://github.com/containerd/containerdDerek McGowanMay 20, 2026Fixed in 2.3.1via llm-release-walk
2 files changed · +36 7
  • pkg/oci/spec_opts.go+25 4 modified
    @@ -625,14 +625,25 @@ func WithUser(userstr string) SpecOpts {
     			return nil
     		}
     
    +		isErrRange := func(err error) bool {
    +			var numErr *strconv.NumError
    +			return errors.As(err, &numErr) && numErr.Err == strconv.ErrRange
    +		}
    +
     		parts := strings.Split(userstr, ":")
     		switch len(parts) {
     		case 1:
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    -				// if we cannot parse as an int32 then try to see if it is a username
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
    +				// Non-numeric user value; treat it as a username.
     				return WithUsername(userstr)(ctx, client, c, s)
     			}
    +			if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +			}
     			return WithUserID(uint32(v))(ctx, client, c, s)
     		case 2:
     			var (
    @@ -641,14 +652,24 @@ func WithUser(userstr string) SpecOpts {
     			)
     			var uid, gid uint32
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
     				username = parts[0]
    +			} else if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
     			} else {
     				uid = uint32(v)
     			}
     			v, err = strconv.Atoi(parts[1])
    -			if err != nil || v < minGroupID || v > maxGroupID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
    +				}
     				groupname = parts[1]
    +			} else if v < minGroupID || v > maxGroupID {
    +				return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
     			} else {
     				gid = uint32(v)
     			}
    
  • pkg/oci/spec_opts_user_test.go+11 3 modified
    @@ -88,15 +88,23 @@ guest:x:100:guest
     		},
     		{
     			user: "405:2147483648",
    -			err:  "no groups found",
    +			err:  "invalid USER value \"405:2147483648\": gid out of range",
     		},
     		{
     			user: "-1000",
    -			err:  "no users found",
    +			err:  "invalid USER value \"-1000\": uid out of range",
     		},
     		{
     			user: "2147483648",
    -			err:  "no users found",
    +			err:  "invalid USER value \"2147483648\": uid out of range",
    +		},
    +		{
    +			user: "999999999999999999999999999999999999",
    +			err:  "invalid USER value \"999999999999999999999999999999999999\": uid out of range",
    +		},
    +		{
    +			user: "0:999999999999999999999999999999999999",
    +			err:  "invalid USER value \"0:999999999999999999999999999999999999\": gid out of range",
     		},
     	}
     	for _, testCase := range testCases {
    
a05ae7885038

oci: return explicit error for out-of-range USER values

https://github.com/containerd/containerdLEI WANGMar 17, 2026Fixed in 2.3.1via llm-release-walk
2 files changed · +36 7
  • pkg/oci/spec_opts.go+25 4 modified
    @@ -625,14 +625,25 @@ func WithUser(userstr string) SpecOpts {
     			return nil
     		}
     
    +		isErrRange := func(err error) bool {
    +			var numErr *strconv.NumError
    +			return errors.As(err, &numErr) && numErr.Err == strconv.ErrRange
    +		}
    +
     		parts := strings.Split(userstr, ":")
     		switch len(parts) {
     		case 1:
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    -				// if we cannot parse as an int32 then try to see if it is a username
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
    +				// Non-numeric user value; treat it as a username.
     				return WithUsername(userstr)(ctx, client, c, s)
     			}
    +			if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +			}
     			return WithUserID(uint32(v))(ctx, client, c, s)
     		case 2:
     			var (
    @@ -641,14 +652,24 @@ func WithUser(userstr string) SpecOpts {
     			)
     			var uid, gid uint32
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
     				username = parts[0]
    +			} else if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
     			} else {
     				uid = uint32(v)
     			}
     			v, err = strconv.Atoi(parts[1])
    -			if err != nil || v < minGroupID || v > maxGroupID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
    +				}
     				groupname = parts[1]
    +			} else if v < minGroupID || v > maxGroupID {
    +				return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
     			} else {
     				gid = uint32(v)
     			}
    
  • pkg/oci/spec_opts_user_test.go+11 3 modified
    @@ -88,15 +88,23 @@ guest:x:100:guest
     		},
     		{
     			user: "405:2147483648",
    -			err:  "no groups found",
    +			err:  "invalid USER value \"405:2147483648\": gid out of range",
     		},
     		{
     			user: "-1000",
    -			err:  "no users found",
    +			err:  "invalid USER value \"-1000\": uid out of range",
     		},
     		{
     			user: "2147483648",
    -			err:  "no users found",
    +			err:  "invalid USER value \"2147483648\": uid out of range",
    +		},
    +		{
    +			user: "999999999999999999999999999999999999",
    +			err:  "invalid USER value \"999999999999999999999999999999999999\": uid out of range",
    +		},
    +		{
    +			user: "0:999999999999999999999999999999999999",
    +			err:  "invalid USER value \"0:999999999999999999999999999999999999\": gid out of range",
     		},
     	}
     	for _, testCase := range testCases {
    
d20c6267b88b

oci: return explicit error for out-of-range USER values

https://github.com/containerd/containerdLEI WANGMar 17, 2026Fixed in 2.2.4via llm-release-walk
2 files changed · +36 7
  • pkg/oci/spec_opts.go+25 4 modified
    @@ -626,14 +626,25 @@ func WithUser(userstr string) SpecOpts {
     			return nil
     		}
     
    +		isErrRange := func(err error) bool {
    +			var numErr *strconv.NumError
    +			return errors.As(err, &numErr) && numErr.Err == strconv.ErrRange
    +		}
    +
     		parts := strings.Split(userstr, ":")
     		switch len(parts) {
     		case 1:
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    -				// if we cannot parse as an int32 then try to see if it is a username
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
    +				// Non-numeric user value; treat it as a username.
     				return WithUsername(userstr)(ctx, client, c, s)
     			}
    +			if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +			}
     			return WithUserID(uint32(v))(ctx, client, c, s)
     		case 2:
     			var (
    @@ -642,14 +653,24 @@ func WithUser(userstr string) SpecOpts {
     			)
     			var uid, gid uint32
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
     				username = parts[0]
    +			} else if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
     			} else {
     				uid = uint32(v)
     			}
     			v, err = strconv.Atoi(parts[1])
    -			if err != nil || v < minGroupID || v > maxGroupID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
    +				}
     				groupname = parts[1]
    +			} else if v < minGroupID || v > maxGroupID {
    +				return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
     			} else {
     				gid = uint32(v)
     			}
    
  • pkg/oci/spec_opts_linux_test.go+11 3 modified
    @@ -94,15 +94,23 @@ guest:x:100:guest
     		},
     		{
     			user: "405:2147483648",
    -			err:  "no groups found",
    +			err:  "invalid USER value \"405:2147483648\": gid out of range",
     		},
     		{
     			user: "-1000",
    -			err:  "no users found",
    +			err:  "invalid USER value \"-1000\": uid out of range",
     		},
     		{
     			user: "2147483648",
    -			err:  "no users found",
    +			err:  "invalid USER value \"2147483648\": uid out of range",
    +		},
    +		{
    +			user: "999999999999999999999999999999999999",
    +			err:  "invalid USER value \"999999999999999999999999999999999999\": uid out of range",
    +		},
    +		{
    +			user: "0:999999999999999999999999999999999999",
    +			err:  "invalid USER value \"0:999999999999999999999999999999999999\": gid out of range",
     		},
     	}
     	for _, testCase := range testCases {
    
1a3d1c85e0d0

oci: return explicit error for out-of-range USER values

https://github.com/containerd/containerdLEI WANGMar 17, 2026Fixed in 2.0.9via llm-release-walk
2 files changed · +36 7
  • pkg/oci/spec_opts.go+25 4 modified
    @@ -622,14 +622,25 @@ func WithUser(userstr string) SpecOpts {
     			return nil
     		}
     
    +		isErrRange := func(err error) bool {
    +			var numErr *strconv.NumError
    +			return errors.As(err, &numErr) && numErr.Err == strconv.ErrRange
    +		}
    +
     		parts := strings.Split(userstr, ":")
     		switch len(parts) {
     		case 1:
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    -				// if we cannot parse as an int32 then try to see if it is a username
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
    +				// Non-numeric user value; treat it as a username.
     				return WithUsername(userstr)(ctx, client, c, s)
     			}
    +			if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +			}
     			return WithUserID(uint32(v))(ctx, client, c, s)
     		case 2:
     			var (
    @@ -638,14 +649,24 @@ func WithUser(userstr string) SpecOpts {
     			)
     			var uid, gid uint32
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
     				username = parts[0]
    +			} else if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
     			} else {
     				uid = uint32(v)
     			}
     			v, err = strconv.Atoi(parts[1])
    -			if err != nil || v < minGroupID || v > maxGroupID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
    +				}
     				groupname = parts[1]
    +			} else if v < minGroupID || v > maxGroupID {
    +				return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
     			} else {
     				gid = uint32(v)
     			}
    
  • pkg/oci/spec_opts_linux_test.go+11 3 modified
    @@ -93,15 +93,23 @@ guest:x:100:guest
     		},
     		{
     			user: "405:2147483648",
    -			err:  "no groups found",
    +			err:  "invalid USER value \"405:2147483648\": gid out of range",
     		},
     		{
     			user: "-1000",
    -			err:  "no users found",
    +			err:  "invalid USER value \"-1000\": uid out of range",
     		},
     		{
     			user: "2147483648",
    -			err:  "no users found",
    +			err:  "invalid USER value \"2147483648\": uid out of range",
    +		},
    +		{
    +			user: "999999999999999999999999999999999999",
    +			err:  "invalid USER value \"999999999999999999999999999999999999\": uid out of range",
    +		},
    +		{
    +			user: "0:999999999999999999999999999999999999",
    +			err:  "invalid USER value \"0:999999999999999999999999999999999999\": gid out of range",
     		},
     	}
     	for _, testCase := range testCases {
    
503f479466b4

oci: return explicit error for out-of-range USER values

https://github.com/containerd/containerdLEI WANGMar 17, 2026Fixed in 1.7.32via llm-release-walk
2 files changed · +36 7
  • oci/spec_opts.go+25 4 modified
    @@ -623,14 +623,25 @@ func WithUser(userstr string) SpecOpts {
     			return nil
     		}
     
    +		isErrRange := func(err error) bool {
    +			var numErr *strconv.NumError
    +			return errors.As(err, &numErr) && numErr.Err == strconv.ErrRange
    +		}
    +
     		parts := strings.Split(userstr, ":")
     		switch len(parts) {
     		case 1:
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    -				// if we cannot parse as an int32 then try to see if it is a username
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
    +				// Non-numeric user value; treat it as a username.
     				return WithUsername(userstr)(ctx, client, c, s)
     			}
    +			if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +			}
     			return WithUserID(uint32(v))(ctx, client, c, s)
     		case 2:
     			var (
    @@ -639,14 +650,24 @@ func WithUser(userstr string) SpecOpts {
     			)
     			var uid, gid uint32
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
     				username = parts[0]
    +			} else if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
     			} else {
     				uid = uint32(v)
     			}
     			v, err = strconv.Atoi(parts[1])
    -			if err != nil || v < minGroupID || v > maxGroupID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
    +				}
     				groupname = parts[1]
    +			} else if v < minGroupID || v > maxGroupID {
    +				return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
     			} else {
     				gid = uint32(v)
     			}
    
  • oci/spec_opts_linux_test.go+11 3 modified
    @@ -93,15 +93,23 @@ guest:x:100:guest
     		},
     		{
     			user: "405:2147483648",
    -			err:  "no groups found",
    +			err:  "invalid USER value \"405:2147483648\": gid out of range",
     		},
     		{
     			user: "-1000",
    -			err:  "no users found",
    +			err:  "invalid USER value \"-1000\": uid out of range",
     		},
     		{
     			user: "2147483648",
    -			err:  "no users found",
    +			err:  "invalid USER value \"2147483648\": uid out of range",
    +		},
    +		{
    +			user: "999999999999999999999999999999999999",
    +			err:  "invalid USER value \"999999999999999999999999999999999999\": uid out of range",
    +		},
    +		{
    +			user: "0:999999999999999999999999999999999999",
    +			err:  "invalid USER value \"0:999999999999999999999999999999999999\": gid out of range",
     		},
     	}
     	for _, testCase := range testCases {
    
503f47946643
https://github.com/containerd/containerdFixed in 1.7.32via llm-release-walk
0a8f65bef19b

Merge pull request #13448 from samuelkarp/oci-withuser-errrange-2.2

https://github.com/containerd/containerdSamuel KarpMay 20, 2026Fixed in 2.2.4via llm-release-walk
2 files changed · +36 7
  • pkg/oci/spec_opts.go+25 4 modified
    @@ -626,14 +626,25 @@ func WithUser(userstr string) SpecOpts {
     			return nil
     		}
     
    +		isErrRange := func(err error) bool {
    +			var numErr *strconv.NumError
    +			return errors.As(err, &numErr) && numErr.Err == strconv.ErrRange
    +		}
    +
     		parts := strings.Split(userstr, ":")
     		switch len(parts) {
     		case 1:
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    -				// if we cannot parse as an int32 then try to see if it is a username
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
    +				// Non-numeric user value; treat it as a username.
     				return WithUsername(userstr)(ctx, client, c, s)
     			}
    +			if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +			}
     			return WithUserID(uint32(v))(ctx, client, c, s)
     		case 2:
     			var (
    @@ -642,14 +653,24 @@ func WithUser(userstr string) SpecOpts {
     			)
     			var uid, gid uint32
     			v, err := strconv.Atoi(parts[0])
    -			if err != nil || v < minUserID || v > maxUserID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
    +				}
     				username = parts[0]
    +			} else if v < minUserID || v > maxUserID {
    +				return fmt.Errorf("invalid USER value %q: uid out of range", userstr)
     			} else {
     				uid = uint32(v)
     			}
     			v, err = strconv.Atoi(parts[1])
    -			if err != nil || v < minGroupID || v > maxGroupID {
    +			if err != nil {
    +				if isErrRange(err) {
    +					return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
    +				}
     				groupname = parts[1]
    +			} else if v < minGroupID || v > maxGroupID {
    +				return fmt.Errorf("invalid USER value %q: gid out of range", userstr)
     			} else {
     				gid = uint32(v)
     			}
    
  • pkg/oci/spec_opts_linux_test.go+11 3 modified
    @@ -94,15 +94,23 @@ guest:x:100:guest
     		},
     		{
     			user: "405:2147483648",
    -			err:  "no groups found",
    +			err:  "invalid USER value \"405:2147483648\": gid out of range",
     		},
     		{
     			user: "-1000",
    -			err:  "no users found",
    +			err:  "invalid USER value \"-1000\": uid out of range",
     		},
     		{
     			user: "2147483648",
    -			err:  "no users found",
    +			err:  "invalid USER value \"2147483648\": uid out of range",
    +		},
    +		{
    +			user: "999999999999999999999999999999999999",
    +			err:  "invalid USER value \"999999999999999999999999999999999999\": uid out of range",
    +		},
    +		{
    +			user: "0:999999999999999999999999999999999999",
    +			err:  "invalid USER value \"0:999999999999999999999999999999999999\": gid out of range",
     		},
     	}
     	for _, testCase := range testCases {
    

Vulnerability mechanics

Root cause

"In `WithUser` in `pkg/oci/spec_opts.go`, a numeric `USER` value that overflows `strconv.Atoi` (e.g., a value larger than max int) is treated as a username instead of being rejected, allowing a crafted `/etc/passwd` to map that string to UID 0."

Attack vector

An attacker builds a container image whose Dockerfile `USER` directive is set to a very large numeric string (e.g., `999999999999999999999999999999999999`) that cannot be parsed as a 32-bit integer. The image also includes a crafted `/etc/passwd` file that maps that same large string to the root user (UID 0). When containerd processes the `USER` directive, `strconv.Atoi` returns a `strconv.ErrRange` error, and the old code falls back to `WithUsername`, which looks up the string in `/etc/passwd` and resolves it to UID 0. This bypasses Kubernetes `runAsNonRoot` enforcement, causing the container to run as root despite the policy.

Affected code

The vulnerable function is `WithUser` in `pkg/oci/spec_opts.go`. The bug is in the `strconv.Atoi` error handling: when `Atoi` returns an error (including `ErrRange` for overflow), the code unconditionally falls back to `WithUsername`/username lookup instead of distinguishing between a non-numeric string and an out-of-range numeric value.

What the fix does

The patch adds an `isErrRange` helper that detects `strconv.ErrRange` errors from `strconv.Atoi`. When an out-of-range numeric value is encountered, the code now returns an explicit error (e.g., `"invalid USER value ...: uid out of range"`) instead of falling through to the username lookup path. The same logic is applied to both the single-value (UID) and colon-separated (UID:GID) cases. This ensures that any numeric `USER` value that cannot be represented as a valid 32-bit integer is rejected outright, preventing the username-based bypass.

Preconditions

  • inputAttacker must be able to build and push a container image with a crafted USER directive and /etc/passwd file
  • configThe container runtime must be containerd (versions before the fix)
  • configKubernetes cluster must have runAsNonRoot enforcement enabled

Generated on May 21, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

1