diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 01:31:19 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 01:35:12 -0300 |
| commit | 3f3c5981769168674b974f44e7e187dc688b83d6 (patch) | |
| tree | c2e9f22468870bc17ba4f981f2dcc1c9168fa190 | |
| parent | 0bc1ca8f9d3ece476ac469fc0a180793aab412df (diff) | |
| download | pardes-3f3c5981769168674b974f44e7e187dc688b83d6.tar.gz pardes-3f3c5981769168674b974f44e7e187dc688b83d6.zip | |
An event write-back refuses an origin not E, F, K or M, and a number past what a range holds, EINVAL with why
A record from origin Q was taken as any other, and an offset past
2^32 was clamped to the largest one and acted on. Both are now
refused before anything runs, the overflow as "bad number"; a range
past the end was and is EINVAL too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
| -rw-r--r-- | src/ninep/events.zig | 41 |
1 files changed, 34 insertions, 7 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig index a8c5022f..8fae6eab 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -661,6 +661,8 @@ const EventRecord = struct { origin: u8, action: Action, q0: u32, q1: u32, flag: const EventReader = struct { data: []const u8, i: usize = 0, + /// A number past what a range can hold: said as such, not clamped. + overflow: bool = false, fn next(er: *EventReader) ?EventRecord { if (er.i >= er.data.len) return null; @@ -670,8 +672,8 @@ const EventReader = struct { i += 1; const action = Action.fromChar(er.data[i]) orelse return null; i += 1; - const q0 = scanNumber(er.data, &i) orelse return null; - const q1 = scanNumber(er.data, &i) orelse return null; + const q0 = scanNumber(er.data, &i, &er.overflow) orelse return null; + const q1 = scanNumber(er.data, &i, &er.overflow) orelse return null; while (i < er.data.len and er.data[i] == ' ') i += 1; // acme takes only `origin action q0 q1`; the record whole, as read, // is taken too, so a click with no place in the text (one written @@ -679,8 +681,8 @@ const EventReader = struct { var text: ?[]const u8 = null; var flag: u32 = 0; if (i < er.data.len and er.data[i] != '\n') { - flag = scanNumber(er.data, &i) orelse return null; - const n = scanNumber(er.data, &i) orelse return null; + flag = scanNumber(er.data, &i, &er.overflow) orelse return null; + const n = scanNumber(er.data, &i, &er.overflow) orelse return null; if (i >= er.data.len or er.data[i] != ' ' or er.data.len - (i + 1) < n) return null; text = er.data[i + 1 ..][0..n]; i += 1 + n; @@ -691,12 +693,17 @@ const EventReader = struct { } }; -fn scanNumber(data: []const u8, i: *usize) ?u32 { +fn scanNumber(data: []const u8, i: *usize, overflow: *bool) ?u32 { while (i.* < data.len and data[i.*] == ' ') i.* += 1; const s = i.*; var n: u64 = 0; - while (i.* < data.len and data[i.*] >= '0' and data[i.*] <= '9') : (i.* += 1) - n = @min(n * 10 + (data[i.*] - '0'), std.math.maxInt(u32)); + while (i.* < data.len and data[i.*] >= '0' and data[i.*] <= '9') : (i.* += 1) { + n = n * 10 + (data[i.*] - '0'); + if (n > std.math.maxInt(u32)) { + overflow.* = true; + return null; + } + } if (i.* == s) return null; return @intCast(n); } @@ -744,6 +751,8 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { const tag = pane_files.tagOf(p, pane0); var check: EventReader = .{ .data = req.data }; while (check.next()) |r| { + // Who made it: E, F, K or M, as acme's records say. + if (std.mem.indexOfScalar(u8, "EFKM", r.origin) == null) return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": its origin is E, F, K or M"); switch (r.action) { .body_look, .tag_look, .body_exec, .tag_exec => {}, else => return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": only a look or exec (L l X x) is written back"), @@ -753,6 +762,7 @@ pub fn writeEvent(p: *Pardes, asked: Req, id: usize) Reply { // Well formed, but not a range of this text: say which. if (r.q1 > n) return tree.failText(req.tag, E.INVAL, if (r.action.onTag()) "range past end of tag" else "range past end of body"); } + if (check.overflow) return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": bad number: past what a range holds"); if (check.i != req.data.len) return tree.failText(req.tag, E.INVAL, tree.e_bad_event ++ ": a record is <origin><action><q0> <q1> <flag> <count> <text>, a line each"); } // What a record written back runs fails the write as a click written @@ -998,6 +1008,23 @@ test "one event reader at a time, and a writer still holds the pane" { _ = call(p, .{ .tag = 19, .op = .release, .node = event, .handle = next.reply.handle }); } +test "an event write-back refuses an origin not E, F, K or M, a number past a range's, and a range past the end, EINVAL" { + const p = try withFile(testing.allocator, "hello world\n"); + defer p.deinit(); + const event = Node.of(serialOf(p), .event); + const h = call(p, .{ .tag = 1, .op = .open, .node = event }).reply.handle; + defer _ = call(p, .{ .tag = 9, .op = .release, .node = event, .handle = h }); + for ([_][2][]const u8{ + .{ "QX0 1\n", "its origin is E, F, K or M" }, + .{ "MX18446744073709551616 1\n", "bad number" }, + .{ "MX99999 99999\n", "range past end of body" }, + }) |c| { + const r = wr(p, event, c[0]); + try testing.expectEqual(E.INVAL, r.errno()); + try testing.expect(std.mem.indexOf(u8, r.reply.ename, c[1]) != null); + } +} + test "an event record read a byte at a time arrives whole, in order, as a shell's read takes it" { const p = try withFile(testing.allocator, "x\n"); defer p.deinit(); |
