Description
The managed cgroup v2 memory limit traversal in src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cs can walk above the cgroup hierarchy mount root.
FindCGroupPath can legitimately return the hierarchy mount itself when the current process is assigned to the visible cgroup hierarchy root. For example:
hierarchyRoot = /some/container
hierarchyMount = /sys/fs/cgroup
cgroupPathRelativeToMount = /some/container
This produces:
TryGetMemoryLimitV2 currently uses a do/while loop that moves to the parent directory before checking whether the mount root has been reached. When the current path is already equal to the mount path, the traversal therefore continues above the cgroup hierarchy and can eventually dereference a null path.
The CoreCLR C++ implementation already protects this boundary (PR #130377), but the managed Common implementation does not.
Reproduction Steps
- Run on Linux with cgroup v2 enabled.
- Place the current process at the cgroup hierarchy mount root.
- Call
Interop.cgroups.TryGetMemoryLimit.
- Observe that the managed traversal attempts to walk above the mount root and can fail while resolving the parent path.
Expected behavior
When the current cgroup path equals the hierarchy mount path, traversal should stop at that path and must not inspect any parent directory.
Actual behavior
The loop executes its body and calls Path.GetDirectoryName even when the current path is already the hierarchy mount root. The traversal can continue through parent directories and eventually reach a null path.
Regression?
The issue appears to be a latent edge case in the managed cgroup v2 implementation. The corresponding CoreCLR native implementation was already fixed in #130377.
Known Workarounds
No reliable application-level workaround. The fallback to procfs is not reached if the traversal throws before returning from TryGetMemoryLimit.
Configuration
- OS: Linux
- cgroup version: v2
- Affected code: managed Common cgroup interop
- Relevant environments: containers and cgroup namespaces whose visible root is the current process cgroup
Other information
The proposed fix changes the loop so that it only reads and moves upward while the current path is below the hierarchy mount root. It also adds tests for the equality case and for nested paths that must stop at the mount root.
Description
The managed cgroup v2 memory limit traversal in
src/libraries/Common/src/Interop/Linux/cgroups/Interop.cgroups.cscan walk above the cgroup hierarchy mount root.FindCGroupPathcan legitimately return the hierarchy mount itself when the current process is assigned to the visible cgroup hierarchy root. For example:This produces:
TryGetMemoryLimitV2currently uses ado/whileloop that moves to the parent directory before checking whether the mount root has been reached. When the current path is already equal to the mount path, the traversal therefore continues above the cgroup hierarchy and can eventually dereference a null path.The CoreCLR C++ implementation already protects this boundary (PR #130377), but the managed Common implementation does not.
Reproduction Steps
Interop.cgroups.TryGetMemoryLimit.Expected behavior
When the current cgroup path equals the hierarchy mount path, traversal should stop at that path and must not inspect any parent directory.
Actual behavior
The loop executes its body and calls
Path.GetDirectoryNameeven when the current path is already the hierarchy mount root. The traversal can continue through parent directories and eventually reach a null path.Regression?
The issue appears to be a latent edge case in the managed cgroup v2 implementation. The corresponding CoreCLR native implementation was already fixed in #130377.
Known Workarounds
No reliable application-level workaround. The fallback to procfs is not reached if the traversal throws before returning from
TryGetMemoryLimit.Configuration
Other information
The proposed fix changes the loop so that it only reads and moves upward while the current path is below the hierarchy mount root. It also adds tests for the equality case and for nested paths that must stop at the mount root.