Compare commits

...

1 Commits

Author SHA1 Message Date
Claude
769495df77 fix(socket): handle non-object argument in Listener.getsockname
Listener.getsockname() assumed its first argument was always an object
and called .put() on it unconditionally. When called without arguments
or with a non-object argument (e.g. a number), JSC__JSValue__putBunString
would dereference a null pointer from getObject(), causing a crash.

Fix: check if the argument is an object first. If not, create a new
empty object. Also return the object so callers can use the result
directly.

Crash reproduction:
```js
const listener = Bun.listen({
  hostname: "localhost", port: 0,
  socket: { data(a, b) { return a; } }
});
listener.getsockname(536870887); // crash: null deref in BunString.cpp:942
listener.stop();
```
2026-02-20 22:35:19 +00:00
2 changed files with 35 additions and 2 deletions

View File

@@ -850,7 +850,8 @@ pub fn getsockname(this: *Listener, globalThis: *jsc.JSGlobalObject, callFrame:
return .js_undefined;
}
const out = callFrame.argumentsAsArray(1)[0];
const arg = callFrame.argumentsAsArray(1)[0];
const out = if (arg.isObject()) arg else JSValue.createEmptyObject(globalThis, 3);
const socket = this.listener.uws;
var buf: [64]u8 = [_]u8{0} ** 64;
@@ -872,7 +873,7 @@ pub fn getsockname(this: *Listener, globalThis: *jsc.JSGlobalObject, callFrame:
out.put(globalThis, bun.String.static("family"), family_js);
out.put(globalThis, bun.String.static("address"), address_js);
out.put(globalThis, bun.String.static("port"), port_js);
return .js_undefined;
return out;
}
pub fn jsAddServerName(global: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {

View File

@@ -295,6 +295,38 @@ describe("tcp socket binaryType", () => {
}
});
it("getsockname should not crash when called without an object argument", () => {
using server = listen({
socket: {
open() {},
close() {},
data() {},
},
hostname: "localhost",
port: 0,
});
// Calling with no arguments should return a new object
const result = server.getsockname();
expect(result).toBeObject();
expect(["IPv4", "IPv6"]).toContain(result.family);
expect(result.address).toBeDefined();
expect(typeof result.port).toBe("number");
// Calling with a non-object argument should return a new object
const result2 = server.getsockname(42 as any);
expect(result2).toBeObject();
expect(["IPv4", "IPv6"]).toContain(result2.family);
// Calling with an object should populate it
const obj = {} as any;
const result3 = server.getsockname(obj);
expect(result3).toBe(obj);
expect(["IPv4", "IPv6"]).toContain(obj.family);
expect(obj.address).toBeDefined();
expect(typeof obj.port).toBe("number");
});
it("should not leak memory", async () => {
// assert we don't leak the sockets
// we expect 1 or 2 because that's the prototype / structure