summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 01:31:19 -0300
committerGabriel Schneider <[email protected]>2026-10-01 01:35:12 -0300
commit3f3c5981769168674b974f44e7e187dc688b83d6 (patch)
treec2e9f22468870bc17ba4f981f2dcc1c9168fa190 /src
parent0bc1ca8f9d3ece476ac469fc0a180793aab412df (diff)
downloadpardes-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]>
Diffstat (limited to 'src')
-rw-r--r--src/ninep/events.zig41
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();