image/tiff: do not allow zero bits per sample

Fuzzing detected a divide by zero in images with 0 bits
per sample. Instead of panicing, return an error. Do more
validation of bits per sample so that the package only
supports what we've actually tested.

Fixes golang/go#10711.

Change-Id: Ib41b5cd798c32b06429164c9bc471f5f321d88c5
Reviewed-on: https://go-review.googlesource.com/10943
Reviewed-by: Benny Siegert <bsiegert@gmail.com>
Reviewed-by: Nigel Tao <nigeltao@golang.org>
diff --git a/tiff/reader.go b/tiff/reader.go
index 3f6e397..ecbd474 100644
--- a/tiff/reader.go
+++ b/tiff/reader.go
@@ -231,7 +231,7 @@
 					off += 2
 				}
 			}
-		} else if d.bpp == 8 {
+		} else {
 			var off int
 			n := 1 * len(d.features[tBitsPerSample]) // bytes per sample times samples per pixel
 			for y := ymin; y < ymax; y++ {
@@ -436,6 +436,14 @@
 		return nil, FormatError("BitsPerSample tag missing")
 	}
 	d.bpp = d.firstVal(tBitsPerSample)
+	switch d.bpp {
+	case 0:
+		return nil, FormatError("BitsPerSample must not be 0")
+	case 1, 8, 16:
+		// Nothing to do, these are accepted by this implementation.
+	default:
+		return nil, UnsupportedError(fmt.Sprintf("BitsPerSample of %v", d.bpp))
+	}
 
 	// Determine the image mode.
 	switch d.firstVal(tPhotometricInterpretation) {
diff --git a/tiff/reader_test.go b/tiff/reader_test.go
index 1a72ac3..f3d7128 100644
--- a/tiff/reader_test.go
+++ b/tiff/reader_test.go
@@ -255,12 +255,35 @@
 	}
 }
 
+// TestZeroBitsPerSample verifies that an IFD with a bitsPerSample of 0 does not cause a crash.
+// Issue 10711.
+func TestZeroBitsPerSample(t *testing.T) {
+	contents, err := ioutil.ReadFile(testdataDir + "bw-deflate.tiff")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// Mutate the loaded image to have the problem.
+	// 02 01: tag number (tBitsPerSample)
+	// 03 00: data type (short, or uint16)
+	// 01 00 00 00: count
+	// ?? 00 00 00: value (1 -> 0)
+	find := []byte{2, 1, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0}
+	repl := []byte{2, 1, 3, 0, 1, 0, 0, 0, 0, 0, 0, 0}
+	contents = bytes.Replace(contents, find, repl, 1)
+
+	_, err = Decode(bytes.NewReader(contents))
+	if err == nil {
+		t.Fatal("Decode with 0 bits per sample: got nil error, want non-nil")
+	}
+}
+
 // benchmarkDecode benchmarks the decoding of an image.
 func benchmarkDecode(b *testing.B, filename string) {
 	b.StopTimer()
 	contents, err := ioutil.ReadFile(testdataDir + filename)
 	if err != nil {
-		panic(err)
+		b.Fatal(err)
 	}
 	r := &buffer{buf: contents}
 	b.StartTimer()