tiff/lzw: avoid hi code overflow

This is the identical fix from CL 42032 (compress/lzw)
applied to the tiff lzw reader.

Fixes golang/go#81125

Change-Id: Ie2b1fbc88608c3d778224c72d19564d16a6a6964
Reviewed-on: https://go-review.googlesource.com/c/image/+/821880
Reviewed-by: Nicholas Husin <nsh@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Nicholas Husin <husin@google.com>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
diff --git a/testdata/lzw-hi-overflow.tiff.bz2 b/testdata/lzw-hi-overflow.tiff.bz2
new file mode 100644
index 0000000..8fa9ebb
--- /dev/null
+++ b/testdata/lzw-hi-overflow.tiff.bz2
Binary files differ
diff --git a/tiff/lzw/reader.go b/tiff/lzw/reader.go
index 0aae475..149b905 100644
--- a/tiff/lzw/reader.go
+++ b/tiff/lzw/reader.go
@@ -211,6 +211,10 @@
 		if d.hi+1 >= d.overflow { // NOTE: the "+1" is where TIFF's LZW differs from the standard algorithm.
 			if d.width == maxWidth {
 				d.last = decoderInvalidCode
+				// Undo the d.hi++ a few lines above, so that (1) we maintain
+				// the invariant that d.hi < d.overflow, and (2) d.hi does not
+				// eventually overflow a uint16.
+				d.hi--
 			} else {
 				d.width++
 				d.overflow <<= 1
diff --git a/tiff/reader_test.go b/tiff/reader_test.go
index 8bbce2b..3fa8fb2 100644
--- a/tiff/reader_test.go
+++ b/tiff/reader_test.go
@@ -36,7 +36,11 @@
 		return nil, err
 	}
 	defer f.Close()
-	img, _, err := image.Decode(f)
+	var r io.Reader = f
+	if strings.HasSuffix(name, ".bz2") {
+		r = bzip2.NewReader(f)
+	}
+	img, _, err := image.Decode(r)
 	if err != nil {
 		return nil, err
 	}
@@ -428,6 +432,27 @@
 	}
 }
 
+// TestLZWHiCodeOverflow tests decoding a 256x256 black image
+// which used to cause a panic due to the decoder's hi code overflowing the code table.
+func TestLZWHiCodeOverflow(t *testing.T) {
+	img, err := load("lzw-hi-overflow.tiff.bz2")
+	if err != nil {
+		t.Fatal(err)
+	}
+	g, ok := img.(*image.Gray)
+	if !ok {
+		t.Fatalf("got %T, want *image.Gray", img)
+	}
+	if got, want := g.Bounds(), image.Rect(0, 0, 256, 256); got != want {
+		t.Fatalf("bounds: got %v, want %v", got, want)
+	}
+	for i, v := range g.Pix {
+		if v != 0 {
+			t.Fatalf("pixel %d: got %d, want 0", i, v)
+		}
+	}
+}
+
 // benchmarkDecode benchmarks the decoding of an image.
 func benchmarkDecode(b *testing.B, filename string) {
 	b.Helper()