mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
fix double free with invalid TLSOptions (#14648)
This commit is contained in:
@@ -3305,21 +3305,22 @@ pub fn serve(
|
||||
const exception = &exception_;
|
||||
|
||||
var args = JSC.Node.ArgumentsSlice.init(globalObject.bunVM(), arguments);
|
||||
var config_ = JSC.API.ServerConfig.fromJS(globalObject.ptr(), &args, exception);
|
||||
var config: JSC.API.ServerConfig = .{};
|
||||
JSC.API.ServerConfig.fromJS(globalObject, &config, &args, exception);
|
||||
if (exception[0] != null) {
|
||||
config_.deinit();
|
||||
config.deinit();
|
||||
|
||||
globalObject.throwValue(exception_[0].?.value());
|
||||
return .undefined;
|
||||
return .zero;
|
||||
}
|
||||
|
||||
if (globalObject.hasException()) {
|
||||
config_.deinit();
|
||||
config.deinit();
|
||||
|
||||
return .zero;
|
||||
}
|
||||
|
||||
break :brk config_;
|
||||
break :brk config;
|
||||
};
|
||||
|
||||
var exception_value: *JSC.JSValue = undefined;
|
||||
|
||||
@@ -1174,11 +1174,16 @@ pub const ServerConfig = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn fromJS(global: *JSC.JSGlobalObject, arguments: *JSC.Node.ArgumentsSlice, exception: JSC.C.ExceptionRef) ServerConfig {
|
||||
pub fn fromJS(
|
||||
global: *JSC.JSGlobalObject,
|
||||
args: *ServerConfig,
|
||||
arguments: *JSC.Node.ArgumentsSlice,
|
||||
exception: JSC.C.ExceptionRef,
|
||||
) void {
|
||||
const vm = arguments.vm;
|
||||
const env = vm.bundler.env;
|
||||
|
||||
var args = ServerConfig{
|
||||
args.* = .{
|
||||
.address = .{
|
||||
.tcp = .{
|
||||
.port = 3000,
|
||||
@@ -1236,13 +1241,13 @@ pub const ServerConfig = struct {
|
||||
if (arguments.next()) |arg| {
|
||||
if (!arg.isObject()) {
|
||||
JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.get(global, "static")) |static| {
|
||||
if (!static.isObject()) {
|
||||
JSC.throwInvalidArguments("Bun.serve expects 'static' to be an object shaped like { [pathname: string]: Response }", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
var iter = JSC.JSPropertyIterator(.{
|
||||
@@ -1259,13 +1264,13 @@ pub const ServerConfig = struct {
|
||||
if (path.len == 0 or path[0] != '/') {
|
||||
bun.default_allocator.free(path);
|
||||
JSC.throwInvalidArguments("Invalid static route \"{s}\". path must start with '/'", .{path}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_ascii) {
|
||||
bun.default_allocator.free(path);
|
||||
JSC.throwInvalidArguments("Invalid static route \"{s}\". Please encode all non-ASCII characters in the path.", .{path}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (StaticRoute.fromJS(global, value)) |route| {
|
||||
@@ -1275,28 +1280,28 @@ pub const ServerConfig = struct {
|
||||
}) catch bun.outOfMemory();
|
||||
} else if (global.hasException()) {
|
||||
bun.default_allocator.free(path);
|
||||
return args;
|
||||
return;
|
||||
} else {
|
||||
Output.panic("Internal error: expected exception or static route", .{});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.get(global, "idleTimeout")) |value| {
|
||||
if (!value.isUndefinedOrNull()) {
|
||||
if (!value.isAnyInt()) {
|
||||
JSC.throwInvalidArguments("Bun.serve expects idleTimeout to be an integer", .{}, global, exception);
|
||||
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
args.has_idleTimeout = true;
|
||||
|
||||
const idleTimeout: u64 = @intCast(@max(value.toInt64(), 0));
|
||||
if (idleTimeout > 255) {
|
||||
JSC.throwInvalidArguments("Bun.serve expects idleTimeout to be 255 or less", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
args.idleTimeout = @truncate(idleTimeout);
|
||||
@@ -1309,7 +1314,7 @@ pub const ServerConfig = struct {
|
||||
if (args.ssl_config) |*conf| {
|
||||
conf.deinit();
|
||||
}
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (WebSocketServer.onCreate(global, websocket_object)) |wss| {
|
||||
@@ -1318,10 +1323,10 @@ pub const ServerConfig = struct {
|
||||
if (args.ssl_config) |*conf| {
|
||||
conf.deinit();
|
||||
}
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.getTruthy(global, "port")) |port_| {
|
||||
args.address.tcp.port = @as(
|
||||
@@ -1333,7 +1338,7 @@ pub const ServerConfig = struct {
|
||||
);
|
||||
port = args.address.tcp.port;
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.getTruthy(global, "baseURI")) |baseURI| {
|
||||
var sliced = baseURI.toSlice(global, bun.default_allocator);
|
||||
@@ -1343,7 +1348,7 @@ pub const ServerConfig = struct {
|
||||
args.base_uri = bun.default_allocator.dupe(u8, sliced.slice()) catch unreachable;
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.getTruthy(global, "hostname") orelse arg.getTruthy(global, "host")) |host| {
|
||||
const host_str = host.toSlice(
|
||||
@@ -1357,7 +1362,7 @@ pub const ServerConfig = struct {
|
||||
has_hostname = true;
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.getTruthy(global, "unix")) |unix| {
|
||||
const unix_str = unix.toSlice(
|
||||
@@ -1368,13 +1373,13 @@ pub const ServerConfig = struct {
|
||||
if (unix_str.len > 0) {
|
||||
if (has_hostname) {
|
||||
JSC.throwInvalidArguments("Cannot specify both hostname and unix", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
args.address = .{ .unix = bun.default_allocator.dupeZ(u8, unix_str.slice()) catch unreachable };
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.get(global, "id")) |id| {
|
||||
if (id.isUndefinedOrNull()) {
|
||||
@@ -1392,59 +1397,59 @@ pub const ServerConfig = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.get(global, "development")) |dev| {
|
||||
args.development = dev.coerce(bool, global);
|
||||
args.reuse_port = !args.development;
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.get(global, "reusePort")) |dev| {
|
||||
args.reuse_port = dev.coerce(bool, global);
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.get(global, "inspector")) |inspector| {
|
||||
args.inspector = inspector.coerce(bool, global);
|
||||
|
||||
if (args.inspector and !args.development) {
|
||||
JSC.throwInvalidArguments("Cannot enable inspector in production. Please set development: true in Bun.serve()", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.getTruthy(global, "maxRequestBodySize")) |max_request_body_size| {
|
||||
if (max_request_body_size.isNumber()) {
|
||||
args.max_request_body_size = @as(u64, @intCast(@max(0, max_request_body_size.toInt64())));
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.getTruthyComptime(global, "error")) |onError| {
|
||||
if (!onError.isCallable(global.vm())) {
|
||||
JSC.throwInvalidArguments("Expected error to be a function", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
const onErrorSnapshot = onError.withAsyncContextIfNeeded(global);
|
||||
args.onError = onErrorSnapshot;
|
||||
onErrorSnapshot.protect();
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
if (arg.getTruthy(global, "fetch")) |onRequest_| {
|
||||
if (!onRequest_.isCallable(global.vm())) {
|
||||
JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
const onRequest = onRequest_.withAsyncContextIfNeeded(global);
|
||||
JSC.C.JSValueProtect(global, onRequest.asObjectRef());
|
||||
args.onRequest = onRequest;
|
||||
} else {
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
JSC.throwInvalidArguments("Expected fetch() to be a function", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (arg.getTruthy(global, "tls")) |tls| {
|
||||
@@ -1452,7 +1457,7 @@ pub const ServerConfig = struct {
|
||||
var value_iter = tls.arrayIterator(global);
|
||||
if (value_iter.len == 1) {
|
||||
JSC.throwInvalidArguments("tls option expects at least 1 tls object", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
while (value_iter.next()) |item| {
|
||||
if (SSLConfig.inJS(vm, global, item, exception)) |ssl_config| {
|
||||
@@ -1463,7 +1468,7 @@ pub const ServerConfig = struct {
|
||||
var config = ssl_config;
|
||||
defer config.deinit();
|
||||
JSC.throwInvalidArguments("SNI tls object must have a serverName", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
if (args.sni == null) {
|
||||
args.sni = bun.BabyList(SSLConfig).initCapacity(bun.default_allocator, value_iter.len - 1) catch bun.outOfMemory();
|
||||
@@ -1474,11 +1479,11 @@ pub const ServerConfig = struct {
|
||||
}
|
||||
|
||||
if (exception.* != null) {
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (global.hasException()) {
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1487,15 +1492,15 @@ pub const ServerConfig = struct {
|
||||
}
|
||||
|
||||
if (exception.* != null) {
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (global.hasException()) {
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (global.hasException()) return args;
|
||||
if (global.hasException()) return;
|
||||
|
||||
// @compatibility Bun v0.x - v0.2.1
|
||||
// this used to be top-level, now it's "tls" object
|
||||
@@ -1505,16 +1510,16 @@ pub const ServerConfig = struct {
|
||||
}
|
||||
|
||||
if (exception.* != null) {
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (global.hasException()) {
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
JSC.throwInvalidArguments("Bun.serve expects an object", .{}, global, exception);
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.base_uri.len > 0) {
|
||||
@@ -1523,14 +1528,14 @@ pub const ServerConfig = struct {
|
||||
JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global, exception);
|
||||
bun.default_allocator.free(@constCast(args.base_uri));
|
||||
args.base_uri = "";
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strings.isAllASCII(args.base_uri)) {
|
||||
JSC.throwInvalidArguments("Unicode baseURI must already be encoded for now.\nnew URL(baseuRI).toString() should do the trick.", .{}, global, exception);
|
||||
bun.default_allocator.free(@constCast(args.base_uri));
|
||||
args.base_uri = "";
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.base_url.protocol.len == 0) {
|
||||
@@ -1598,7 +1603,7 @@ pub const ServerConfig = struct {
|
||||
JSC.throwInvalidArguments("Unicode hostnames must already be encoded for now.\nnew URL(input).hostname should do the trick.", .{}, global, exception);
|
||||
bun.default_allocator.free(@constCast(args.base_uri));
|
||||
args.base_uri = "";
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
args.base_url = URL.parse(args.base_uri);
|
||||
@@ -1610,17 +1615,17 @@ pub const ServerConfig = struct {
|
||||
JSC.throwInvalidArguments("baseURI must have a hostname", .{}, global, exception);
|
||||
bun.default_allocator.free(@constCast(args.base_uri));
|
||||
args.base_uri = "";
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.base_url.username.len > 0 or args.base_url.password.len > 0) {
|
||||
JSC.throwInvalidArguments("baseURI can't have a username or password", .{}, global, exception);
|
||||
bun.default_allocator.free(@constCast(args.base_uri));
|
||||
args.base_uri = "";
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
|
||||
return args;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6167,7 +6172,9 @@ pub fn NewServer(comptime NamespaceType: type, comptime ssl_enabled_: bool, comp
|
||||
defer args_slice.deinit();
|
||||
var exception_ref = [_]JSC.C.JSValueRef{null};
|
||||
const exception: JSC.C.ExceptionRef = &exception_ref;
|
||||
var new_config = ServerConfig.fromJS(globalThis, &args_slice, exception);
|
||||
|
||||
var new_config: ServerConfig = .{};
|
||||
ServerConfig.fromJS(globalThis, &new_config, &args_slice, exception);
|
||||
if (exception.* != null) {
|
||||
new_config.deinit();
|
||||
globalThis.throwValue(exception_ref[0].?.value());
|
||||
|
||||
@@ -1649,6 +1649,24 @@ describe("should error with invalid options", async () => {
|
||||
});
|
||||
}).toThrow("Expected lowMemoryMode to be a boolean");
|
||||
});
|
||||
it("multiple missing server name", () => {
|
||||
expect(() => {
|
||||
Bun.serve({
|
||||
port: 0,
|
||||
fetch(req) {
|
||||
return new Response("hi");
|
||||
},
|
||||
tls: [
|
||||
{
|
||||
key: "lkwejflkwjeflkj",
|
||||
},
|
||||
{
|
||||
key: "lkwjefhwlkejfklwj",
|
||||
},
|
||||
],
|
||||
});
|
||||
}).toThrow("SNI tls object must have a serverName");
|
||||
});
|
||||
});
|
||||
it("should resolve pending promise if requested ended with pending read", async () => {
|
||||
let error: Error;
|
||||
|
||||
Reference in New Issue
Block a user