lib/encoding/utf8: decoder offs i32->size, closing prev/next OOB (#70)
prev()'s walk-back decremented offs (i32) past 0 to -1 and returned `more`; a subsequent next() then passed the signed `-1 < len` guard and read d.src[-1] — a silent OOB decode of a garbage rune (no runtime bounds net). Hare's decoder.offs is `size`: the underflow wraps to SIZE_MAX so every `offs < len` guard exits safely (next returns more, not a rune). Change offs to size and spell prev's loop as the Hare-form `offs < len` guard; index sites take an i32 temp (ww's slice index is i32 and `[...]` reads ':' as the slice separator). No-runtime-net residual: remaining() would silently build a ptr-1/len+1 OOB view when called in the post-`more` state; guard it with a loud abort (caller contract: don't call after `more`). The offs type ripples into strings.ww's iterator<->decoder bridge (move/slice) — cast at the four sites, safe on the rune-return path where offs is in range. utf8/strings embed into all five selfhost combined.ww snapshots plus the smoke.combined.ww test amalgamation; all regen'd. utf8test gains prev_more_then_next_no_oob pinning the closed OOB.
This commit is contained in:
@@ -437,6 +437,32 @@ fn streq(a: str, b: str) bool = {
|
||||
};
|
||||
};
|
||||
|
||||
// #70: after prev() returns `more` it leaves offs out of range (SIZE_MAX,
|
||||
// via the unsigned underflow). A subsequent next() must see offs >= len
|
||||
// and return `more` — NOT read one byte before the buffer and decode a
|
||||
// garbage rune. The byte before the slice (buf[2] = 0x41 = 'A') is the
|
||||
// planted sentinel the old i32 offs=-1 path returned as rune 65.
|
||||
@test fn prev_more_then_next_no_oob() void = {
|
||||
let buf: [4]u8;
|
||||
buf[0] = 0x58u8; buf[1] = 0x59u8; buf[2] = 0x41u8; // 'A' sentinel
|
||||
buf[3] = 0xA0u8; // lone continuation
|
||||
let d: utf8.decoder = utf8.decode(buf[3:4]);
|
||||
match (utf8.next(&d)) {
|
||||
case let e: utf8.invalid => void;
|
||||
case => { abort(); };
|
||||
};
|
||||
match (utf8.prev(&d)) {
|
||||
case utf8.more => void;
|
||||
case => { abort(); };
|
||||
};
|
||||
match (utf8.next(&d)) {
|
||||
case utf8.more => void; // safe: not src[-1]'s 'A'
|
||||
case let r: rune => { abort(); };
|
||||
case utf8.done => { abort(); };
|
||||
case let e: utf8.invalid => { abort(); };
|
||||
};
|
||||
};
|
||||
|
||||
@test fn prev_incomplete_invalid() void = {
|
||||
let src: [2]u8;
|
||||
src[0] = 0xE3u8; src[1] = 0x81u8;
|
||||
|
||||
Reference in New Issue
Block a user