mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 03:45:46 -06:00
fix(scripts): reject inherited cgroup mount views instead of guessing
ClawSweeper P1 on b4d200c5d2: the previous commit resolved a namespace-relative
record against any mount root, including the inherited views cgroup_namespaces(7)
documents, whose field-4 root reads "/..". Which cgroup such a view exposes is not
derivable from mountinfo, so probing it can size the build from an unrelated
cgroup's limit.
Reject non-canonical mount roots outright. An undecidable view now falls back to
host sizing, which is current main's behavior, rather than silently adopting the
wrong budget.
Regression test covers the "/.." inherited mount: it must yield host MemTotal
sizing, not the 5 GiB limit sitting behind that mount.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -539,9 +539,15 @@ function relativeCgroupPath(mountRoot: string, cgroupPath: string) {
|
||||
if (mountRoot === "/") {
|
||||
return cgroupPath;
|
||||
}
|
||||
// Inside a cgroup namespace the record is namespace-relative and reads "/", while the
|
||||
// mount root stays the host subtree it was mounted from. That namespace root is exactly
|
||||
// what this mount exposes at its mount point, so it resolves rather than failing closed.
|
||||
// cgroup_namespaces(7): a mount inherited across a namespace can report a field-4 root of
|
||||
// "/.." or another non-canonical path. Which cgroup such a view exposes is not derivable
|
||||
// here, and guessing would size the build from an unrelated cgroup's limit.
|
||||
if (mountRoot.split("/").includes("..")) {
|
||||
return null;
|
||||
}
|
||||
// Otherwise a namespace-relative record reads "/" while the mount root stays the host
|
||||
// subtree it was mounted from. That namespace root is what this mount exposes at its
|
||||
// mount point, so it resolves rather than failing closed.
|
||||
if (cgroupPath === "/") {
|
||||
return "/";
|
||||
}
|
||||
|
||||
@@ -453,6 +453,36 @@ describe("resolveTsdownBuildInvocation", () => {
|
||||
expect(result.options.env.NODE_OPTIONS).toBe("--max-old-space-size=4352");
|
||||
});
|
||||
|
||||
it("ignores an inherited namespace mount whose root is not derivable", () => {
|
||||
// cgroup_namespaces(7): an inherited mount can report a field-4 root of "/..". Which
|
||||
// cgroup it exposes is not derivable, so it must not be probed as if it were the
|
||||
// process's own; the resolver falls back rather than sizing from an unrelated limit.
|
||||
const cgroupFiles = new Map([
|
||||
["/proc/self/cgroup", "0::/\n"],
|
||||
["/proc/self/mountinfo", "30 25 0:26 /.. /sys/fs/cgroup rw,nosuid - cgroup2 cgroup2 rw\n"],
|
||||
["/sys/fs/cgroup/memory.max", `${5 * 1024 * 1024 * 1024}\n`],
|
||||
["/proc/meminfo", `MemTotal: ${16 * 1024 * 1024} kB\n`],
|
||||
]);
|
||||
|
||||
const result = resolveTsdownBuildInvocation({
|
||||
nodeExecPath: "/usr/bin/node",
|
||||
npmExecPath: "/tmp/pnpm.cjs",
|
||||
env: {},
|
||||
fs: {
|
||||
readFileSync(filePath: string) {
|
||||
const contents = cgroupFiles.get(filePath);
|
||||
if (contents === undefined) {
|
||||
throw new Error(`ENOENT: ${filePath}`);
|
||||
}
|
||||
return contents;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Host MemTotal sizing, not the unrelated 5 GiB limit behind the inherited mount.
|
||||
expect(result.options.env.NODE_OPTIONS).toBe("--max-old-space-size=12288");
|
||||
});
|
||||
|
||||
it("caps the tsdown heap from a cgroup-namespace-relative record", () => {
|
||||
// Inside a container the record is namespace-relative ("/") while the mount root stays
|
||||
// the host subtree. Failing to resolve that pair skips the limit and falls back to
|
||||
|
||||
Reference in New Issue
Block a user