mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
## Summary - Fix use-after-free vulnerability during socket adoption by properly tracking reallocated sockets - Add safety checks to prevent linking closed sockets to context lists - Properly track socket state with new `is_closed`, `adopted`, and `is_tls` flags ## What does this PR do? This PR improves event loop stability by addressing potential use-after-free issues that can occur when sockets are reallocated during adoption (e.g., when upgrading a TCP socket to TLS). ### Key Changes **Socket State Tracking ([internal.h](packages/bun-usockets/src/internal/internal.h))** - Added `is_closed` flag to explicitly track when a socket has been closed - Added `adopted` flag to mark sockets that were reallocated during context adoption - Added `is_tls` flag to track TLS socket state for proper low-priority queue handling **Safe Socket Adoption ([context.c](packages/bun-usockets/src/context.c))** - When `us_poll_resize()` returns a new pointer (reallocation occurred), the old socket is now: - Marked as closed (`is_closed = 1`) - Added to the closed socket cleanup list - Marked as adopted (`adopted = 1`) - Has its `prev` pointer set to the new socket for event redirection - Added guards to `us_internal_socket_context_link_socket/listen_socket/connecting_socket` to prevent linking already-closed sockets **Event Loop Handling ([loop.c](packages/bun-usockets/src/loop.c))** - After callbacks that can trigger socket adoption (`on_open`, `on_writable`, `on_data`), the event loop now checks if the socket was reallocated and redirects to the new socket - Low-priority socket handling now properly checks `is_closed` state and uses `is_tls` flag for correct SSL handling **Poll Resize Safety ([epoll_kqueue.c](packages/bun-usockets/src/eventing/epoll_kqueue.c))** - Changed `us_poll_resize()` to always allocate new memory with `us_calloc()` instead of `us_realloc()` to ensure the old pointer remains valid for cleanup - Now takes `old_ext_size` parameter to correctly calculate memory sizes - Re-enabled `us_internal_loop_update_pending_ready_polls()` call in `us_poll_change()` to ensure pending events are properly redirected ### How did you verify your code works? Run existing CI and existing socket upgrade tests under asan build
68 lines
1.9 KiB
JavaScript
Generated
68 lines
1.9 KiB
JavaScript
Generated
import path from "path";
|
|
|
|
const server = Bun.serve({
|
|
port: 0,
|
|
idleTimeout: 100,
|
|
tls: {
|
|
cert: Bun.file(path.join(import.meta.dir, "fixtures", "cert.pem")),
|
|
key: Bun.file(path.join(import.meta.dir, "fixtures", "cert.key")),
|
|
},
|
|
fetch(req, server) {
|
|
if (server.upgrade(req)) {
|
|
return;
|
|
}
|
|
return new Response("Upgrade failed", { status: 500 });
|
|
},
|
|
websocket: {
|
|
idleTimeout: 120,
|
|
open(ws) {},
|
|
message(ws, message) {
|
|
ws.send(message);
|
|
},
|
|
},
|
|
});
|
|
|
|
const ws = new WebSocket(`wss://${server.hostname}:${server.port}`, { tls: { rejectUnauthorized: false } });
|
|
const { promise: openWS, resolve: onWSOpen } = Promise.withResolvers();
|
|
ws.onopen = onWSOpen;
|
|
await openWS;
|
|
for (let i = 0; i < 1000; i++) {
|
|
ws.send("hello");
|
|
}
|
|
let bytesReceived = 0;
|
|
ws.onmessage = event => {
|
|
bytesReceived += event.data.length;
|
|
};
|
|
|
|
let previousUsage = process.cpuUsage();
|
|
let previousTime = Date.now();
|
|
|
|
let count = 0;
|
|
setInterval(() => {
|
|
count++;
|
|
|
|
const currentUsage = process.cpuUsage(previousUsage);
|
|
const currentTime = Date.now();
|
|
|
|
const userCpuTime = currentUsage.user; // microseconds
|
|
const systemCpuTime = currentUsage.system; // microseconds
|
|
const totalCpuTime = userCpuTime + systemCpuTime;
|
|
|
|
const timeDeltaMs = currentTime - previousTime; // milliseconds
|
|
const timeDeltaMicroseconds = timeDeltaMs * 1000; // convert to microseconds
|
|
|
|
// Calculate percentage for the current process
|
|
const cpuUsagePercentage = (totalCpuTime / timeDeltaMicroseconds) * 100;
|
|
|
|
console.log(`CPU Usage: ${cpuUsagePercentage.toFixed(2)}%`);
|
|
|
|
previousUsage = process.cpuUsage(); // Update for the next interval
|
|
previousTime = currentTime;
|
|
|
|
if (count == 3) {
|
|
server.stop(true);
|
|
// The expected value is around 0.XX%, but we allow a 2% margin of error to account for potential flakiness.
|
|
process.exit(cpuUsagePercentage < 2 ? 0 : 1);
|
|
}
|
|
}, 1000);
|