authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-10-30 16:02:25-04:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2025-10-30 16:53:05-04:00
logd000574380baba7b63fdcde713b51f8318509474
tree648383832fbb88146e51ffac22695822f01fc208
parent0ca4df540f96615e3ea794b307381ba508d35f37

Io: fix some horrible data races and UAFs caused by `Condition` misuse


1 files changed, 115 insertions(+), 102 deletions(-)

lib/std/Io.zig+115-102
......@@ -1272,7 +1272,7 @@ pub const TypeErasedQueue = struct {
12721272 if (elements.len == 0) return 0;
12731273 try q.mutex.lock(io);
12741274 defer q.mutex.unlock(io);
1275 return putLocked(q, io, elements, min, false);
1275 return q.putLocked(io, elements, min, false);
12761276 }
12771277
12781278 /// Same as `put` but cannot be canceled.
......@@ -1281,7 +1281,7 @@ pub const TypeErasedQueue = struct {
12811281 if (elements.len == 0) return 0;
12821282 q.mutex.lockUncancelable(io);
12831283 defer q.mutex.unlock(io);
1284 return putLocked(q, io, elements, min, true) catch |err| switch (err) {
1284 return q.putLocked(io, elements, min, true) catch |err| switch (err) {
12851285 error.Canceled => unreachable,
12861286 };
12871287 }
......@@ -1291,50 +1291,49 @@ pub const TypeErasedQueue = struct {
12911291 // queue is empty do we start populating the buffer.
12921292
12931293 var remaining = elements;
1294 while (true) {
1295 const getter: *Get = @alignCast(@fieldParentPtr("node", q.getters.popFirst() orelse break));
1294 while (q.getters.popFirst()) |getter_node| {
1295 const getter: *Get = @alignCast(@fieldParentPtr("node", getter_node));
12961296 const copy_len = @min(getter.remaining.len, remaining.len);
1297 assert(copy_len > 0);
12971298 @memcpy(getter.remaining[0..copy_len], remaining[0..copy_len]);
12981299 remaining = remaining[copy_len..];
12991300 getter.remaining = getter.remaining[copy_len..];
13001301 if (getter.remaining.len == 0) {
13011302 getter.condition.signal(io);
1302 continue;
1303 }
1304 q.getters.prepend(&getter.node);
1303 if (remaining.len > 0) continue;
1304 } else q.getters.prepend(getter_node);
13051305 assert(remaining.len == 0);
13061306 return elements.len;
13071307 }
13081308
1309 while (true) {
1310 {
1311 const available = q.buffer[q.put_index..];
1312 const copy_len = @min(available.len, remaining.len);
1313 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1314 remaining = remaining[copy_len..];
1315 q.put_index += copy_len;
1316 if (remaining.len == 0) return elements.len;
1317 }
1318 {
1319 const available = q.buffer[0..q.get_index];
1320 const copy_len = @min(available.len, remaining.len);
1321 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1322 remaining = remaining[copy_len..];
1323 q.put_index = copy_len;
1324 if (remaining.len == 0) return elements.len;
1325 }
1326
1327 const total_filled = elements.len - remaining.len;
1328 if (total_filled >= min) return total_filled;
1329
1330 var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} };
1331 q.putters.append(&pending.node);
1332 if (uncancelable)
1333 pending.condition.waitUncancelable(io, &q.mutex)
1334 else
1335 try pending.condition.wait(io, &q.mutex);
1336 remaining = pending.remaining;
1309 {
1310 const available = q.buffer[q.put_index..];
1311 const copy_len = @min(available.len, remaining.len);
1312 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1313 remaining = remaining[copy_len..];
1314 q.put_index += copy_len;
1315 if (remaining.len == 0) return elements.len;
1316 }
1317 {
1318 const available = q.buffer[0..q.get_index];
1319 const copy_len = @min(available.len, remaining.len);
1320 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1321 remaining = remaining[copy_len..];
1322 q.put_index = copy_len;
1323 if (remaining.len == 0) return elements.len;
13371324 }
1325
1326 const total_filled = elements.len - remaining.len;
1327 if (total_filled >= min) return total_filled;
1328
1329 var pending: Put = .{ .remaining = remaining, .condition = .{}, .node = .{} };
1330 q.putters.append(&pending.node);
1331 defer if (pending.remaining.len > 0) q.putters.remove(&pending.node);
1332 while (pending.remaining.len > 0) if (uncancelable)
1333 pending.condition.waitUncancelable(io, &q.mutex)
1334 else
1335 try pending.condition.wait(io, &q.mutex);
1336 return elements.len;
13381337 }
13391338
13401339 pub fn get(q: *@This(), io: Io, buffer: []u8, min: usize) Cancelable!usize {
......@@ -1342,7 +1341,7 @@ pub const TypeErasedQueue = struct {
13421341 if (buffer.len == 0) return 0;
13431342 try q.mutex.lock(io);
13441343 defer q.mutex.unlock(io);
1345 return getLocked(q, io, buffer, min, false);
1344 return q.getLocked(io, buffer, min, false);
13461345 }
13471346
13481347 pub fn getUncancelable(q: *@This(), io: Io, buffer: []u8, min: usize) usize {
......@@ -1350,99 +1349,113 @@ pub const TypeErasedQueue = struct {
13501349 if (buffer.len == 0) return 0;
13511350 q.mutex.lockUncancelable(io);
13521351 defer q.mutex.unlock(io);
1353 return getLocked(q, io, buffer, min, true) catch |err| switch (err) {
1352 return q.getLocked(io, buffer, min, true) catch |err| switch (err) {
13541353 error.Canceled => unreachable,
13551354 };
13561355 }
13571356
1358 pub fn getLocked(q: *@This(), io: Io, buffer: []u8, min: usize, uncancelable: bool) Cancelable!usize {
1357 fn getLocked(q: *@This(), io: Io, buffer: []u8, min: usize, uncancelable: bool) Cancelable!usize {
13591358 // The ring buffer gets first priority, then data should come from any
13601359 // queued putters, then finally the ring buffer should be filled with
13611360 // data from putters so they can be resumed.
13621361
13631362 var remaining = buffer;
1364 while (true) {
1365 if (q.get_index <= q.put_index) {
1366 const available = q.buffer[q.get_index..q.put_index];
1363 if (q.get_index <= q.put_index) {
1364 const available = q.buffer[q.get_index..q.put_index];
1365 const copy_len = @min(available.len, remaining.len);
1366 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1367 q.get_index += copy_len;
1368 remaining = remaining[copy_len..];
1369 if (remaining.len == 0) {
1370 q.fillRingBufferFromPutters(io);
1371 return buffer.len;
1372 }
1373 } else {
1374 {
1375 const available = q.buffer[q.get_index..];
13671376 const copy_len = @min(available.len, remaining.len);
13681377 @memcpy(remaining[0..copy_len], available[0..copy_len]);
13691378 q.get_index += copy_len;
13701379 remaining = remaining[copy_len..];
1371 if (remaining.len == 0) return fillRingBufferFromPutters(q, io, buffer.len);
1372 } else {
1373 {
1374 const available = q.buffer[q.get_index..];
1375 const copy_len = @min(available.len, remaining.len);
1376 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1377 q.get_index += copy_len;
1378 remaining = remaining[copy_len..];
1379 if (remaining.len == 0) return fillRingBufferFromPutters(q, io, buffer.len);
1380 }
1381 {
1382 const available = q.buffer[0..q.put_index];
1383 const copy_len = @min(available.len, remaining.len);
1384 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1385 q.get_index = copy_len;
1386 remaining = remaining[copy_len..];
1387 if (remaining.len == 0) return fillRingBufferFromPutters(q, io, buffer.len);
1380 if (remaining.len == 0) {
1381 q.fillRingBufferFromPutters(io);
1382 return buffer.len;
13881383 }
13891384 }
1390 // Copy directly from putters into buffer.
1391 while (remaining.len > 0) {
1392 const putter: *Put = @alignCast(@fieldParentPtr("node", q.putters.popFirst() orelse break));
1393 const copy_len = @min(putter.remaining.len, remaining.len);
1394 @memcpy(remaining[0..copy_len], putter.remaining[0..copy_len]);
1395 putter.remaining = putter.remaining[copy_len..];
1385 {
1386 const available = q.buffer[0..q.put_index];
1387 const copy_len = @min(available.len, remaining.len);
1388 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1389 q.get_index = copy_len;
13961390 remaining = remaining[copy_len..];
1397 if (putter.remaining.len == 0) {
1398 putter.condition.signal(io);
1399 } else {
1400 assert(remaining.len == 0);
1401 q.putters.prepend(&putter.node);
1402 return fillRingBufferFromPutters(q, io, buffer.len);
1391 if (remaining.len == 0) {
1392 q.fillRingBufferFromPutters(io);
1393 return buffer.len;
14031394 }
14041395 }
1405 // Both ring buffer and putters queue is empty.
1406 const total_filled = buffer.len - remaining.len;
1407 if (total_filled >= min) return total_filled;
1408
1409 var pending: Get = .{ .remaining = remaining, .condition = .{}, .node = .{} };
1410 q.getters.append(&pending.node);
1411 if (uncancelable)
1412 pending.condition.waitUncancelable(io, &q.mutex)
1413 else
1414 try pending.condition.wait(io, &q.mutex);
1415 remaining = pending.remaining;
14161396 }
1397 // Copy directly from putters into buffer.
1398 while (q.putters.popFirst()) |putter_node| {
1399 const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node));
1400 const copy_len = @min(putter.remaining.len, remaining.len);
1401 assert(copy_len > 0);
1402 @memcpy(remaining[0..copy_len], putter.remaining[0..copy_len]);
1403 putter.remaining = putter.remaining[copy_len..];
1404 remaining = remaining[copy_len..];
1405 if (putter.remaining.len == 0) {
1406 putter.condition.signal(io);
1407 if (remaining.len > 0) continue;
1408 } else q.putters.prepend(putter_node);
1409 assert(remaining.len == 0);
1410 q.fillRingBufferFromPutters(io);
1411 return buffer.len;
1412 }
1413 // Both ring buffer and putters queue is empty.
1414 const total_filled = buffer.len - remaining.len;
1415 if (total_filled >= min) return total_filled;
1416
1417 var pending: Get = .{ .remaining = remaining, .condition = .{}, .node = .{} };
1418 q.getters.append(&pending.node);
1419 defer if (pending.remaining.len > 0) q.getters.remove(&pending.node);
1420 while (pending.remaining.len > 0) if (uncancelable)
1421 pending.condition.waitUncancelable(io, &q.mutex)
1422 else
1423 try pending.condition.wait(io, &q.mutex);
1424 q.fillRingBufferFromPutters(io);
1425 return buffer.len;
14171426 }
14181427
14191428 /// Called when there is nonzero space available in the ring buffer and
14201429 /// potentially putters waiting. The mutex is already held and the task is
14211430 /// to copy putter data to the ring buffer and signal any putters whose
14221431 /// buffers been fully copied.
1423 fn fillRingBufferFromPutters(q: *TypeErasedQueue, io: Io, len: usize) usize {
1424 while (true) {
1425 const putter: *Put = @alignCast(@fieldParentPtr("node", q.putters.popFirst() orelse return len));
1426 const available = q.buffer[q.put_index..];
1427 const copy_len = @min(available.len, putter.remaining.len);
1428 @memcpy(available[0..copy_len], putter.remaining[0..copy_len]);
1429 putter.remaining = putter.remaining[copy_len..];
1430 q.put_index += copy_len;
1431 if (putter.remaining.len == 0) {
1432 putter.condition.signal(io);
1433 continue;
1432 fn fillRingBufferFromPutters(q: *TypeErasedQueue, io: Io) void {
1433 while (q.putters.popFirst()) |putter_node| {
1434 const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node));
1435 {
1436 const available = q.buffer[q.put_index..];
1437 const copy_len = @min(available.len, putter.remaining.len);
1438 @memcpy(available[0..copy_len], putter.remaining[0..copy_len]);
1439 putter.remaining = putter.remaining[copy_len..];
1440 q.put_index += copy_len;
1441 if (putter.remaining.len == 0) {
1442 putter.condition.signal(io);
1443 continue;
1444 }
14341445 }
1435 const second_available = q.buffer[0..q.get_index];
1436 const second_copy_len = @min(second_available.len, putter.remaining.len);
1437 @memcpy(second_available[0..second_copy_len], putter.remaining[0..second_copy_len]);
1438 putter.remaining = putter.remaining[copy_len..];
1439 q.put_index = copy_len;
1440 if (putter.remaining.len == 0) {
1441 putter.condition.signal(io);
1442 continue;
1446 {
1447 const available = q.buffer[0..q.get_index];
1448 const copy_len = @min(available.len, putter.remaining.len);
1449 @memcpy(available[0..copy_len], putter.remaining[0..copy_len]);
1450 putter.remaining = putter.remaining[copy_len..];
1451 q.put_index = copy_len;
1452 if (putter.remaining.len == 0) {
1453 putter.condition.signal(io);
1454 continue;
1455 }
14431456 }
1444 q.putters.prepend(&putter.node);
1445 return len;
1457 q.putters.prepend(putter_node);
1458 break;
14461459 }
14471460 }
14481461};