mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
## Summary Fixed an off-by-one error in buffer allocation for several path module functions when handling paths longer than `PATH_SIZE` (typically 4096 bytes on most platforms). ## Changes - `normalizeJS_T`: Added +1 to buffer allocation for null terminator - `relativeJS_T`: Added +1 to buffer allocation for null terminator - `toNamespacedPathJS_T`: Added +9 bytes (8 for possible UNC prefix + 1 for null terminator) ## Test plan - Added tests for `path.normalize()` with paths up to 100,000 characters - Added tests for `path.relative()` with very long paths - All existing path tests continue to pass The issue occurred because when a path is exactly equal to or longer than `PATH_SIZE`, the buffer was allocated with size equal to the path length, but then a null terminator was written at `buf[bufSize]`, which was out of bounds. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
131 lines
5.3 KiB
JavaScript
131 lines
5.3 KiB
JavaScript
import { describe, expect, test } from "bun:test";
|
|
import { isWindows } from "harness";
|
|
import assert from "node:assert";
|
|
// import child from "node:child_process";
|
|
import path from "node:path";
|
|
// import fixtures from "./common/fixtures.js";
|
|
|
|
describe("path.resolve", () => {
|
|
test("general", () => {
|
|
const failures = [];
|
|
const slashRE = /\//g;
|
|
const backslashRE = /\\/g;
|
|
|
|
const posixyCwd = isWindows
|
|
? (() => {
|
|
const _ = process.cwd().replaceAll(path.sep, path.posix.sep);
|
|
return _.slice(_.indexOf(path.posix.sep));
|
|
})()
|
|
: process.cwd();
|
|
|
|
const resolveTests = [
|
|
[
|
|
path.win32.resolve,
|
|
// Arguments result
|
|
[
|
|
[["c:/blah\\blah", "d:/games", "c:../a"], "c:\\blah\\a"],
|
|
[["c:/ignore", "d:\\a/b\\c/d", "\\e.exe"], "d:\\e.exe"],
|
|
[["c:/ignore", "c:/some/file"], "c:\\some\\file"],
|
|
[["d:/ignore", "d:some/dir//"], "d:\\ignore\\some\\dir"],
|
|
[["."], process.cwd()],
|
|
[["//server/share", "..", "relative\\"], "\\\\server\\share\\relative"],
|
|
[["c:/", "//"], "c:\\"],
|
|
[["c:/", "//dir"], "c:\\dir"],
|
|
[["c:/", "//server/share"], "\\\\server\\share\\"],
|
|
[["c:/", "//server//share"], "\\\\server\\share\\"],
|
|
[["c:/", "///some//dir"], "c:\\some\\dir"],
|
|
[["C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js"], "C:\\foo\\tmp.3\\cycles\\root.js"],
|
|
],
|
|
],
|
|
[
|
|
path.posix.resolve,
|
|
// Arguments result
|
|
[
|
|
[["/var/lib", "../", "file/"], "/var/file"],
|
|
[["/var/lib", "/../", "file/"], "/file"],
|
|
[["a/b/c/", "../../.."], posixyCwd],
|
|
[["."], posixyCwd],
|
|
[["/some/dir", ".", "/absolute/"], "/absolute"],
|
|
[["/foo/tmp.3/", "../tmp.3/cycles/root.js"], "/foo/tmp.3/cycles/root.js"],
|
|
],
|
|
],
|
|
];
|
|
resolveTests.forEach(([resolve, tests]) => {
|
|
tests.forEach(([test, expected]) => {
|
|
const actual = resolve.apply(null, test);
|
|
let actualAlt;
|
|
const os = resolve === path.win32.resolve ? "win32" : "posix";
|
|
if (resolve === path.win32.resolve && !isWindows) actualAlt = actual.replace(backslashRE, "/");
|
|
else if (resolve !== path.win32.resolve && isWindows) actualAlt = actual.replace(slashRE, "\\");
|
|
|
|
const message = `path.${os}.resolve(${test.map(JSON.stringify).join(",")})\n expect=${JSON.stringify(
|
|
expected,
|
|
)}\n actual=${JSON.stringify(actual)}`;
|
|
if (actual !== expected && actualAlt !== expected) failures.push(message);
|
|
});
|
|
});
|
|
assert.strictEqual(failures.length, 0, failures.join("\n"));
|
|
|
|
// TODO: Enable test once spawnResult.stdout works on Windows.
|
|
// if (isWindows) {
|
|
// // Test resolving the current Windows drive letter from a spawned process.
|
|
// // See https://github.com/nodejs/node/issues/7215
|
|
// const currentDriveLetter = path.parse(process.cwd()).root.substring(0, 2);
|
|
// const relativeFixture = fixtures.path("path-resolve.js");
|
|
|
|
// const spawnResult = child.spawnSync(process.argv[0], [relativeFixture, currentDriveLetter]);
|
|
// const resolvedPath = spawnResult.stdout.toString().trim();
|
|
// assert.strictEqual(resolvedPath.toLowerCase(), process.cwd().toLowerCase());
|
|
// }
|
|
|
|
// TODO: Enable once support for customizing process.cwd lands.
|
|
// if (!isWindows) {
|
|
// // Test handling relative paths to be safe when process.cwd() fails.
|
|
// const cwd = process.cwd;
|
|
// process.cwd = () => "";
|
|
// try {
|
|
// assert.strictEqual(process.cwd(), "");
|
|
// const resolved = path.resolve();
|
|
// const expected = ".";
|
|
// assert.strictEqual(resolved, expected);
|
|
// } finally {
|
|
// process.cwd = cwd;
|
|
// }
|
|
// }
|
|
});
|
|
|
|
test("undefined argument are ignored if absolute path comes first (reverse loop through args)", () => {
|
|
expect(() => {
|
|
return path.posix.resolve(undefined, "hi");
|
|
}).toThrow('The "paths[0]" property must be of type string, got undefined');
|
|
expect(() => {
|
|
return path.posix.resolve(undefined, "/hi");
|
|
}).not.toThrow();
|
|
});
|
|
|
|
test("very long paths", () => {
|
|
// Regression test: buffer overflow with very long paths
|
|
// This used to panic because the buffer didn't account for the null terminator
|
|
for (const len of [4096, 10000, 50000, 98340, 100000]) {
|
|
// Use platform-specific absolute path prefix
|
|
const prefix = isWindows ? "C:\\" : "/";
|
|
const longPath = prefix + "a".repeat(len);
|
|
const result = path.resolve(longPath);
|
|
// Should return an absolute path with the repeated 'a' characters
|
|
assert.ok(result.includes("a"));
|
|
assert.ok(path.isAbsolute(result));
|
|
// Length should be prefix length + repeated characters
|
|
assert.strictEqual(result.length, prefix.length + len);
|
|
}
|
|
|
|
// Test with multiple paths that concatenate to a very long path
|
|
const longSegment = "b".repeat(50000);
|
|
const pathPrefix = isWindows ? "C:\\" : "/";
|
|
const result = path.resolve(pathPrefix, longSegment, "c");
|
|
assert.ok(result.includes("b"));
|
|
// On Windows, paths use backslash; on POSIX, forward slash
|
|
const expectedEnding = isWindows ? "\\c" : "/c";
|
|
assert.ok(result.endsWith(expectedEnding));
|
|
});
|
|
});
|