Files
bun.sh/test/js/node/path/resolve.test.js
robobun 5971bf67ef fix: buffer allocation for path operations with very long paths (#23819)
## 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>
2025-10-20 19:28:34 -07:00

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));
});
});