Commit 3697982

Eric Bower  ·  2026-08-02 12:30:28 -0400 EDT
parent 0843272
fix(log): race between checking the length of the file and writing to it
1 files changed,  +13, -6
+13, -6
 1@@ -1,4 +1,5 @@
 2 const std = @import("std");
 3+const cross = @import("cross.zig");
 4 
 5 pub var log_system = LogSystem{};
 6 
 7@@ -34,11 +35,17 @@ pub const LogSystem = struct {
 8             else => return err,
 9         };
10 
11-        const end_pos = try std.Io.File.length(file, self.io);
12-        var buf: [1]u8 = undefined;
13-        var w = std.Io.File.writer(file, self.io, &buf);
14-        try w.seekTo(end_pos);
15-        self.current_size = end_pos;
16+        // Use lseek(SEEK_END) instead of length() + seekTo() to avoid a
17+        // TOCTOU race: after fork() the parent may still write to the log
18+        // between our length() check and seekTo(), causing us to overwrite
19+        // recent parent entries. lseek(fd, 0, SEEK_END) is atomic — it
20+        // always positions at the true end of file at seek time.
21+        const new_pos = cross.c.lseek(file.handle, 0, cross.c.SEEK_END);
22+        if (new_pos == -1) {
23+            std.Io.File.close(file, self.io);
24+            return error.SeekFailed;
25+        }
26+        self.current_size = @as(u64, @intCast(new_pos));
27         self.file = file;
28     }
29 
30@@ -73,7 +80,7 @@ pub const LogSystem = struct {
31         const level_name = level.asText();
32 
33         const prefix_args = .{
34-            now,
35+            now.toSeconds(),
36             level_name,
37             scope_name,
38         };