Conversation
|
cc @TomWright if you are curious |
There was a problem hiding this comment.
Pull request overview
This PR aims to harden the internal libyaml scanner/emitter against malformed or truncated UTF-8 so that invalid input returns an error instead of panicking (Fixes #358).
Changes:
- Add predicate-side bounds handling for UTF-8 lookahead in
internal/libyaml/scanner.goand expand predicate test vectors inscanner.yaml. - Add an incomplete UTF-8 sequence check in the emitter’s double-quoted scalar writer to return a clean error instead of panicking.
- Add new scanner/emitter tests intended to validate “no panic” behavior on truncated UTF-8.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/libyaml/testdata/scanner.yaml | Adds new predicate test cases for truncated/edge UTF-8 sequences and adjusts some existing predicate inputs. |
| internal/libyaml/scanner.go | Introduces byteAt and refactors several predicates to avoid out-of-bounds reads. |
| internal/libyaml/scanner_test.go | Adds targeted tests to ensure malformed UTF-8 at EOF and predicate calls do not panic. |
| internal/libyaml/emitter.go | Adds a length guard for truncated UTF-8 sequences while escaping in double-quoted scalars. |
| internal/libyaml/emitter_test.go | Adds emitter tests intended to ensure malformed UTF-8 scalars don’t panic and return errors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
556a6fd to
f4d8fd6
Compare
f4d8fd6 to
d3beb6d
Compare
Add security against panic with incomplete multi-byte UTF-8 input so malformed data returns an error instead of panicking.
d3beb6d to
3034a7c
Compare
| func isPrintable(b []byte, i int) bool { | ||
| return ((b[i] == 0x0A) || // . == #x0A | ||
| (b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E | ||
| (b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF | ||
| (b[i] > 0xC2 && b[i] < 0xED) || | ||
| (b[i] == 0xED && b[i+1] < 0xA0) || | ||
| (b[i] == 0xEE) || | ||
| (b[i] == 0xEF && // #xE000 <= . <= #xFFFD | ||
| !(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF | ||
| !(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF)))) | ||
| c0 := b[i] | ||
| c1 := byteAt(b, i+1) | ||
| c2 := byteAt(b, i+2) |
| // "? " | ||
| case '?': | ||
| return isBlankOrZero(b, i+1) |
There was a problem hiding this comment.
I'm amused by development by the fact it's always the same.
- You take something that is supposed to work.
- You find one small bug, you fix it.
- And then other issues appear all around 😅🤣
At least Copilot helps here. I'm unsure a human would have noticed BT reviewing the PR, as this code was unchanged.
| // isPrintable should not panic on truncated UTF-8 sequences | ||
| notPanic("isPrintable with 0xF0", func() { | ||
| _ = isPrintable([]byte{0xF0}, 0) | ||
| }) | ||
|
|
||
| // Test other predicates with truncated sequences | ||
| notPanic("isLineBreak with 0xC2", func() { | ||
| _ = isLineBreak([]byte{0xC2}, 0) | ||
| }) | ||
|
|
||
| notPanic("isBOM with truncated", func() { | ||
| _ = isBOM([]byte{0xEF, 0xBB}, 0) | ||
| }) | ||
|
|
||
| notPanic("isEndOfScalarInFlowContentChar", func() { | ||
| _ = isEndOfScalarInFlowContentChar([]byte{':'}, 0) | ||
| }) |
| for name, malformed := range map[string][]byte{ | ||
| "Incomplete 2-byte UTF-8 sequence": {0xC2}, | ||
| "Incomplete 3-byte UTF-8 sequence": {0xEF}, | ||
| "Incomplete 4-byte UTF-8 sequence": {0xF0}, | ||
| "truncated BOM sequence": {0xEF, 0xBB}, | ||
| } { |
| t.Run(name, func(t *testing.T) { | ||
| t.Run("double-quoted scalar style", func(t *testing.T) { |
|
@ccoVeille I'm definitely not an expert on UTF-8 sequences, but the suggestions made by copilot regarding additional 3-byte sequences seem legit. Do you want to handle those too? |
|
While #422 was AI slop, the approach of using this is interesting // remaining reports whether b[i:] holds at least n bytes.
func remaining(b []byte, i, n int) bool {
return i >= 0 && n >= 0 && i+n <= len(b)
}For example - (b[i] == 0xED && b[i+1] < 0xA0) ||
+ (b[i] == 0xED && remaining(b, i, 2) && b[i+1] < 0xA0) || |
Add security against panic with incomplete multi-byte UTF-8 input
so malformed data returns an error instead of panicking.
Fixes #358