Skip to content

Commit 4b5d010

Browse files
authored
Merge pull request #2513 from lightpanda-io/syncRequest_uaf
Protect against dangling pointer in syncRequest
2 parents c0c1ae2 + 05a373d commit 4b5d010

1 file changed

Lines changed: 23 additions & 8 deletions

File tree

src/browser/HttpClient.zig

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -441,13 +441,18 @@ pub fn _request(_: *anyopaque, transfer: *Transfer) !void {
441441
return transfer.client.process(transfer);
442442
}
443443

444-
// Ownership contract: from the moment this function is entered, the
445-
// HttpClient owns `req` — specifically `req.headers` (a curl_slist).
446-
// On success, transfer.deinit eventually frees it. On any failure path
447-
// inside this function, we free it before returning the error. Callers
448-
// must NOT pair `request()` with their own `errdefer headers.deinit()`
449-
// — that's a double-free.
444+
// HttpClient takes ownership of req.headers; do not pair with
445+
// `errdefer headers.deinit()`
450446
pub fn request(self: *Client, req: Request, owner: ?*Owner) !void {
447+
_ = try self.requestT(req, owner);
448+
}
449+
450+
// Like `request`, but returns the created `*Transfer`. The caller does not own
451+
// the returned `*Transfer` and must thus use it with care. From the moment this
452+
// function is entered, the HttpClient owns `req` — specifically `req.headers`
453+
// On success, transfer.deinit eventually frees it. On any failure path inside
454+
// this function, we free it before returning the error.
455+
fn requestT(self: *Client, req: Request, owner: ?*Owner) !*Transfer {
451456
const arena = self.arena_pool.acquire(.small, "Request.arena") catch |err| {
452457
req.headers.deinit();
453458
return err;
@@ -502,6 +507,8 @@ pub fn request(self: *Client, req: Request, owner: ?*Owner) !void {
502507
}
503508
return err;
504509
};
510+
511+
return transfer;
505512
}
506513

507514
const SyncContext = struct {
@@ -565,10 +572,18 @@ pub fn syncRequest(self: *Client, allocator: Allocator, req: Request) !SyncRespo
565572
r.done_callback = SyncContext.doneCallback;
566573
r.error_callback = SyncContext.errorCallback;
567574
r.shutdown_callback = SyncContext.shutdownCallback;
568-
try self.request(r, null);
575+
const transfer = try self.requestT(r, null);
569576

570577
while (sync_ctx.completion == .in_progress) {
571-
try self.tick(200, .sync_wait);
578+
self.tick(200, .sync_wait) catch |err| {
579+
if (sync_ctx.completion == .in_progress) {
580+
// tick failed for a reason unrelated to our transfer (likely OOM or
581+
// client disconnect). transfer.req.ctx points at &sync_ctx on this
582+
// stack — abort to sever that reference before we return
583+
transfer.abort(err);
584+
}
585+
return err;
586+
};
572587
}
573588

574589
switch (sync_ctx.completion) {

0 commit comments

Comments
 (0)