Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protect against stray EOFs #44

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ func emitCborUnmarshalStructField(w io.Writer, f Field) error {

if extra > 0 {
buf := make([]byte, extra)
if _, err := io.ReadFull(br, buf); err != nil {
if err := cbg.ReadExact(br, buf); err != nil {
return err
}
{{ .Name }} = big.NewInt(0).SetBytes(buf)
Expand Down Expand Up @@ -897,7 +897,7 @@ func emitCborUnmarshalSliceField(w io.Writer, f Field) error {
{{ .Name }} = make({{ .TypeName }}, extra)
}
{{end}}
if _, err := io.ReadFull(br, {{ .Name }}[:]); err != nil {
if err := cbg.ReadExact(br, {{ .Name }}[:]); err != nil {
return err
}
`)
Expand Down
1 change: 0 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 h1:MHkK1uRtFbVqvAgvWxafZe54+5uBxLluGylDiKgdhwo=
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mr-tron/base58 v1.1.0 h1:Y51FGVJ91WBqCEabAi5OPUz38eAx8DakuAm5svLcsfQ=
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc=
github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
Expand Down
8 changes: 8 additions & 0 deletions peeker.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,16 @@ func (p *peeker) ReadByte() (byte, error) {
var buf [1]byte
n, err := p.reader.Read(buf[:])
if n == 0 {
if err == nil {
err = io.ErrNoProgress
}
return 0, err
}
// ReadByte is not allowed to return an EOF when a byte was successfully
// read, but the underlying reader is allowed to do so.
if err == io.EOF {
err = nil
}
b := buf[0]
p.lastByte = b
p.peekState = peekSet
Expand Down
8 changes: 4 additions & 4 deletions testing/cbor_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion testing/cbor_map_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 47 additions & 26 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,29 @@ const maxHeaderSize = 9
// the most common readers we encounter in this library for a significant
// performance boost.
func discard(br io.Reader, n int) error {
var err error
switch r := br.(type) {
case *bytes.Buffer:
buf := r.Next(n)
if len(buf) < n {
return io.ErrUnexpectedEOF
err = io.ErrUnexpectedEOF
}
return nil
case *bytes.Reader:
if r.Len() < n {
_, _ = r.Seek(0, io.SeekEnd)
return io.ErrUnexpectedEOF
err = io.ErrUnexpectedEOF
} else {
_, err = r.Seek(int64(n), io.SeekCurrent)
}
_, err := r.Seek(int64(n), io.SeekCurrent)
return err
case *bufio.Reader:
_, err := r.Discard(n)
return err
_, err = r.Discard(n)
default:
_, err := io.CopyN(ioutil.Discard, br, int64(n))
return err
_, err = io.CopyN(ioutil.Discard, br, int64(n))
}
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
}

func ScanForLinks(br io.Reader, cb func(cid.Cid)) error {
Expand Down Expand Up @@ -73,7 +75,7 @@ func ScanForLinks(br io.Reader, cb func(cid.Cid)) error {
return fmt.Errorf("string in cbor input too long")
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Without knowing this code it is hard to know which functions are safe from returning io.EOFs, for example CborReadHeaderBuf above does not check for EOFs but from the names and arguments it seems like it could return them. I don't know if this is actually what we should do but here and in discard it would help me be more sure EOF handling is correct if we did error escalation in a defer statement.


if _, err := io.ReadAtLeast(br, scratch[:extra], int(extra)); err != nil {
if err := ReadExact(br, scratch[:extra]); err != nil {
return err
}

Expand Down Expand Up @@ -172,7 +174,7 @@ func (d *Deferred) UnmarshalCBOR(br io.Reader) error {
// Copy the bytes
limitedReader.N = int64(extra)
buf.Grow(int(extra))
if n, err := buf.ReadFrom(&limitedReader); err != nil {
if n, err := buf.ReadFrom(&limitedReader); err != nil && err != io.EOF {
Copy link
Contributor

Choose a reason for hiding this comment

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

buf.ReadFrom doesn't return io.EOF: https://pkg.go.dev/bytes@go1.17.6#Buffer.ReadFrom

return err
} else if n < int64(extra) {
return io.ErrUnexpectedEOF
Expand Down Expand Up @@ -213,7 +215,7 @@ func readByte(r io.Reader) (byte, error) {
return r.ReadByte()
}
var buf [1]byte
_, err := io.ReadFull(r, buf[:1])
err := ReadExact(r, buf[:1])
return buf[0], err
}

Expand All @@ -240,27 +242,27 @@ func CborReadHeader(br io.Reader) (byte, uint64, error) {
return maj, uint64(next), nil
case low == 25:
scratch := make([]byte, 2)
if _, err := io.ReadAtLeast(br, scratch[:2], 2); err != nil {
if err := ReadExact(br, scratch); err != nil {
return 0, 0, err
}
val := uint64(binary.BigEndian.Uint16(scratch[:2]))
val := uint64(binary.BigEndian.Uint16(scratch))
if val <= math.MaxUint8 {
return 0, 0, fmt.Errorf("cbor input was not canonical (lval 25 with value <= MaxUint8)")
}
return maj, val, nil
case low == 26:
scratch := make([]byte, 4)
if _, err := io.ReadAtLeast(br, scratch[:4], 4); err != nil {
if err := ReadExact(br, scratch); err != nil {
return 0, 0, err
}
val := uint64(binary.BigEndian.Uint32(scratch[:4]))
val := uint64(binary.BigEndian.Uint32(scratch))
if val <= math.MaxUint16 {
return 0, 0, fmt.Errorf("cbor input was not canonical (lval 26 with value <= MaxUint16)")
}
return maj, val, nil
case low == 27:
scratch := make([]byte, 8)
if _, err := io.ReadAtLeast(br, scratch, 8); err != nil {
if err := ReadExact(br, scratch); err != nil {
return 0, 0, err
}
val := binary.BigEndian.Uint64(scratch)
Expand All @@ -276,6 +278,7 @@ func CborReadHeader(br io.Reader) (byte, uint64, error) {
func readByteBuf(r io.Reader, scratch []byte) (byte, error) {
// Reading a single byte from these buffers is much faster than copying
// into a slice.
// None of these will return EOF if they also read a byte.
switch r := r.(type) {
case *bytes.Buffer:
return r.ReadByte()
Expand All @@ -285,13 +288,23 @@ func readByteBuf(r io.Reader, scratch []byte) (byte, error) {
return r.ReadByte()
}
n, err := r.Read(scratch[:1])
if err == io.EOF {
// Read can return EOF, even if it reads data. Furthermore, we
// never want to return EOF on actual error cases (so we return
// "unexpected eof").
if n == 1 {
err = nil
} else {
err = io.ErrUnexpectedEOF
}
}
if err != nil {
return 0, err
}
if n != 1 {
return 0, fmt.Errorf("failed to read a byte")
}
return scratch[0], err
return scratch[0], nil
}

// same as the above, just tries to allocate less by using a passed in scratch buffer
Expand All @@ -317,7 +330,7 @@ func CborReadHeaderBuf(br io.Reader, scratch []byte) (byte, uint64, error) {
}
return maj, uint64(next), nil
case low == 25:
if _, err := io.ReadAtLeast(br, scratch[:2], 2); err != nil {
if err := ReadExact(br, scratch[:2]); err != nil {
return 0, 0, err
}
val := uint64(binary.BigEndian.Uint16(scratch[:2]))
Expand All @@ -326,7 +339,7 @@ func CborReadHeaderBuf(br io.Reader, scratch []byte) (byte, uint64, error) {
}
return maj, val, nil
case low == 26:
if _, err := io.ReadAtLeast(br, scratch[:4], 4); err != nil {
if err := ReadExact(br, scratch[:4]); err != nil {
return 0, 0, err
}
val := uint64(binary.BigEndian.Uint32(scratch[:4]))
Expand All @@ -335,7 +348,7 @@ func CborReadHeaderBuf(br io.Reader, scratch []byte) (byte, uint64, error) {
}
return maj, val, nil
case low == 27:
if _, err := io.ReadAtLeast(br, scratch[:8], 8); err != nil {
if err := ReadExact(br, scratch[:8]); err != nil {
return 0, 0, err
}
val := binary.BigEndian.Uint64(scratch[:8])
Expand Down Expand Up @@ -474,7 +487,7 @@ func ReadByteArray(br io.Reader, maxlen uint64) ([]byte, error) {
}

buf := make([]byte, extra)
if _, err := io.ReadAtLeast(br, buf, int(extra)); err != nil {
if err := ReadExact(br, buf); err != nil {
return nil, err
}

Expand Down Expand Up @@ -514,8 +527,7 @@ func ReadString(r io.Reader) (string, error) {
}

buf := make([]byte, l)
_, err = io.ReadAtLeast(r, buf, int(l))
if err != nil {
if err := ReadExact(r, buf); err != nil {
return "", err
}

Expand All @@ -537,8 +549,7 @@ func ReadStringBuf(r io.Reader, scratch []byte) (string, error) {
}

buf := make([]byte, l)
_, err = io.ReadAtLeast(r, buf, int(l))
if err != nil {
if err := ReadExact(r, buf); err != nil {
return "", err
}

Expand Down Expand Up @@ -623,6 +634,16 @@ func WriteCidBuf(buf []byte, w io.Writer, c cid.Cid) error {
return nil
}

// ReadExact is equivalent to ReadFull except that it returns ErrUnexpectedEOF when it reads
// nothing, instead of just EOF.
func ReadExact(r io.Reader, buf []byte) error {
_, err := io.ReadFull(r, buf)
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
return err
}

type CborBool bool

func (cb *CborBool) MarshalCBOR(w io.Writer) error {
Expand Down