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,4 +1,5 @@ | |
| 1 | 1 | const std = @import("std"); | |
| 2 | + | const cross = @import("cross.zig"); | |
| 2 | 3 | ||
| 3 | 4 | pub var log_system = LogSystem{}; | |
| 4 | 5 |
| ... | ... | @@ -34,11 +35,17 @@ pub const LogSystem = struct { | |
| 34 | 35 | else => return err, | |
| 35 | 36 | }; | |
| 36 | 37 | ||
| 37 | - | const end_pos = try std.Io.File.length(file, self.io); | |
| 38 | - | var buf: [1]u8 = undefined; | |
| 39 | - | var w = std.Io.File.writer(file, self.io, &buf); | |
| 40 | - | try w.seekTo(end_pos); | |
| 41 | - | self.current_size = end_pos; | |
| 38 | + | // Use lseek(SEEK_END) instead of length() + seekTo() to avoid a | |
| 39 | + | // TOCTOU race: after fork() the parent may still write to the log | |
| 40 | + | // between our length() check and seekTo(), causing us to overwrite | |
| 41 | + | // recent parent entries. lseek(fd, 0, SEEK_END) is atomic — it | |
| 42 | + | // always positions at the true end of file at seek time. | |
| 43 | + | const new_pos = cross.c.lseek(file.handle, 0, cross.c.SEEK_END); | |
| 44 | + | if (new_pos == -1) { | |
| 45 | + | std.Io.File.close(file, self.io); | |
| 46 | + | return error.SeekFailed; | |
| 47 | + | } | |
| 48 | + | self.current_size = @as(u64, @intCast(new_pos)); | |
| 42 | 49 | self.file = file; | |
| 43 | 50 | } | |
| 44 | 51 |