Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
27415b3fb8 fix: handle file truncation race in readFileWithHandleAndAllocator
When reading a file without a shared buffer, the code first reads up to
16384 bytes, then calls getEndPos() to get the file size. If the file
was truncated between these operations, the stat size could be smaller
than the initial read, causing an index out of bounds panic when trying
to slice buf[initial_read.len..].

Since stat can sometimes lie about file size, allocate an extra 16KB
buffer beyond what stat reports and always attempt to read. Shrink the
allocation afterward if we read less than allocated. Also move the
size == 0 check before allocation and add errdefer to free buf on error.

Fixes panic: index out of bounds: index 16384, len 9171

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 02:48:04 +00:00

View File

@@ -1267,29 +1267,43 @@ pub const FileSystem = struct {
} else initial_buf[0..0];
// Skip the extra file.stat() call when possible
const size = size_hint orelse (file.getEndPos().unwrap() catch |err| {
const stat_size = size_hint orelse (file.getEndPos().unwrap() catch |err| {
fs.readFileError(path, err);
return err;
});
debug("stat({f}) = {d}", .{ file.handle, size });
debug("stat({f}) = {d}", .{ file.handle, stat_size });
var buf = try allocator.alloc(u8, size + 1);
@memcpy(buf[0..initial_read.len], initial_read);
// Use the larger of stat_size and initial_read.len to handle cases where
// the file was truncated between the initial read and the stat call.
// Add extra 16KB buffer since stat can sometimes lie about file size.
const size = @max(stat_size, initial_read.len + 16384);
if (size == 0) {
return PathContentsPair{ .path = Path.init(path), .contents = "" };
}
var buf = try allocator.alloc(u8, size + 1);
errdefer allocator.free(buf);
@memcpy(buf[0..initial_read.len], initial_read);
// stick a zero at the end
buf[size] = 0;
const read_count = file.readAll(buf[initial_read.len..]).unwrap() catch |err| {
const read_count = (file.readAll(buf[initial_read.len..]).unwrap() catch |err| {
fs.readFileError(path, err);
return err;
};
});
file_contents = buf[0 .. read_count + initial_read.len];
debug("read({f}, {d}) = {d}", .{ file.handle, size, read_count });
// Shrink allocation if we read less than allocated
if (file_contents.len + 1 < buf.len) {
if (allocator.resize(buf, file_contents.len + 1)) {
buf = buf[0 .. file_contents.len + 1];
}
buf[file_contents.len] = 0;
}
if (strings.BOM.detect(file_contents)) |bom| {
debug("Convert {s} BOM", .{@tagName(bom)});
file_contents = try bom.removeAndConvertToUTF8AndFree(allocator, file_contents);