CVE-2024-40635: Integer Overflow in Containerd User ID Handling Leads to Privilege Escalation
Executive Summary
CVE-2024-40635 is a vulnerability affecting containerd, a widely used open-source container runtime. This vulnerability stems from an integer overflow in the handling of User IDs (UIDs) and Group IDs (GIDs) when launching containers. Specifically, if a container is launched with a user specified as UID:GID
where either the UID or GID is larger than the maximum 32-bit signed integer (math.MaxInt32
), an overflow condition can occur. This overflow results in the container unexpectedly running as root (UID 0), effectively bypassing intended security restrictions and leading to potential privilege escalation. The vulnerability has been patched in containerd versions 1.6.38, 1.7.27, and 2.0.4. Users of earlier versions are strongly advised to upgrade immediately.
Technical Details
This vulnerability affects containerd versions prior to:
- 1.6.38
- 1.7.27
- 2.0.4
The vulnerability resides in the pkg/oci/spec_opts.go
file, specifically within the WithUser
function. This function is responsible for parsing the user string provided when launching a container and setting the appropriate UID and GID for the container process.
The core issue is the lack of proper validation of the UID and GID values parsed from the user string. Before the patch, the code would attempt to convert the UID and GID strings to integers using strconv.Atoi
. However, it did not check if the resulting integer values were within the valid range for a 32-bit signed integer. This omission allowed an attacker to provide a UID or GID value exceeding math.MaxInt32
, leading to an integer overflow.
The affected component is the OCI specification handling within containerd, which is responsible for configuring the container's runtime environment based on the provided specifications.
Root Cause Analysis
The root cause of CVE-2024-40635 is an integer overflow vulnerability (CWE-190) in the WithUser
function within pkg/oci/spec_opts.go
. Let's examine the vulnerable code snippet before the patch:
func WithUser(userstr string) SpecOpts {
// ...
switch len(parts) {
case 1:
v, err := strconv.Atoi(parts[0])
if err != nil {
// if we cannot parse as a uint they try to see if it is a username
return WithUsername(userstr)(ctx, client, c, s)
}
return WithUserID(uint32(v))(ctx, client, c, s)
case 2:
// ...
var uid, gid uint32
v, err := strconv.Atoi(parts[0])
if err != nil {
username = parts[0]
} else {
uid = uint32(v)
}
if v, err = strconv.Atoi(parts[1]); err != nil {
groupname = parts[1]
} else {
gid = uint32(v)
}
// ...
}
// ...
}
In this code, the strconv.Atoi
function converts the string representation of the UID and GID to integers. If the provided string is a valid integer, strconv.Atoi
returns the integer value. However, if the integer value is larger than math.MaxInt32
, the conversion will still succeed, but the resulting value will wrap around due to the nature of integer overflow. This wrapped-around value is then cast to uint32
and assigned to the uid
or gid
variable.
Because the kernel and underlying system calls often use unsigned integers for UID and GID, a large signed integer that overflows and becomes negative can then wrap around to a very large unsigned integer when cast to uint32
. However, the more direct issue is that the lack of validation allows the value to become 0, which is root.
For example, if parts[0]
is "2147483648" (which is math.MaxInt32 + 1
), strconv.Atoi
will return an error. However, if the value is -2147483649
, it will be parsed as an integer, and when cast to uint32
, it will become a large positive number. More importantly, if the value is 4294967296
(2^32), then strconv.Atoi
will return an error, but if the value is 2147483648
, it will be parsed as an integer, and when cast to uint32
, it will become 2147483648
.
The critical flaw is that the code doesn't validate whether the parsed integer v
falls within the acceptable range before casting it to uint32
. This allows an attacker to specify a large UID or GID value that, after the implicit conversion to uint32
, results in the container running with unintended privileges, potentially as root (UID 0).
Patch Analysis
The fix for CVE-2024-40635 involves adding validation to ensure that the UID and GID values are within the valid range of 32-bit signed integers. The patch introduces constants for the minimum and maximum allowed UID and GID values and checks the parsed integer against these limits.
Here's the relevant code diff from pkg/oci/spec_opts.go
:
--- a/pkg/oci/spec_opts.go
+++ b/pkg/oci/spec_opts.go
@@ -593,6 +594,20 @@ func WithUser(userstr string) SpecOpts {
defer ensureAdditionalGids(s)
setProcess(s)
s.Process.User.AdditionalGids = nil
+ // While the Linux kernel allows the max UID to be MaxUint32 - 2,
+ // and the OCI Runtime Spec has no definition about the max UID,
+ // the runc implementation is known to require the UID to be <= MaxInt32.
+ //
+ // containerd follows runc's limitation here.
+ //
+ // In future we may relax this limitation to allow MaxUint32 - 2,
+ // or, amend the OCI Runtime Spec to codify the implementation limitation.
+ const (
+ minUserID = 0
+ maxUserID = math.MaxInt32
+ minGroupID = 0
+ maxGroupID = math.MaxInt32
+ )
// For LCOW it's a bit harder to confirm that the user actually exists on the host as a rootfs isn't
// mounted on the host and shared into the guest, but rather the rootfs is constructed entirely in the
@@ -611,8 +626,8 @@ func WithUser(userstr string) SpecOpts {
switch len(parts) {
case 1:
v, err := strconv.Atoi(parts[0])
- if err != nil {
- // if we cannot parse as a uint they try to see if it is a username
+ if err != nil || v < minUserID || v > maxUserID {
+ // if we cannot parse as an int32 then try to see if it is a username
return WithUsername(userstr)(ctx, client, c, s)
}
return WithUserID(uint32(v))(ctx, client, c, s)
@@ -623,12 +638,13 @@ func WithUser(userstr string) SpecOpts {
)
var uid, gid uint32
v, err := strconv.Atoi(parts[0])
- if err != nil {
+ if err != nil || v < minUserID || v > maxUserID {
username = parts[0]
} else {
uid = uint32(v)
}
- if v, err = strconv.Atoi(parts[1]); err != nil {
+ if v, err = strconv.Atoi(parts[1])
+ if err != nil || v < minGroupID || v > maxGroupID {
groupname = parts[1]
} else {
gid = uint32(v)
The patch effectively prevents the integer overflow by validating the UID and GID values before they are used to configure the container's runtime environment.
The provided diff also includes a test file oci/spec_opts_linux_test.go
which adds comprehensive test cases to verify the fix. These tests cover various scenarios, including valid and invalid UID/GID values, usernames, and group names.
Exploitation Techniques
An attacker can exploit this vulnerability by crafting a container image or configuration that specifies a user with a UID or GID exceeding math.MaxInt32
. When containerd attempts to launch the container, the WithUser
function will parse the malicious UID/GID value. Due to the missing validation in the vulnerable versions, the integer overflow will occur, potentially causing the container to run as root (UID 0).
Proof of Concept (PoC) - Theoretical
Since direct container image crafting is complex and depends on the specific containerization tools used, a simplified PoC focuses on demonstrating how the vulnerable WithUser
function can be triggered with a malicious user string.
Disclaimer: This PoC is theoretical and demonstrates the vulnerability's core mechanism. It does not represent a complete exploit, as it requires integration with a container runtime environment.
package main
import (
"context"
"fmt"
"math"
"strconv"
specs "github.com/opencontainers/runtime-spec/specs-go"
)
// Simplified Spec and Container structs for demonstration
type Spec struct {
Version string
Process *specs.Process
}
type Container struct {
ID string
}
// Simplified WithUser function (vulnerable version)
func WithUser(userstr string) func(context.Context, interface{}, *Container, *Spec) error {
return func(_ context.Context, _ interface{}, _ *Container, s *Spec) error {
if s.Process == nil {
s.Process = &specs.Process{
User: specs.User{},
}
}
parts := []string{userstr} // Simplified parsing
v, err := strconv.Atoi(parts[0])
if err != nil {
fmt.Println("Error converting to integer:", err)
return err
}
s.Process.User.UID = uint32(v) // Vulnerable line: no validation
fmt.Printf("UID set to: %d\n", s.Process.User.UID)
return nil
}
}
func main() {
c := Container{ID: "test-container"}
s := Spec{
Version: "1.0.0",
Process: &specs.Process{
User: specs.User{},
},
}
// Malicious user string with UID exceeding math.MaxInt32
maliciousUser := "2147483648" // math.MaxInt32 + 1
err := WithUser(maliciousUser)(context.Background(), nil, &c, &s)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Container UID: %d\n", s.Process.User.UID)
// Check if the UID is 0 (root) - this is a simplified check
if s.Process.User.UID == 0 {
fmt.Println("VULNERABILITY TRIGGERED: Container potentially running as root!")
} else if s.Process.User.UID == 2147483648 {
fmt.Println("VULNERABILITY TRIGGERED: Container potentially running as UID 2147483648!")
}
}
Explanation:
- This PoC simulates the vulnerable
WithUser
function. - It takes a malicious user string "2147483648" (math.MaxInt32 + 1).
- It converts the string to an integer using
strconv.Atoi
. - It directly assigns the resulting integer (without validation) to
s.Process.User.UID
. - The output demonstrates that the UID is set to 2147483648, which could lead to unexpected behavior or privilege escalation in a real container runtime.
Attack Scenario:
- An attacker crafts a malicious container image that specifies a user with a UID/GID exceeding
math.MaxInt32
in its configuration (e.g., Dockerfile, Kubernetes deployment manifest). - The attacker deploys this malicious container image to a vulnerable containerd environment.
- When containerd attempts to launch the container, the
WithUser
function processes the malicious UID/GID value. - Due to the integer overflow, the container is launched with root privileges (UID 0) or an extremely high UID.
- The attacker can then execute arbitrary commands within the container with root privileges, potentially compromising the entire host system.
Real-World Impacts:
- Privilege Escalation: The most direct impact is privilege escalation within the container. An attacker can gain root privileges inside the container, even if the container was intended to run as a non-root user.
- Container Escape: In some cases, privilege escalation within the container can lead to container escape, where the attacker gains access to the host system.
- Data Breach: If the container has access to sensitive data, an attacker with root privileges can steal or modify this data.
- Denial of Service: An attacker can use the compromised container to launch denial-of-service attacks against other containers or the host system.
- Supply Chain Attacks: Malicious container images with this vulnerability can be distributed through public or private registries, potentially affecting a large number of users.
Mitigation Strategies
To mitigate the risk of CVE-2024-40635, the following strategies are recommended:
- Upgrade Containerd: The most effective mitigation is to upgrade to containerd version 1.6.38, 1.7.27, or 2.0.4 or later. These versions contain the patch that fixes the integer overflow vulnerability.
- Image Scanning: Implement container image scanning to identify and block malicious images with suspicious UID/GID configurations. Tools like Clair, Trivy, and Anchore can be used for this purpose.
- Principle of Least Privilege: Enforce the principle of least privilege by ensuring that containers run with the minimum necessary privileges. Avoid running containers as root whenever possible.
- User Namespace Remapping: Use user namespace remapping to map container UIDs and GIDs to different UIDs and GIDs on the host system. This can help to isolate containers and prevent privilege escalation.
- Pod Security Policies/Pod Security Standards (Kubernetes): In Kubernetes environments, use Pod Security Policies (PSPs) or Pod Security Standards (PSS) to restrict the capabilities of containers and prevent them from running as root.
- Runtime Security: Implement runtime security monitoring to detect and prevent malicious activity within containers. Tools like Falco and Sysdig can be used for this purpose.
- Regular Security Audits: Conduct regular security audits of your container infrastructure to identify and address potential vulnerabilities.
- Trusted Image Registries: Only use container images from trusted registries. Verify the integrity and authenticity of images before deploying them.
Configuration Changes:
- Kubernetes Pod Security Context:
To mitigate, ensureapiVersion: v1 kind: Pod metadata: name: vulnerable-pod spec: securityContext: runAsUser: 2147483648 # Malicious UID containers: - name: vulnerable-container image: malicious-image:latest
runAsUser
is within the valid range or use a security policy to prevent setting such values.
Security Best Practices:
- Regularly update container images: Keep your container images up-to-date with the latest security patches.
- Use a minimal base image: Reduce the attack surface by using a minimal base image for your containers.
- Secure your container registry: Protect your container registry from unauthorized access.
- Monitor container activity: Monitor container activity for suspicious behavior.
Timeline of Discovery and Disclosure
- Vulnerability Discovered: (Exact date unavailable, but prior to patch commits)
- Vulnerability Reported: (Date unavailable)
- Patches Released:
- containerd 1.6.38: (Commit cf158e884cfe4812a6c371b59e4ea9bc4c46e51a)
- containerd 1.7.27: (Commit 05044ec0a9a75232cad458027ca83437aae3f4da)
- containerd 2.0.4: (Commit 1a43cb6a1035441f9aca8f5666a9b3ef9e70ab20)
- Public Disclosure: March 17, 2025 (According to NVD)
References
- NVD: https://nvd.nist.gov/vuln/detail/CVE-2024-40635
- GitHub Security Advisory: https://github.com/containerd/containerd/security/advisories/GHSA-265r-hfxg-fhmg
- Red Hat CVE Page: https://access.redhat.com/security/cve/cve-2024-40635?utm_source=feedly
- OSV: https://osv.dev/vulnerability/CVE-2024-40635
- Commit 1.6.38: https://github.com/containerd/containerd/commit/cf158e884cfe4812a6c371b59e4ea9bc4c46e51a
- Commit 1.7.27: https://github.com/containerd/containerd/commit/05044ec0a9a75232cad458027ca83437aae3f4da
- Commit 2.0.4: https://github.com/containerd/containerd/commit/1a43cb6a1035441f9aca8f5666a9b3ef9e70ab20
Comparative Analysis
This vulnerability shares similarities with other integer overflow vulnerabilities found in various software components. For example, similar vulnerabilities have been found in web servers, operating systems, and other container runtimes. The common theme is the lack of proper input validation, which allows attackers to manipulate integer values and cause unexpected behavior.
Compared to past vulnerabilities, the security community has become more aware of the risks associated with integer overflows. Modern programming languages and compilers often provide built-in mechanisms to detect and prevent these types of vulnerabilities. However, as CVE-2024-40635 demonstrates, integer overflows can still occur in complex software systems like container runtimes.
The evolution of security practices has led to the development of more robust mitigation strategies, such as container image scanning, runtime security monitoring, and user namespace remapping. These techniques can help to detect and prevent exploitation of integer overflow vulnerabilities and other security flaws.