mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-21 20:24:42 +00:00
Fix use-after-free with certain CDP scripts
Origins were introduced to group memory/data that can be owned by multiple frames (on the same origin). There's a general idea that the initial "opaque" origin is very transient and should get replaced before any actual JavaScript is executed (because the real origin is setup as soon as we get the header from the response, long before we execute any script). But...with CDP, this guarantee doesn't hold There's nothing stop a CDP script from executing javascript at any point, including while the main page is still being loaded. This can result on allocations made on the opaque origin which is promptly discarded. To solve this, this commit introduced origin takeover. Rather than just transferring any data from one origin (the opaque) to the new one and then deinit' the opaque one (which is what results in user-after-free), the new origin simply maintains a list of opaque origins it has "taken-over"and is responsible for freeing it (in its own deinit). This ensures that any allocation made in the opaque origin remain valid.
This commit is contained in:
@@ -167,12 +167,11 @@ pub fn setOrigin(self: *Context, key: ?[]const u8) !void {
|
|||||||
const env = self.env;
|
const env = self.env;
|
||||||
const isolate = env.isolate;
|
const isolate = env.isolate;
|
||||||
|
|
||||||
|
lp.assert(self.origin.rc == 1, "Ref opaque origin", .{ .rc = self.origin.rc });
|
||||||
|
|
||||||
const origin = try self.session.getOrCreateOrigin(key);
|
const origin = try self.session.getOrCreateOrigin(key);
|
||||||
errdefer self.session.releaseOrigin(origin);
|
errdefer self.session.releaseOrigin(origin);
|
||||||
|
try origin.takeover(self.origin);
|
||||||
try self.origin.transferTo(origin);
|
|
||||||
lp.assert(self.origin.rc == 1, "Ref opaque origin", .{ .rc = self.origin.rc });
|
|
||||||
self.origin.deinit(env.app);
|
|
||||||
|
|
||||||
self.origin = origin;
|
self.origin = origin;
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ temps: std.AutoHashMapUnmanaged(usize, v8.Global) = .empty,
|
|||||||
// if v8 hasn't called the finalizer directly itself.
|
// if v8 hasn't called the finalizer directly itself.
|
||||||
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *FinalizerCallback) = .empty,
|
finalizer_callbacks: std.AutoHashMapUnmanaged(usize, *FinalizerCallback) = .empty,
|
||||||
|
|
||||||
|
taken_over: std.ArrayList(*Origin),
|
||||||
|
|
||||||
pub fn init(app: *App, isolate: js.Isolate, key: []const u8) !*Origin {
|
pub fn init(app: *App, isolate: js.Isolate, key: []const u8) !*Origin {
|
||||||
const arena = try app.arena_pool.acquire();
|
const arena = try app.arena_pool.acquire();
|
||||||
errdefer app.arena_pool.release(arena);
|
errdefer app.arena_pool.release(arena);
|
||||||
@@ -86,14 +88,19 @@ pub fn init(app: *App, isolate: js.Isolate, key: []const u8) !*Origin {
|
|||||||
.rc = 1,
|
.rc = 1,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.key = owned_key,
|
.key = owned_key,
|
||||||
.globals = .empty,
|
|
||||||
.temps = .empty,
|
.temps = .empty,
|
||||||
|
.globals = .empty,
|
||||||
|
.taken_over = .empty,
|
||||||
.security_token = token_global,
|
.security_token = token_global,
|
||||||
};
|
};
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Origin, app: *App) void {
|
pub fn deinit(self: *Origin, app: *App) void {
|
||||||
|
for (self.taken_over.items) |o| {
|
||||||
|
o.deinit(app);
|
||||||
|
}
|
||||||
|
|
||||||
// Call finalizers before releasing anything
|
// Call finalizers before releasing anything
|
||||||
{
|
{
|
||||||
var it = self.finalizer_callbacks.valueIterator();
|
var it = self.finalizer_callbacks.valueIterator();
|
||||||
@@ -196,42 +203,44 @@ pub fn createFinalizerCallback(
|
|||||||
return fc;
|
return fc;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transferTo(self: *Origin, dest: *Origin) !void {
|
pub fn takeover(self: *Origin, original: *Origin) !void {
|
||||||
const arena = dest.arena;
|
const arena = self.arena;
|
||||||
|
|
||||||
try dest.globals.ensureUnusedCapacity(arena, self.globals.items.len);
|
try self.globals.ensureUnusedCapacity(arena, self.globals.items.len);
|
||||||
for (self.globals.items) |obj| {
|
for (original.globals.items) |obj| {
|
||||||
dest.globals.appendAssumeCapacity(obj);
|
self.globals.appendAssumeCapacity(obj);
|
||||||
}
|
}
|
||||||
self.globals.clearRetainingCapacity();
|
original.globals.clearRetainingCapacity();
|
||||||
|
|
||||||
{
|
{
|
||||||
try dest.temps.ensureUnusedCapacity(arena, self.temps.count());
|
try self.temps.ensureUnusedCapacity(arena, original.temps.count());
|
||||||
var it = self.temps.iterator();
|
var it = original.temps.iterator();
|
||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
try dest.temps.put(arena, kv.key_ptr.*, kv.value_ptr.*);
|
try self.temps.put(arena, kv.key_ptr.*, kv.value_ptr.*);
|
||||||
}
|
}
|
||||||
self.temps.clearRetainingCapacity();
|
original.temps.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
try dest.finalizer_callbacks.ensureUnusedCapacity(arena, self.finalizer_callbacks.count());
|
try self.finalizer_callbacks.ensureUnusedCapacity(arena, original.finalizer_callbacks.count());
|
||||||
var it = self.finalizer_callbacks.iterator();
|
var it = original.finalizer_callbacks.iterator();
|
||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
kv.value_ptr.*.origin = dest;
|
kv.value_ptr.*.origin = self;
|
||||||
try dest.finalizer_callbacks.put(arena, kv.key_ptr.*, kv.value_ptr.*);
|
try self.finalizer_callbacks.put(arena, kv.key_ptr.*, kv.value_ptr.*);
|
||||||
}
|
}
|
||||||
self.finalizer_callbacks.clearRetainingCapacity();
|
original.finalizer_callbacks.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
try dest.identity_map.ensureUnusedCapacity(arena, self.identity_map.count());
|
try self.identity_map.ensureUnusedCapacity(arena, original.identity_map.count());
|
||||||
var it = self.identity_map.iterator();
|
var it = original.identity_map.iterator();
|
||||||
while (it.next()) |kv| {
|
while (it.next()) |kv| {
|
||||||
try dest.identity_map.put(arena, kv.key_ptr.*, kv.value_ptr.*);
|
try self.identity_map.put(arena, kv.key_ptr.*, kv.value_ptr.*);
|
||||||
}
|
}
|
||||||
self.identity_map.clearRetainingCapacity();
|
original.identity_map.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try self.taken_over.append(self.arena, original);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A type that has a finalizer can have its finalizer called one of two ways.
|
// A type that has a finalizer can have its finalizer called one of two ways.
|
||||||
|
|||||||
Reference in New Issue
Block a user