Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/jsc/bindings/BunProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
#ifndef WIN32
#include <errno.h>
#include <dlfcn.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <netdb.h>
Expand Down Expand Up @@ -531,6 +532,13 @@ JSC_DEFINE_HOST_FUNCTION_WITH_ATTRIBUTES(Process_functionDlopen, __attribute__((
}
}

#if !OS(WINDOWS)
// glibc's dlopen() copies a name without a '/' to the stack (alloca), and no path this long can be opened.
if (filename.length() >= PATH_MAX) [[unlikely]] {
Comment thread
robobun marked this conversation as resolved.
return throwError(globalObject, scope, ErrorCode::ERR_DLOPEN_FAILED, "dlopen failed: File name too long"_s);
Comment thread
robobun marked this conversation as resolved.
}
#endif

{
auto utf8_filename = filename.tryGetUTF8(ConversionMode::LenientConversion);
if (!utf8_filename) [[unlikely]] {
Expand Down
42 changes: 35 additions & 7 deletions test/js/node/process/process.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CString, dlopen, ptr } from "bun:ffi";
import { memoryUsage as jscMemoryUsage } from "bun:jsc";
import { describe, expect, it } from "bun:test";
import { familySync } from "detect-libc";
import { bunEnv, bunExe, isMacOS, isWindows, tempDir, tmpdirSync } from "harness";
import { bunEnv, bunExe, isAndroid, isLinux, isMacOS, isWindows, tempDir, tmpdirSync } from "harness";
import { basename, join, resolve } from "path";
import { getHeapStatistics } from "v8";

Expand Down Expand Up @@ -1526,20 +1526,26 @@ describe.concurrent(() => {
expect(() => process.dlopen({ module: { exports: Symbol("123") } }, Symbol("badddd"))).toThrow();
});

it("dlopen rejects over-length paths with ERR_DLOPEN_FAILED", async () => {
// Spawn so an unfixed build crashing doesn't take the whole suite down.
it.each([
// On Windows the path is widened into a 32767-unit WPathBuffer; an
// over-length path must come back as an error, not a Rust panic across
// the extern "C" boundary. POSIX already surfaces dlerror() here.
// the extern "C" boundary.
["longer than the Windows path buffer", `Buffer.alloc(40000, "x").toString()`],
// glibc's dlopen() copies a name that has no "/" to the stack with
// alloca(), so a name longer than the stack is a segfault inside dlopen().
["longer than the stack", `Buffer.alloc(16 * 1024 * 1024, "x").toString()`],
["longer than the stack, UTF-16", `"\\u0100" + Buffer.alloc(16 * 1024 * 1024, "x").toString()`],
])("dlopen rejects over-length paths with ERR_DLOPEN_FAILED (%s)", async (_, filename) => {
Comment thread
robobun marked this conversation as resolved.
// Spawn so an unfixed build crashing doesn't take the whole suite down.
await using proc = Bun.spawn({
cmd: [
bunExe(),
"-e",
`try {
process.dlopen({ exports: {} }, Buffer.alloc(40000, "x").toString());
process.dlopen({ exports: {} }, ${filename});
console.log("FAIL: did not throw");
} catch (e) {
console.log("CODE:" + e.code);
console.log(e.code + ": " + e.message);
}`,
],
env: bunEnv,
Expand All @@ -1548,12 +1554,34 @@ describe.concurrent(() => {
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect({ stdout: stdout.trim(), stderr, exitCode }).toEqual({
stdout: "CODE:ERR_DLOPEN_FAILED",
// POSIX rejects the path before dlopen() sees it, so the message does not depend on the libc.
stdout: isWindows
? expect.stringMatching(/^ERR_DLOPEN_FAILED: /)
: "ERR_DLOPEN_FAILED: dlopen failed: File name too long",
stderr: "",
exitCode: 0,
});
});

// The POSIX limit is PATH_MAX code units. One under it still goes to
// dlopen(), which reports the failure in its own words.
it.skipIf(isWindows)("dlopen rejects a path itself only from PATH_MAX", () => {
const PATH_MAX = isLinux || isAndroid ? 4096 : 1024;
const tooLong = "ERR_DLOPEN_FAILED: dlopen failed: File name too long";
function load(length) {
try {
process.dlopen({ exports: {} }, Buffer.alloc(length, "x").toString());
return "did not throw";
} catch (e) {
return e.code + ": " + e.message;
}
}
expect(load(PATH_MAX)).toBe(tooLong);
const under = load(PATH_MAX - 1);
expect(under).toStartWith("ERR_DLOPEN_FAILED: ");
expect(under).not.toBe(tooLong);
});

it("dlopen accepts file: URLs", () => {
const mod = { exports: {} };
try {
Expand Down
Loading