Skip to content

Fix libyaml handling of truncated UTF-8 sequences - #359

Open
ccoVeille wants to merge 1 commit into
yaml:mainfrom
ccoVeille:fix-unicode-error
Open

ccoVeille wants to merge 1 commit into
yaml:mainfrom
ccoVeille:fix-unicode-error

Conversation

@ccoVeille

Copy link
Copy Markdown
Contributor

Add security against panic with incomplete multi-byte UTF-8 input
so malformed data returns an error instead of panicking.

Fixes #358

Copilot AI review requested due to automatic review settings May 25, 2026 16:13
@ccoVeille

Copy link
Copy Markdown
Contributor Author

cc @TomWright if you are curious

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.go and expand predicate test vectors in scanner.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.

Comment thread internal/libyaml/emitter_test.go
Comment thread internal/libyaml/scanner.go
Comment thread internal/libyaml/scanner.go Outdated
Comment thread internal/libyaml/scanner.go Outdated
Comment thread internal/libyaml/emitter_test.go
Comment thread internal/libyaml/emitter.go
Comment thread internal/libyaml/emitter_test.go Outdated
@ccoVeille
ccoVeille force-pushed the fix-unicode-error branch from 556a6fd to f4d8fd6 Compare May 25, 2026 16:51
Comment thread internal/libyaml/scanner.go
@ingydotnet
ingydotnet force-pushed the fix-unicode-error branch from f4d8fd6 to d3beb6d Compare May 27, 2026 19:48
Add security against panic with incomplete multi-byte UTF-8 input
so malformed data returns an error instead of panicking.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Comment on lines 629 to +632
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)
Comment on lines 751 to 753
// "? "
case '?':
return isBlankOrZero(b, i+1)

@ccoVeille ccoVeille Jun 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm amused by development by the fact it's always the same.

https://youtu.be/AbSehcT19u0

  • 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.

Comment on lines +58 to +74
// 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)
})
Comment on lines +58 to +63
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},
} {
Comment on lines +99 to +100
t.Run(name, func(t *testing.T) {
t.Run("double-quoted scalar style", func(t *testing.T) {
Comment thread internal/libyaml/scanner.go
@colinjlacy

Copy link
Copy Markdown
Contributor

@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?

@ccoVeille

Copy link
Copy Markdown
Contributor Author

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) ||

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Potential panic in emitter with malformed UTF-8

4 participants