mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -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 === "/") {
|
if (mountRoot === "/") {
|
||||||
return cgroupPath;
|
return cgroupPath;
|
||||||
}
|
}
|
||||||
// Inside a cgroup namespace the record is namespace-relative and reads "/", while the
|
// cgroup_namespaces(7): a mount inherited across a namespace can report a field-4 root of
|
||||||
// mount root stays the host subtree it was mounted from. That namespace root is exactly
|
// "/.." or another non-canonical path. Which cgroup such a view exposes is not derivable
|
||||||
// what this mount exposes at its mount point, so it resolves rather than failing closed.
|
// 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 === "/") {
|
if (cgroupPath === "/") {
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -453,6 +453,36 @@ describe("resolveTsdownBuildInvocation", () => {
|
|||||||
expect(result.options.env.NODE_OPTIONS).toBe("--max-old-space-size=4352");
|
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", () => {
|
it("caps the tsdown heap from a cgroup-namespace-relative record", () => {
|
||||||
// Inside a container the record is namespace-relative ("/") while the mount root stays
|
// 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
|
// the host subtree. Failing to resolve that pair skips the limit and falls back to
|
||||||
|
|||||||
Reference in New Issue
Block a user