mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-08-02 18:59:36 -04:00
uaf: Dupe blobs before using them, or else risk revokeObjectURL
When using a blob that might live through a JS call, dupe it so that any subsequent revokeObjectURL doesn't invalidate the memory.
This commit is contained in:
@@ -670,8 +670,11 @@ pub fn navigate(self: *Frame, request_url: [:0]const u8, opts: NavigateOpts) !vo
|
||||
};
|
||||
const parse_arena = try self.getArena(.medium, "Frame.parseBlob");
|
||||
defer self.releaseArena(parse_arena);
|
||||
// A script executed mid-parse can revoke the blob URL, letting GC
|
||||
// free the buffer under the parser; parse a copy.
|
||||
const html = try parse_arena.dupe(u8, blob._slice);
|
||||
var parser = Parser.init(parse_arena, self.document.asNode(), self, .{ .allow_declarative_shadow = true });
|
||||
parser.parse(blob._slice);
|
||||
parser.parse(html);
|
||||
} else {
|
||||
self.document.injectBlank(self) catch |err| {
|
||||
log.err(.browser, "inject blank", .{ .err = err });
|
||||
|
||||
@@ -483,3 +483,26 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="worker_from_blob_url_revoked_before_delivery" type=module>
|
||||
// Revoking right after construction must not invalidate the queued
|
||||
// script response: it's delivered on a later tick, after the blob's
|
||||
// last reference may be gone.
|
||||
{
|
||||
const state = await testing.async();
|
||||
const blob = new Blob(["onmessage = (e) => postMessage({ echo: e.data, from: 'revoked-blob-worker' });"], { type: 'text/javascript' });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
const worker = new Worker(blobUrl);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
|
||||
worker.onmessage = function(event) {
|
||||
state.resolve(event.data);
|
||||
};
|
||||
worker.postMessage({ greeting: 'still-works' });
|
||||
|
||||
await state.done((response) => {
|
||||
testing.expectEqual('still-works', response.echo.greeting);
|
||||
testing.expectEqual('revoked-blob-worker', response.from);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -199,8 +199,10 @@ fn readInternal(self: *FileReader, blob: *Blob, read_type: ReadType) !void {
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform the read (synchronous since data is in memory)
|
||||
const data = blob._slice;
|
||||
// Perform the read (synchronous since data is in memory). _result
|
||||
// outlives this call and the blob can be GC'd before JS reads it,
|
||||
// so the result must not borrow the blob's memory.
|
||||
const data = try self._arena.dupe(u8, blob._slice);
|
||||
const size = data.len;
|
||||
try self.dispatch(.progress, .{ .loaded = size, .total = size }, exec);
|
||||
if (self._aborted) {
|
||||
|
||||
@@ -2959,8 +2959,9 @@ const Synthetic = struct {
|
||||
const owner = transfer.owner orelse return error.BlobNotFound;
|
||||
const blob_urls = owner.blob_urls orelse return error.BlobNotFound;
|
||||
const blob = blob_urls.get(url) orelse return error.BlobNotFound;
|
||||
content_type = blob._mime;
|
||||
body = blob._slice;
|
||||
// blob can be removed by the time we run, dupe it.
|
||||
content_type = try arena.dupe(u8, blob._mime);
|
||||
body = try arena.dupe(u8, blob._slice);
|
||||
}
|
||||
|
||||
const has_content_type = content_type.len > 0;
|
||||
|
||||
Reference in New Issue
Block a user