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 {...@@ -1272,7 +1272,7 @@ pub const TypeErasedQueue = struct {
1272 if (elements.len == 0) return 0;1272 if (elements.len == 0) return 0;
1273 try q.mutex.lock(io);1273 try q.mutex.lock(io);
1274 defer q.mutex.unlock(io);1274 defer q.mutex.unlock(io);
1275 return putLocked(q, io, elements, min, false);1275 return q.putLocked(io, elements, min, false);
1276 }1276 }
12771277
1278 /// Same as `put` but cannot be canceled.1278 /// Same as `put` but cannot be canceled.
...@@ -1281,7 +1281,7 @@ pub const TypeErasedQueue = struct {...@@ -1281,7 +1281,7 @@ pub const TypeErasedQueue = struct {
1281 if (elements.len == 0) return 0;1281 if (elements.len == 0) return 0;
1282 q.mutex.lockUncancelable(io);1282 q.mutex.lockUncancelable(io);
1283 defer q.mutex.unlock(io);1283 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) {
1285 error.Canceled => unreachable,1285 error.Canceled => unreachable,
1286 };1286 };
1287 }1287 }
...@@ -1291,50 +1291,49 @@ pub const TypeErasedQueue = struct {...@@ -1291,50 +1291,49 @@ pub const TypeErasedQueue = struct {
1291 // queue is empty do we start populating the buffer.1291 // queue is empty do we start populating the buffer.
12921292
1293 var remaining = elements;1293 var remaining = elements;
1294 while (true) {1294 while (q.getters.popFirst()) |getter_node| {
1295 const getter: *Get = @alignCast(@fieldParentPtr("node", q.getters.popFirst() orelse break));1295 const getter: *Get = @alignCast(@fieldParentPtr("node", getter_node));
1296 const copy_len = @min(getter.remaining.len, remaining.len);1296 const copy_len = @min(getter.remaining.len, remaining.len);
1297 assert(copy_len > 0);
1297 @memcpy(getter.remaining[0..copy_len], remaining[0..copy_len]);1298 @memcpy(getter.remaining[0..copy_len], remaining[0..copy_len]);
1298 remaining = remaining[copy_len..];1299 remaining = remaining[copy_len..];
1299 getter.remaining = getter.remaining[copy_len..];1300 getter.remaining = getter.remaining[copy_len..];
1300 if (getter.remaining.len == 0) {1301 if (getter.remaining.len == 0) {
1301 getter.condition.signal(io);1302 getter.condition.signal(io);
1302 continue;1303 if (remaining.len > 0) continue;
1303 }1304 } else q.getters.prepend(getter_node);
1304 q.getters.prepend(&getter.node);
1305 assert(remaining.len == 0);1305 assert(remaining.len == 0);
1306 return elements.len;1306 return elements.len;
1307 }1307 }
13081308
1309 while (true) {1309 {
1310 {1310 const available = q.buffer[q.put_index..];
1311 const available = q.buffer[q.put_index..];1311 const copy_len = @min(available.len, remaining.len);
1312 const copy_len = @min(available.len, remaining.len);1312 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1313 @memcpy(available[0..copy_len], remaining[0..copy_len]);1313 remaining = remaining[copy_len..];
1314 remaining = remaining[copy_len..];1314 q.put_index += copy_len;
1315 q.put_index += copy_len;1315 if (remaining.len == 0) return elements.len;
1316 if (remaining.len == 0) return elements.len;1316 }
1317 }1317 {
1318 {1318 const available = q.buffer[0..q.get_index];
1319 const available = q.buffer[0..q.get_index];1319 const copy_len = @min(available.len, remaining.len);
1320 const copy_len = @min(available.len, remaining.len);1320 @memcpy(available[0..copy_len], remaining[0..copy_len]);
1321 @memcpy(available[0..copy_len], remaining[0..copy_len]);1321 remaining = remaining[copy_len..];
1322 remaining = remaining[copy_len..];1322 q.put_index = copy_len;
1323 q.put_index = copy_len;1323 if (remaining.len == 0) return elements.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;
1337 }1324 }
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;
1338 }1337 }
13391338
1340 pub fn get(q: *@This(), io: Io, buffer: []u8, min: usize) Cancelable!usize {1339 pub fn get(q: *@This(), io: Io, buffer: []u8, min: usize) Cancelable!usize {
...@@ -1342,7 +1341,7 @@ pub const TypeErasedQueue = struct {...@@ -1342,7 +1341,7 @@ pub const TypeErasedQueue = struct {
1342 if (buffer.len == 0) return 0;1341 if (buffer.len == 0) return 0;
1343 try q.mutex.lock(io);1342 try q.mutex.lock(io);
1344 defer q.mutex.unlock(io);1343 defer q.mutex.unlock(io);
1345 return getLocked(q, io, buffer, min, false);1344 return q.getLocked(io, buffer, min, false);
1346 }1345 }
13471346
1348 pub fn getUncancelable(q: *@This(), io: Io, buffer: []u8, min: usize) usize {1347 pub fn getUncancelable(q: *@This(), io: Io, buffer: []u8, min: usize) usize {
...@@ -1350,99 +1349,113 @@ pub const TypeErasedQueue = struct {...@@ -1350,99 +1349,113 @@ pub const TypeErasedQueue = struct {
1350 if (buffer.len == 0) return 0;1349 if (buffer.len == 0) return 0;
1351 q.mutex.lockUncancelable(io);1350 q.mutex.lockUncancelable(io);
1352 defer q.mutex.unlock(io);1351 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) {
1354 error.Canceled => unreachable,1353 error.Canceled => unreachable,
1355 };1354 };
1356 }1355 }
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 {
1359 // The ring buffer gets first priority, then data should come from any1358 // The ring buffer gets first priority, then data should come from any
1360 // queued putters, then finally the ring buffer should be filled with1359 // queued putters, then finally the ring buffer should be filled with
1361 // data from putters so they can be resumed.1360 // data from putters so they can be resumed.
13621361
1363 var remaining = buffer;1362 var remaining = buffer;
1364 while (true) {1363 if (q.get_index <= q.put_index) {
1365 if (q.get_index <= q.put_index) {1364 const available = q.buffer[q.get_index..q.put_index];
1366 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..];
1367 const copy_len = @min(available.len, remaining.len);1376 const copy_len = @min(available.len, remaining.len);
1368 @memcpy(remaining[0..copy_len], available[0..copy_len]);1377 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1369 q.get_index += copy_len;1378 q.get_index += copy_len;
1370 remaining = remaining[copy_len..];1379 remaining = remaining[copy_len..];
1371 if (remaining.len == 0) return fillRingBufferFromPutters(q, io, buffer.len);1380 if (remaining.len == 0) {
1372 } else {1381 q.fillRingBufferFromPutters(io);
1373 {1382 return buffer.len;
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);
1388 }1383 }
1389 }1384 }
1390 // Copy directly from putters into buffer.1385 {
1391 while (remaining.len > 0) {1386 const available = q.buffer[0..q.put_index];
1392 const putter: *Put = @alignCast(@fieldParentPtr("node", q.putters.popFirst() orelse break));1387 const copy_len = @min(available.len, remaining.len);
1393 const copy_len = @min(putter.remaining.len, remaining.len);1388 @memcpy(remaining[0..copy_len], available[0..copy_len]);
1394 @memcpy(remaining[0..copy_len], putter.remaining[0..copy_len]);1389 q.get_index = copy_len;
1395 putter.remaining = putter.remaining[copy_len..];
1396 remaining = remaining[copy_len..];1390 remaining = remaining[copy_len..];
1397 if (putter.remaining.len == 0) {1391 if (remaining.len == 0) {
1398 putter.condition.signal(io);1392 q.fillRingBufferFromPutters(io);
1399 } else {1393 return buffer.len;
1400 assert(remaining.len == 0);
1401 q.putters.prepend(&putter.node);
1402 return fillRingBufferFromPutters(q, io, buffer.len);
1403 }1394 }
1404 }1395 }
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;
1416 }1396 }
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;
1417 }1426 }
14181427
1419 /// Called when there is nonzero space available in the ring buffer and1428 /// Called when there is nonzero space available in the ring buffer and
1420 /// potentially putters waiting. The mutex is already held and the task is1429 /// potentially putters waiting. The mutex is already held and the task is
1421 /// to copy putter data to the ring buffer and signal any putters whose1430 /// to copy putter data to the ring buffer and signal any putters whose
1422 /// buffers been fully copied.1431 /// buffers been fully copied.
1423 fn fillRingBufferFromPutters(q: *TypeErasedQueue, io: Io, len: usize) usize {1432 fn fillRingBufferFromPutters(q: *TypeErasedQueue, io: Io) void {
1424 while (true) {1433 while (q.putters.popFirst()) |putter_node| {
1425 const putter: *Put = @alignCast(@fieldParentPtr("node", q.putters.popFirst() orelse return len));1434 const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node));
1426 const available = q.buffer[q.put_index..];1435 {
1427 const copy_len = @min(available.len, putter.remaining.len);1436 const available = q.buffer[q.put_index..];
1428 @memcpy(available[0..copy_len], putter.remaining[0..copy_len]);1437 const copy_len = @min(available.len, putter.remaining.len);
1429 putter.remaining = putter.remaining[copy_len..];1438 @memcpy(available[0..copy_len], putter.remaining[0..copy_len]);
1430 q.put_index += copy_len;1439 putter.remaining = putter.remaining[copy_len..];
1431 if (putter.remaining.len == 0) {1440 q.put_index += copy_len;
1432 putter.condition.signal(io);1441 if (putter.remaining.len == 0) {
1433 continue;1442 putter.condition.signal(io);
1443 continue;
1444 }
1434 }1445 }
1435 const second_available = q.buffer[0..q.get_index];1446 {
1436 const second_copy_len = @min(second_available.len, putter.remaining.len);1447 const available = q.buffer[0..q.get_index];
1437 @memcpy(second_available[0..second_copy_len], putter.remaining[0..second_copy_len]);1448 const copy_len = @min(available.len, putter.remaining.len);
1438 putter.remaining = putter.remaining[copy_len..];1449 @memcpy(available[0..copy_len], putter.remaining[0..copy_len]);
1439 q.put_index = copy_len;1450 putter.remaining = putter.remaining[copy_len..];
1440 if (putter.remaining.len == 0) {1451 q.put_index = copy_len;
1441 putter.condition.signal(io);1452 if (putter.remaining.len == 0) {
1442 continue;1453 putter.condition.signal(io);
1454 continue;
1455 }
1443 }1456 }
1444 q.putters.prepend(&putter.node);1457 q.putters.prepend(putter_node);
1445 return len;1458 break;
1446 }1459 }
1447 }1460 }
1448};1461};