mirror of
https://github.com/golang/go
synced 2024-11-17 23:24:50 -07:00
archive/tar: avoid empty IO operations
The interfaces for io.Reader and io.Writer permit calling Read/Write with an empty buffer. However, this condition is often not well tested and can lead to bugs in various implementations of io.Reader and io.Writer. For example, see #22028 for buggy io.Reader in the bzip2 package. We reduce the likelihood of hitting these bugs by adjusting regFileReader.Read and regFileWriter.Write to avoid performing Read and Write calls when the buffer is known to be empty. Fixes #22029 Change-Id: Ie4a26be53cf87bc4d2abd951fa005db5871cc75c Reviewed-on: https://go-review.googlesource.com/66111 Run-TryBot: Joe Tsai <thebrokentoaster@gmail.com> Reviewed-by: Giovanni Bajo <rasky@develer.com> Reviewed-by: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
57f7bc3a05
commit
7246585f8c
@ -649,12 +649,14 @@ type regFileReader struct {
|
|||||||
nb int64 // Number of remaining bytes to read
|
nb int64 // Number of remaining bytes to read
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fr *regFileReader) Read(b []byte) (int, error) {
|
func (fr *regFileReader) Read(b []byte) (n int, err error) {
|
||||||
if int64(len(b)) > fr.nb {
|
if int64(len(b)) > fr.nb {
|
||||||
b = b[:fr.nb]
|
b = b[:fr.nb]
|
||||||
}
|
}
|
||||||
n, err := fr.r.Read(b)
|
if len(b) > 0 {
|
||||||
|
n, err = fr.r.Read(b)
|
||||||
fr.nb -= int64(n)
|
fr.nb -= int64(n)
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case err == io.EOF && fr.nb > 0:
|
case err == io.EOF && fr.nb > 0:
|
||||||
return n, io.ErrUnexpectedEOF
|
return n, io.ErrUnexpectedEOF
|
||||||
|
@ -7,6 +7,7 @@ package tar
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -1395,6 +1396,17 @@ func TestReadGNUSparsePAXHeaders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testNonEmptyReader wraps an io.Reader and ensures that
|
||||||
|
// Read is never called with an empty buffer.
|
||||||
|
type testNonEmptyReader struct{ io.Reader }
|
||||||
|
|
||||||
|
func (r testNonEmptyReader) Read(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, errors.New("unexpected empty Read call")
|
||||||
|
}
|
||||||
|
return r.Reader.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileReader(t *testing.T) {
|
func TestFileReader(t *testing.T) {
|
||||||
type (
|
type (
|
||||||
testRead struct { // Read(cnt) == (wantStr, wantErr)
|
testRead struct { // Read(cnt) == (wantStr, wantErr)
|
||||||
@ -1443,7 +1455,6 @@ func TestFileReader(t *testing.T) {
|
|||||||
maker: makeReg{"", 1},
|
maker: makeReg{"", 1},
|
||||||
tests: []testFnc{
|
tests: []testFnc{
|
||||||
testRemaining{1, 1},
|
testRemaining{1, 1},
|
||||||
testRead{0, "", io.ErrUnexpectedEOF},
|
|
||||||
testRead{5, "", io.ErrUnexpectedEOF},
|
testRead{5, "", io.ErrUnexpectedEOF},
|
||||||
testWriteTo{nil, 0, io.ErrUnexpectedEOF},
|
testWriteTo{nil, 0, io.ErrUnexpectedEOF},
|
||||||
testRemaining{1, 1},
|
testRemaining{1, 1},
|
||||||
@ -1611,14 +1622,14 @@ func TestFileReader(t *testing.T) {
|
|||||||
var fr fileReader
|
var fr fileReader
|
||||||
switch maker := v.maker.(type) {
|
switch maker := v.maker.(type) {
|
||||||
case makeReg:
|
case makeReg:
|
||||||
r := strings.NewReader(maker.str)
|
r := testNonEmptyReader{strings.NewReader(maker.str)}
|
||||||
fr = ®FileReader{r, maker.size}
|
fr = ®FileReader{r, maker.size}
|
||||||
case makeSparse:
|
case makeSparse:
|
||||||
if !validateSparseEntries(maker.spd, maker.size) {
|
if !validateSparseEntries(maker.spd, maker.size) {
|
||||||
t.Fatalf("invalid sparse map: %v", maker.spd)
|
t.Fatalf("invalid sparse map: %v", maker.spd)
|
||||||
}
|
}
|
||||||
sph := invertSparseEntries(maker.spd, maker.size)
|
sph := invertSparseEntries(maker.spd, maker.size)
|
||||||
r := strings.NewReader(maker.makeReg.str)
|
r := testNonEmptyReader{strings.NewReader(maker.makeReg.str)}
|
||||||
fr = ®FileReader{r, maker.makeReg.size}
|
fr = ®FileReader{r, maker.makeReg.size}
|
||||||
fr = &sparseFileReader{fr, sph, 0}
|
fr = &sparseFileReader{fr, sph, 0}
|
||||||
default:
|
default:
|
||||||
|
@ -452,13 +452,15 @@ type regFileWriter struct {
|
|||||||
nb int64 // Number of remaining bytes to write
|
nb int64 // Number of remaining bytes to write
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fw *regFileWriter) Write(b []byte) (int, error) {
|
func (fw *regFileWriter) Write(b []byte) (n int, err error) {
|
||||||
overwrite := int64(len(b)) > fw.nb
|
overwrite := int64(len(b)) > fw.nb
|
||||||
if overwrite {
|
if overwrite {
|
||||||
b = b[:fw.nb]
|
b = b[:fw.nb]
|
||||||
}
|
}
|
||||||
n, err := fw.w.Write(b)
|
if len(b) > 0 {
|
||||||
|
n, err = fw.w.Write(b)
|
||||||
fw.nb -= int64(n)
|
fw.nb -= int64(n)
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return n, err
|
return n, err
|
||||||
|
@ -7,6 +7,7 @@ package tar
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -987,6 +988,17 @@ func TestIssue12594(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testNonEmptyWriter wraps an io.Writer and ensures that
|
||||||
|
// Write is never called with an empty buffer.
|
||||||
|
type testNonEmptyWriter struct{ io.Writer }
|
||||||
|
|
||||||
|
func (w testNonEmptyWriter) Write(b []byte) (int, error) {
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, errors.New("unexpected empty Write call")
|
||||||
|
}
|
||||||
|
return w.Writer.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileWriter(t *testing.T) {
|
func TestFileWriter(t *testing.T) {
|
||||||
type (
|
type (
|
||||||
testWrite struct { // Write(str) == (wantCnt, wantErr)
|
testWrite struct { // Write(str) == (wantCnt, wantErr)
|
||||||
@ -1225,17 +1237,18 @@ func TestFileWriter(t *testing.T) {
|
|||||||
for i, v := range vectors {
|
for i, v := range vectors {
|
||||||
var wantStr string
|
var wantStr string
|
||||||
bb := new(bytes.Buffer)
|
bb := new(bytes.Buffer)
|
||||||
|
w := testNonEmptyWriter{bb}
|
||||||
var fw fileWriter
|
var fw fileWriter
|
||||||
switch maker := v.maker.(type) {
|
switch maker := v.maker.(type) {
|
||||||
case makeReg:
|
case makeReg:
|
||||||
fw = ®FileWriter{bb, maker.size}
|
fw = ®FileWriter{w, maker.size}
|
||||||
wantStr = maker.wantStr
|
wantStr = maker.wantStr
|
||||||
case makeSparse:
|
case makeSparse:
|
||||||
if !validateSparseEntries(maker.sph, maker.size) {
|
if !validateSparseEntries(maker.sph, maker.size) {
|
||||||
t.Fatalf("invalid sparse map: %v", maker.sph)
|
t.Fatalf("invalid sparse map: %v", maker.sph)
|
||||||
}
|
}
|
||||||
spd := invertSparseEntries(maker.sph, maker.size)
|
spd := invertSparseEntries(maker.sph, maker.size)
|
||||||
fw = ®FileWriter{bb, maker.makeReg.size}
|
fw = ®FileWriter{w, maker.makeReg.size}
|
||||||
fw = &sparseFileWriter{fw, spd, 0}
|
fw = &sparseFileWriter{fw, spd, 0}
|
||||||
wantStr = maker.makeReg.wantStr
|
wantStr = maker.makeReg.wantStr
|
||||||
default:
|
default:
|
||||||
|
Loading…
Reference in New Issue
Block a user