unicode/norm: avoid infinite loop on invalid input Invalid characters are given a Properties with a size of 0. The nextComposed function can enter an infinite loop when encountering an invalid character, since it advances its input by the (possibly 0) character size. Rather than finding every place which might assume characters have a non-zero size, change compInfo to return a size-1 Properties for invalid characters and use the property flags to record validity. Fixes golang/go#80142 Change-Id: Ie0791faefeddc1e8f671b0ed73f29e906a6a6964 Reviewed-on: https://go-review.googlesource.com/c/text/+/794100 LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Neal Patel <neal@golang.org> Reviewed-by: Neal Patel <nealpatel@google.com>
diff --git a/unicode/norm/forminfo.go b/unicode/norm/forminfo.go index f3a234e..b3cf5d9 100644 --- a/unicode/norm/forminfo.go +++ b/unicode/norm/forminfo.go
@@ -121,8 +121,12 @@ // // When all 6 bits are zero, the character is inert, meaning it is never // influenced by normalization. +// +// We set flags to 0x80 (high bit 7 unused in quick check data) to indicate an invalid rune. type qcInfo uint8 +func (p Properties) isInvalid() bool { return p.flags == 0x80 } + func (p Properties) isYesC() bool { return p.flags&0x10 == 0 } func (p Properties) isYesD() bool { return p.flags&0x4 == 0 } @@ -247,6 +251,9 @@ // to a Properties. See the comment at the top of the file // for more information on the format. func compInfo(v uint16, sz int) Properties { + if sz == 0 { + return Properties{flags: 0x80, size: 1} + } if v == 0 { return Properties{size: uint8(sz)} } else if v >= 0x8000 { @@ -254,7 +261,7 @@ size: uint8(sz), ccc: uint8(v), tccc: uint8(v), - flags: qcInfo(v >> 8), + flags: qcInfo(v>>8) & 0x3f, } if p.ccc > 0 || p.combinesBackward() { p.nLead = uint8(p.flags & 0x3)
diff --git a/unicode/norm/iter.go b/unicode/norm/iter.go index 417c6b2..3cc0592 100644 --- a/unicode/norm/iter.go +++ b/unicode/norm/iter.go
@@ -376,16 +376,12 @@ goto doNorm } prevCC = i.info.tccc - sz := int(i.info.size) - if sz == 0 { - sz = 1 // illegal rune: copy byte-by-byte - } - p := outp + sz + p := outp + int(i.info.size) if p > len(i.buf) { break } outp = p - i.p += sz + i.p += int(i.info.size) if i.p >= i.rb.nsrc { i.setDone() break
diff --git a/unicode/norm/normalize.go b/unicode/norm/normalize.go index 4747ad0..60b1511 100644 --- a/unicode/norm/normalize.go +++ b/unicode/norm/normalize.go
@@ -148,7 +148,7 @@ // patched buffer and whether the decomposition is still in progress. func patchTail(rb *reorderBuffer) bool { info, p := lastRuneStart(&rb.f, rb.out) - if p == -1 || info.size == 0 { + if p == -1 || info.isInvalid() { return true } end := p + int(info.size) @@ -225,7 +225,7 @@ } fd := &rb.f if doMerge { - var info Properties + info := Properties{flags: 0x80, size: 1} // invalid rune if p < n { info = fd.info(src, p) if !info.BoundaryBefore() || info.nLeadingNonStarters() > 0 { @@ -235,7 +235,7 @@ p = decomposeSegment(rb, p, true) } } - if info.size == 0 { + if info.isInvalid() { rb.doFlush() // Append incomplete UTF-8 encoding. return src.appendSlice(rb.out, p, n) @@ -314,7 +314,7 @@ continue } info := f.info(src, i) - if info.size == 0 { + if info.isInvalid() { if atEOF { // include incomplete runes return n, true @@ -379,7 +379,7 @@ // CGJ insertion points correctly. Luckily it doesn't have to. for { info := fd.info(src, i) - if info.size == 0 { + if info.isInvalid() { return -1 } if s := ss.next(info); s != ssSuccess { @@ -424,7 +424,7 @@ } fd := formTable[f] info := fd.info(src, 0) - if info.size == 0 { + if info.isInvalid() { if atEOF { return 1 } @@ -435,7 +435,7 @@ for i := int(info.size); i < nsrc; i += int(info.size) { info = fd.info(src, i) - if info.size == 0 { + if info.isInvalid() { if atEOF { return i } @@ -465,7 +465,7 @@ if p == -1 { return -1 } - if info.size == 0 { // ends with incomplete rune + if info.isInvalid() { // ends with incomplete rune if p == 0 { // starts with incomplete rune return -1 } @@ -504,7 +504,7 @@ func decomposeSegment(rb *reorderBuffer, sp int, atEOF bool) int { // Force one character to be consumed. info := rb.f.info(rb.src, sp) - if info.size == 0 { + if info.isInvalid() { return 0 } if s := rb.ss.next(info); s == ssStarter { @@ -528,7 +528,7 @@ break } info = rb.f.info(rb.src, sp) - if info.size == 0 { + if info.isInvalid() { if !atEOF { return int(iShortSrc) }
diff --git a/unicode/norm/normalize_test.go b/unicode/norm/normalize_test.go index 855e7b5..8e27435 100644 --- a/unicode/norm/normalize_test.go +++ b/unicode/norm/normalize_test.go
@@ -664,6 +664,11 @@ "a" + rep(0x0305, maxNonStarters+4) + "\u0316", "a" + rep(0x0305, maxNonStarters) + cgj + "\u0316" + rep(0x305, 4), }, + { // illegal rune + "", + "\xf3\xcc\x80", + "\xf3\xcc\x80", + }, { // Combine across non-blocking non-starters. // U+0327 COMBINING CEDILLA;Mn;202;NSM;;;;;N;NON-SPACING CEDILLA;;;;