From ad9ac6d07ce1819260c2b7f090fd4fe742c80d9f Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 10 Jul 2022 23:49:07 -0600 Subject: Fuzz btrfsitem, and by consequence improve binstruct errors --- lib/binstruct/binint/builtins.go | 38 ++++++------- lib/binstruct/binutil/binutil.go | 16 ++++++ lib/binstruct/errors.go | 48 ++++++++++++++++ lib/binstruct/marshal.go | 27 +++++++-- lib/binstruct/size.go | 22 +++++--- lib/binstruct/structs.go | 5 +- lib/binstruct/unmarshal.go | 23 ++++++-- lib/btrfs/btrfsitem/item_chunk.go | 6 +- lib/btrfs/btrfsitem/item_dir.go | 50 ++++++----------- lib/btrfs/btrfsitem/item_extent.go | 8 +-- lib/btrfs/btrfsitem/item_extentcsum.go | 4 +- lib/btrfs/btrfsitem/item_inoderef.go | 13 +++++ lib/btrfs/btrfsitem/item_rootref.go | 13 +++++ lib/btrfs/btrfsitem/items_gen.go | 8 +-- lib/btrfs/btrfsitem/items_test.go | 65 ++++++++++++++++++++++ ...2854a75c03ce8488d9d0508a66fa4de0817264e458d2260 | 2 + ...b31e3c765955d76d06074ea5dca8f6b4ce523a640d50a2d | 2 + ...27afa2b253c5f0ae7389c05837daa91500317cec5122f2e | 2 + ...c86043bfd0dea657fa522bd2b161d6334effe624c847850 | 2 + ...4491768fae2b9260529446c07a34b99d7e384b5dd847d93 | 2 + ...b4e13d9320aee5f9413109bbab24e3b2269cdd6abb97fb7 | 2 + ...623fc40d643eb42f727ed9b0b744c011a39a40552417cad | 2 + lib/btrfs/internal/misc.go | 5 ++ lib/btrfs/io4_fs.go | 44 ++++++--------- lib/btrfsprogs/btrfsinspect/print_tree.go | 18 +++--- 25 files changed, 304 insertions(+), 123 deletions(-) create mode 100644 lib/binstruct/binutil/binutil.go create mode 100644 lib/binstruct/errors.go create mode 100644 lib/btrfs/btrfsitem/items_test.go create mode 100644 lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/12de190a2cae6f0c72854a75c03ce8488d9d0508a66fa4de0817264e458d2260 create mode 100644 lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/1ecbbb90e92add091b31e3c765955d76d06074ea5dca8f6b4ce523a640d50a2d create mode 100644 lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/2ae21a60c209d36ee27afa2b253c5f0ae7389c05837daa91500317cec5122f2e create mode 100644 lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/7b05bfc61add24e74c86043bfd0dea657fa522bd2b161d6334effe624c847850 create mode 100644 lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/990f7eb92226efeef4491768fae2b9260529446c07a34b99d7e384b5dd847d93 create mode 100644 lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/aff8a82fa526d6a55b4e13d9320aee5f9413109bbab24e3b2269cdd6abb97fb7 create mode 100644 lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/c9330e1640e459c9f623fc40d643eb42f727ed9b0b744c011a39a40552417cad (limited to 'lib') diff --git a/lib/binstruct/binint/builtins.go b/lib/binstruct/binint/builtins.go index 5363dbe..01186bc 100644 --- a/lib/binstruct/binint/builtins.go +++ b/lib/binstruct/binint/builtins.go @@ -6,15 +6,9 @@ package binint import ( "encoding/binary" - "fmt" -) -func needNBytes(t interface{}, dat []byte, n int) error { - if len(dat) < n { - return fmt.Errorf("%T.UnmarshalBinary: need at least %v bytes, only have %v", t, n, len(dat)) - } - return nil -} + "git.lukeshu.com/btrfs-progs-ng/lib/binstruct/binutil" +) // unsigned @@ -23,7 +17,7 @@ type U8 uint8 func (U8) BinaryStaticSize() int { return 1 } func (x U8) MarshalBinary() ([]byte, error) { return []byte{byte(x)}, nil } func (x *U8) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 1); err != nil { + if err := binutil.NeedNBytes(dat, 1); err != nil { return 0, err } *x = U8(dat[0]) @@ -41,7 +35,7 @@ func (x U16le) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *U16le) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 2); err != nil { + if err := binutil.NeedNBytes(dat, 2); err != nil { return 0, err } *x = U16le(binary.LittleEndian.Uint16(dat)) @@ -57,7 +51,7 @@ func (x U32le) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *U32le) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 4); err != nil { + if err := binutil.NeedNBytes(dat, 4); err != nil { return 0, err } *x = U32le(binary.LittleEndian.Uint32(dat)) @@ -73,7 +67,7 @@ func (x U64le) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *U64le) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 8); err != nil { + if err := binutil.NeedNBytes(dat, 8); err != nil { return 0, err } *x = U64le(binary.LittleEndian.Uint64(dat)) @@ -91,7 +85,7 @@ func (x U16be) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *U16be) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 2); err != nil { + if err := binutil.NeedNBytes(dat, 2); err != nil { return 0, err } *x = U16be(binary.BigEndian.Uint16(dat)) @@ -107,7 +101,7 @@ func (x U32be) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *U32be) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 4); err != nil { + if err := binutil.NeedNBytes(dat, 4); err != nil { return 0, err } *x = U32be(binary.BigEndian.Uint32(dat)) @@ -123,7 +117,7 @@ func (x U64be) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *U64be) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 8); err != nil { + if err := binutil.NeedNBytes(dat, 8); err != nil { return 0, err } *x = U64be(binary.BigEndian.Uint64(dat)) @@ -137,7 +131,7 @@ type I8 int8 func (I8) BinaryStaticSize() int { return 1 } func (x I8) MarshalBinary() ([]byte, error) { return []byte{byte(x)}, nil } func (x *I8) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 1); err != nil { + if err := binutil.NeedNBytes(dat, 1); err != nil { return 0, err } *x = I8(dat[0]) @@ -155,7 +149,7 @@ func (x I16le) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *I16le) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 2); err != nil { + if err := binutil.NeedNBytes(dat, 2); err != nil { return 0, err } *x = I16le(binary.LittleEndian.Uint16(dat)) @@ -171,7 +165,7 @@ func (x I32le) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *I32le) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 4); err != nil { + if err := binutil.NeedNBytes(dat, 4); err != nil { return 0, err } *x = I32le(binary.LittleEndian.Uint32(dat)) @@ -187,7 +181,7 @@ func (x I64le) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *I64le) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 8); err != nil { + if err := binutil.NeedNBytes(dat, 8); err != nil { return 0, err } *x = I64le(binary.LittleEndian.Uint64(dat)) @@ -205,7 +199,7 @@ func (x I16be) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *I16be) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 2); err != nil { + if err := binutil.NeedNBytes(dat, 2); err != nil { return 0, err } *x = I16be(binary.BigEndian.Uint16(dat)) @@ -221,7 +215,7 @@ func (x I32be) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *I32be) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 4); err != nil { + if err := binutil.NeedNBytes(dat, 4); err != nil { return 0, err } *x = I32be(binary.BigEndian.Uint32(dat)) @@ -237,7 +231,7 @@ func (x I64be) MarshalBinary() ([]byte, error) { return buf[:], nil } func (x *I64be) UnmarshalBinary(dat []byte) (int, error) { - if err := needNBytes(*x, dat, 8); err != nil { + if err := binutil.NeedNBytes(dat, 8); err != nil { return 0, err } *x = I64be(binary.BigEndian.Uint64(dat)) diff --git a/lib/binstruct/binutil/binutil.go b/lib/binstruct/binutil/binutil.go new file mode 100644 index 0000000..684237f --- /dev/null +++ b/lib/binstruct/binutil/binutil.go @@ -0,0 +1,16 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package binutil + +import ( + "fmt" +) + +func NeedNBytes(dat []byte, n int) error { + if len(dat) < n { + return fmt.Errorf("need at least %v bytes, only have %v", n, len(dat)) + } + return nil +} diff --git a/lib/binstruct/errors.go b/lib/binstruct/errors.go new file mode 100644 index 0000000..3914ec7 --- /dev/null +++ b/lib/binstruct/errors.go @@ -0,0 +1,48 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package binstruct + +import ( + "fmt" + "reflect" +) + +type InvalidTypeError struct { + Type reflect.Type + Err error +} + +func (e *InvalidTypeError) Error() string { + return fmt.Sprintf("%v: %v", e.Type, e.Err) +} +func (e *InvalidTypeError) Unwrap() error { return e.Err } + +type UnmarshalError struct { + Type reflect.Type + Method string + Err error +} + +func (e *UnmarshalError) Error() string { + if e.Method == "" { + return fmt.Sprintf("%v: %v", e.Type, e.Err) + } + return fmt.Sprintf("(%v).%v: %v", e.Type, e.Method, e.Err) +} +func (e *UnmarshalError) Unwrap() error { return e.Err } + +type MarshalError struct { + Type reflect.Type + Method string + Err error +} + +func (e *MarshalError) Error() string { + if e.Method == "" { + return fmt.Sprintf("%v: %v", e.Type, e.Err) + } + return fmt.Sprintf("(%v).%v: %v", e.Type, e.Method, e.Err) +} +func (e *MarshalError) Unwrap() error { return e.Err } diff --git a/lib/binstruct/marshal.go b/lib/binstruct/marshal.go index 8159191..78a4bb5 100644 --- a/lib/binstruct/marshal.go +++ b/lib/binstruct/marshal.go @@ -14,7 +14,15 @@ type Marshaler = encoding.BinaryMarshaler func Marshal(obj any) ([]byte, error) { if mar, ok := obj.(Marshaler); ok { - return mar.MarshalBinary() + dat, err := mar.MarshalBinary() + if err != nil { + err = &UnmarshalError{ + Type: reflect.TypeOf(obj), + Method: "MarshalBinary", + Err: err, + } + } + return dat, err } return MarshalWithoutInterface(obj) } @@ -24,7 +32,15 @@ func MarshalWithoutInterface(obj any) ([]byte, error) { switch val.Kind() { case reflect.Uint8, reflect.Int8, reflect.Uint16, reflect.Int16, reflect.Uint32, reflect.Int32, reflect.Uint64, reflect.Int64: typ := intKind2Type[val.Kind()] - return val.Convert(typ).Interface().(Marshaler).MarshalBinary() + dat, err := val.Convert(typ).Interface().(Marshaler).MarshalBinary() + if err != nil { + err = &UnmarshalError{ + Type: typ, + Method: "MarshalBinary", + Err: err, + } + } + return dat, err case reflect.Ptr: return Marshal(val.Elem().Interface()) case reflect.Array: @@ -40,7 +56,10 @@ func MarshalWithoutInterface(obj any) ([]byte, error) { case reflect.Struct: return getStructHandler(val.Type()).Marshal(val) default: - panic(fmt.Errorf("type=%v does not implement binfmt.Marshaler and kind=%v is not a supported statically-sized kind", - val.Type(), val.Kind())) + panic(&InvalidTypeError{ + Type: val.Type(), + Err: fmt.Errorf("does not implement binfmt.Marshaler and kind=%v is not a supported statically-sized kind", + val.Kind()), + }) } } diff --git a/lib/binstruct/size.go b/lib/binstruct/size.go index 03b42d8..365da85 100644 --- a/lib/binstruct/size.go +++ b/lib/binstruct/size.go @@ -5,6 +5,7 @@ package binstruct import ( + "errors" "fmt" "reflect" ) @@ -31,6 +32,14 @@ func staticSize(typ reflect.Type) (int, error) { if typ.Implements(staticSizerType) { return reflect.New(typ).Elem().Interface().(StaticSizer).BinaryStaticSize(), nil } + if typ.Implements(marshalerType) || typ.Implements(unmarshalerType) { + // If you implement binstruct.Marshaler or binstruct.Unmarshaler, + // then you must implement if you wish to be statically sized. + return 0, &InvalidTypeError{ + Type: typ, + Err: errors.New("does not implement binstruct.StaticSizer but does implement binstruct.Marshaler or binstruct.Unmarshaler"), + } + } switch typ.Kind() { case reflect.Uint8, reflect.Int8: return 1, nil @@ -49,13 +58,12 @@ func staticSize(typ reflect.Type) (int, error) { } return elemSize * typ.Len(), nil case reflect.Struct: - if !(typ.Implements(marshalerType) || typ.Implements(unmarshalerType)) { - return getStructHandler(typ).Size, nil - } - return 0, fmt.Errorf("type=%v (kind=%v) does not implement binfmt.StaticSizer but does implement binfmt.Marshaler or binfmt.Unmarshaler", - typ, typ.Kind()) + return getStructHandler(typ).Size, nil default: - return 0, fmt.Errorf("type=%v does not implement binfmt.StaticSizer and kind=%v is not a supported statically-sized kind", - typ, typ.Kind()) + return 0, &InvalidTypeError{ + Type: typ, + Err: fmt.Errorf("does not implement binfmt.StaticSizer and kind=%v is not a supported statically-sized kind", + typ.Kind()), + } } } diff --git a/lib/binstruct/structs.go b/lib/binstruct/structs.go index 2f224dd..72fd5e5 100644 --- a/lib/binstruct/structs.go +++ b/lib/binstruct/structs.go @@ -183,7 +183,10 @@ func getStructHandler(typ reflect.Type) structHandler { h, err := genStructHandler(typ) if err != nil { - panic(err) + panic(&InvalidTypeError{ + Type: typ, + Err: err, + }) } structCache[typ] = h return h diff --git a/lib/binstruct/unmarshal.go b/lib/binstruct/unmarshal.go index c545137..61c2a4a 100644 --- a/lib/binstruct/unmarshal.go +++ b/lib/binstruct/unmarshal.go @@ -5,6 +5,7 @@ package binstruct import ( + "errors" "fmt" "reflect" ) @@ -15,7 +16,15 @@ type Unmarshaler interface { func Unmarshal(dat []byte, dstPtr any) (int, error) { if unmar, ok := dstPtr.(Unmarshaler); ok { - return unmar.UnmarshalBinary(dat) + n, err := unmar.UnmarshalBinary(dat) + if err != nil { + err = &UnmarshalError{ + Type: reflect.TypeOf(dstPtr), + Method: "UnmarshalBinary", + Err: err, + } + } + return n, err } return UnmarshalWithoutInterface(dat, dstPtr) } @@ -23,7 +32,10 @@ func Unmarshal(dat []byte, dstPtr any) (int, error) { func UnmarshalWithoutInterface(dat []byte, dstPtr any) (int, error) { _dstPtr := reflect.ValueOf(dstPtr) if _dstPtr.Kind() != reflect.Ptr { - return 0, fmt.Errorf("not a pointer: %v", _dstPtr.Type()) + panic(&InvalidTypeError{ + Type: _dstPtr.Type(), + Err: errors.New("not a pointer"), + }) } dst := _dstPtr.Elem() @@ -52,7 +64,10 @@ func UnmarshalWithoutInterface(dat []byte, dstPtr any) (int, error) { case reflect.Struct: return getStructHandler(dst.Type()).Unmarshal(dat, dst) default: - panic(fmt.Errorf("type=%v does not implement binfmt.Unmarshaler and kind=%v is not a supported statically-sized kind", - dst.Type(), dst.Kind())) + panic(&InvalidTypeError{ + Type: _dstPtr.Type(), + Err: fmt.Errorf("does not implement binfmt.Unmarshaler and kind=%v is not a supported statically-sized kind", + dst.Kind()), + }) } } diff --git a/lib/btrfs/btrfsitem/item_chunk.go b/lib/btrfs/btrfsitem/item_chunk.go index 7197fb3..2ccc860 100644 --- a/lib/btrfs/btrfsitem/item_chunk.go +++ b/lib/btrfs/btrfsitem/item_chunk.go @@ -5,8 +5,6 @@ package btrfsitem import ( - "fmt" - "git.lukeshu.com/btrfs-progs-ng/lib/binstruct" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/internal" @@ -70,7 +68,7 @@ func (chunk *Chunk) UnmarshalBinary(dat []byte) (int, error) { _n, err := binstruct.Unmarshal(dat[n:], &stripe) n += _n if err != nil { - return n, fmt.Errorf("%T.UnmarshalBinary: %w", *chunk, err) + return n, err } chunk.Stripes = append(chunk.Stripes, stripe) } @@ -87,7 +85,7 @@ func (chunk Chunk) MarshalBinary() ([]byte, error) { _ret, err := binstruct.Marshal(stripe) ret = append(ret, _ret...) if err != nil { - return ret, fmt.Errorf("%T.MarshalBinary: %w", chunk, err) + return ret, err } } return ret, nil diff --git a/lib/btrfs/btrfsitem/item_dir.go b/lib/btrfs/btrfsitem/item_dir.go index 859cd14..2fb2d41 100644 --- a/lib/btrfs/btrfsitem/item_dir.go +++ b/lib/btrfs/btrfsitem/item_dir.go @@ -9,47 +9,21 @@ import ( "hash/crc32" "git.lukeshu.com/btrfs-progs-ng/lib/binstruct" + "git.lukeshu.com/btrfs-progs-ng/lib/binstruct/binutil" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/internal" ) -// key.objectid = inode of directory containing this entry -// key.offset = -// for DIR_ITEM and XATTR_ITEM = NameHash(name) -// for DIR_INDEX = index id in the directory (starting at 2, because "." and "..") -type DirEntries []DirEntry // DIR_ITEM=84 DIR_INDEX=96 XATTR_ITEM=24 +const MaxNameLen = 255 func NameHash(dat []byte) uint64 { return uint64(^crc32.Update(1, crc32.MakeTable(crc32.Castagnoli), dat)) } -func (o *DirEntries) UnmarshalBinary(dat []byte) (int, error) { - *o = nil - n := 0 - for n < len(dat) { - var ref DirEntry - _n, err := binstruct.Unmarshal(dat, &ref) - n += _n - if err != nil { - return n, err - } - *o = append(*o, ref) - } - return n, nil -} - -func (o DirEntries) MarshalBinary() ([]byte, error) { - var ret []byte - for _, ref := range o { - bs, err := binstruct.Marshal(ref) - ret = append(ret, bs...) - if err != nil { - return ret, err - } - } - return ret, nil -} - -type DirEntry struct { +// key.objectid = inode of directory containing this entry +// key.offset = +// for DIR_ITEM and XATTR_ITEM = NameHash(name) +// for DIR_INDEX = index id in the directory (starting at 2, because "." and "..") +type DirEntry struct { // DIR_ITEM=84 DIR_INDEX=96 XATTR_ITEM=24 Location internal.Key `bin:"off=0x0, siz=0x11"` TransID int64 `bin:"off=0x11, siz=8"` DataLen uint16 `bin:"off=0x19, siz=2"` // [ignored-when-writing] @@ -61,10 +35,20 @@ type DirEntry struct { } func (o *DirEntry) UnmarshalBinary(dat []byte) (int, error) { + if err := binutil.NeedNBytes(dat, 0x1e); err != nil { + return 0, err + } n, err := binstruct.UnmarshalWithoutInterface(dat, o) if err != nil { return n, err } + if o.NameLen > MaxNameLen { + return 0, fmt.Errorf("maximum name len is %v, but .NameLen=%v", + MaxNameLen, o.NameLen) + } + if err := binutil.NeedNBytes(dat, 0x1e+int(o.DataLen)+int(o.NameLen)); err != nil { + return 0, err + } o.Data = dat[n : n+int(o.DataLen)] n += int(o.DataLen) o.Name = dat[n : n+int(o.NameLen)] diff --git a/lib/btrfs/btrfsitem/item_extent.go b/lib/btrfs/btrfsitem/item_extent.go index 9257d2b..d49243d 100644 --- a/lib/btrfs/btrfsitem/item_extent.go +++ b/lib/btrfs/btrfsitem/item_extent.go @@ -74,8 +74,8 @@ type ExtentHeader struct { type TreeBlockInfo struct { Key internal.Key `bin:"off=0, siz=0x11"` - Level uint8 `bin:"off=0x11, siz=0x8"` - binstruct.End `bin:"off=0x19"` + Level uint8 `bin:"off=0x11, siz=0x1"` + binstruct.End `bin:"off=0x12"` } type ExtentFlags uint64 @@ -131,7 +131,7 @@ func (o *ExtentInlineRef) UnmarshalBinary(dat []byte) (int, error) { return n, err } default: - return n, fmt.Errorf("btrfsitem.ExtentInlineRef.UnmarshalBinary: unexpected item type %v", o.Type) + return n, fmt.Errorf("unexpected item type %v", o.Type) } return n, nil } @@ -163,7 +163,7 @@ func (o ExtentInlineRef) MarshalBinary() ([]byte, error) { return dat, err } default: - return dat, fmt.Errorf("btrfsitem.ExtentInlineRef.MarshalBinary: unexpected item type %v", o.Type) + return dat, fmt.Errorf("unexpected item type %v", o.Type) } return dat, nil } diff --git a/lib/btrfs/btrfsitem/item_extentcsum.go b/lib/btrfs/btrfsitem/item_extentcsum.go index f9c546d..a945295 100644 --- a/lib/btrfs/btrfsitem/item_extentcsum.go +++ b/lib/btrfs/btrfsitem/item_extentcsum.go @@ -20,7 +20,7 @@ type ExtentCSum struct { // EXTENT_CSUM=128 func (o *ExtentCSum) UnmarshalBinary(dat []byte) (int, error) { if o.ChecksumSize == 0 { - return 0, fmt.Errorf("btrfs.ExtentCSum.UnmarshalBinary: .ChecksumSize must be set") + return 0, fmt.Errorf(".ChecksumSize must be set") } for len(dat) >= o.ChecksumSize { var csum btrfssum.CSum @@ -33,7 +33,7 @@ func (o *ExtentCSum) UnmarshalBinary(dat []byte) (int, error) { func (o ExtentCSum) MarshalBinary() ([]byte, error) { if o.ChecksumSize == 0 { - return nil, fmt.Errorf("btrfs.ExtentCSum.MarshalBinary: .ChecksumSize must be set") + return nil, fmt.Errorf(".ChecksumSize must be set") } var dat []byte for _, csum := range o.Sums { diff --git a/lib/btrfs/btrfsitem/item_inoderef.go b/lib/btrfs/btrfsitem/item_inoderef.go index e4edf4a..b1eaf1b 100644 --- a/lib/btrfs/btrfsitem/item_inoderef.go +++ b/lib/btrfs/btrfsitem/item_inoderef.go @@ -5,7 +5,10 @@ package btrfsitem import ( + "fmt" + "git.lukeshu.com/btrfs-progs-ng/lib/binstruct" + "git.lukeshu.com/btrfs-progs-ng/lib/binstruct/binutil" ) // key.objectid = inode number of the file @@ -18,10 +21,20 @@ type InodeRef struct { // INODE_REF=12 } func (o *InodeRef) UnmarshalBinary(dat []byte) (int, error) { + if err := binutil.NeedNBytes(dat, 0xA); err != nil { + return 0, err + } n, err := binstruct.UnmarshalWithoutInterface(dat, o) if err != nil { return n, err } + if o.NameLen > MaxNameLen { + return 0, fmt.Errorf("maximum name len is %v, but .NameLen=%v", + MaxNameLen, o.NameLen) + } + if err := binutil.NeedNBytes(dat, 0xA+int(o.NameLen)); err != nil { + return 0, err + } dat = dat[n:] o.Name = dat[:o.NameLen] n += int(o.NameLen) diff --git a/lib/btrfs/btrfsitem/item_rootref.go b/lib/btrfs/btrfsitem/item_rootref.go index 228ab55..1ee0ee4 100644 --- a/lib/btrfs/btrfsitem/item_rootref.go +++ b/lib/btrfs/btrfsitem/item_rootref.go @@ -5,7 +5,10 @@ package btrfsitem import ( + "fmt" + "git.lukeshu.com/btrfs-progs-ng/lib/binstruct" + "git.lukeshu.com/btrfs-progs-ng/lib/binstruct/binutil" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/internal" ) @@ -18,10 +21,20 @@ type RootRef struct { // ROOT_REF=156 ROOT_BACKREF=144 } func (o *RootRef) UnmarshalBinary(dat []byte) (int, error) { + if err := binutil.NeedNBytes(dat, 0x12); err != nil { + return 0, err + } n, err := binstruct.UnmarshalWithoutInterface(dat, o) if err != nil { return n, err } + if o.NameLen > MaxNameLen { + return 0, fmt.Errorf("maximum name len is %v, but .NameLen=%v", + MaxNameLen, o.NameLen) + } + if err := binutil.NeedNBytes(dat, 0x12+int(o.NameLen)); err != nil { + return 0, err + } o.Name = dat[n : n+int(o.NameLen)] n += int(o.NameLen) return n, nil diff --git a/lib/btrfs/btrfsitem/items_gen.go b/lib/btrfs/btrfsitem/items_gen.go index 82743b0..8573967 100644 --- a/lib/btrfs/btrfsitem/items_gen.go +++ b/lib/btrfs/btrfsitem/items_gen.go @@ -47,8 +47,8 @@ var keytype2gotype = map[Type]reflect.Type{ CHUNK_ITEM_KEY: reflect.TypeOf(Chunk{}), DEV_EXTENT_KEY: reflect.TypeOf(DevExtent{}), DEV_ITEM_KEY: reflect.TypeOf(Dev{}), - DIR_INDEX_KEY: reflect.TypeOf(DirEntries{}), - DIR_ITEM_KEY: reflect.TypeOf(DirEntries{}), + DIR_INDEX_KEY: reflect.TypeOf(DirEntry{}), + DIR_ITEM_KEY: reflect.TypeOf(DirEntry{}), EXTENT_CSUM_KEY: reflect.TypeOf(ExtentCSum{}), EXTENT_DATA_KEY: reflect.TypeOf(FileExtent{}), EXTENT_DATA_REF_KEY: reflect.TypeOf(ExtentDataRef{}), @@ -70,7 +70,7 @@ var keytype2gotype = map[Type]reflect.Type{ TREE_BLOCK_REF_KEY: reflect.TypeOf(Empty{}), UUID_RECEIVED_SUBVOL_KEY: reflect.TypeOf(UUIDMap{}), UUID_SUBVOL_KEY: reflect.TypeOf(UUIDMap{}), - XATTR_ITEM_KEY: reflect.TypeOf(DirEntries{}), + XATTR_ITEM_KEY: reflect.TypeOf(DirEntry{}), } var untypedObjID2gotype = map[internal.ObjID]reflect.Type{ internal.FREE_SPACE_OBJECTID: reflect.TypeOf(FreeSpaceHeader{}), @@ -81,7 +81,7 @@ func (Chunk) isItem() {} func (Dev) isItem() {} func (DevExtent) isItem() {} func (DevStats) isItem() {} -func (DirEntries) isItem() {} +func (DirEntry) isItem() {} func (Empty) isItem() {} func (Extent) isItem() {} func (ExtentCSum) isItem() {} diff --git a/lib/btrfs/btrfsitem/items_test.go b/lib/btrfs/btrfsitem/items_test.go new file mode 100644 index 0000000..aba8ca2 --- /dev/null +++ b/lib/btrfs/btrfsitem/items_test.go @@ -0,0 +1,65 @@ +// Copyright (C) 2022 Luke Shumaker +// +// SPDX-License-Identifier: GPL-2.0-or-later + +package btrfsitem_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "git.lukeshu.com/btrfs-progs-ng/lib/binstruct" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfssum" + "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/internal" +) + +func FuzzRoundTrip(f *testing.F) { + keySize := binstruct.StaticSize(internal.Key{}) + sumtypeSize := binstruct.StaticSize(btrfssum.CSumType(0)) + + f.Add(make([]byte, 256)) + + f.Fuzz(func(t *testing.T, inDat []byte) { + if len(inDat) < keySize+sumtypeSize { + t.Skip() + } + keyInDat, inDat := inDat[:keySize], inDat[keySize:] + sumtypeInDat, inDat := inDat[:sumtypeSize], inDat[sumtypeSize:] + itemInDat := inDat + + // key + + var key internal.Key + n, err := binstruct.Unmarshal(keyInDat, &key) + require.NoError(t, err, "binstruct.Unmarshal(dat, &key)") + require.Equal(t, keySize, n, "binstruct.Unmarshal(dat, &key)") + + keyOutDat, err := binstruct.Marshal(key) + require.NoError(t, err, "binstruct.Marshal(key)") + require.Equal(t, keyInDat, keyOutDat, "binstruct.Marshal(key)") + + // sumtype + + var sumtype btrfssum.CSumType + n, err = binstruct.Unmarshal(sumtypeInDat, &sumtype) + require.NoError(t, err, "binstruct.Unmarshal(dat, &sumtype)") + require.Equal(t, sumtypeSize, n, "binstruct.Unmarshal(dat, &sumtype)") + + sumtypeOutDat, err := binstruct.Marshal(sumtype) + require.NoError(t, err, "binstruct.Marshal(sumtype)") + require.Equal(t, sumtypeInDat, sumtypeOutDat, "binstruct.Marshal(sumtype)") + + // item + + t.Logf("key=%v sumtype=%v dat=%q", key, sumtype, itemInDat) + + item := btrfsitem.UnmarshalItem(key, sumtype, itemInDat) + require.NotNil(t, item, "btrfsitem.UnmarshalItem") + + itemOutDat, err := binstruct.Marshal(item) + require.NoError(t, err, "binstruct.Marshal(item)") + require.Equal(t, string(itemInDat), string(itemOutDat), "binstruct.Marshal(item)") + }) +} diff --git a/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/12de190a2cae6f0c72854a75c03ce8488d9d0508a66fa4de0817264e458d2260 b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/12de190a2cae6f0c72854a75c03ce8488d9d0508a66fa4de0817264e458d2260 new file mode 100644 index 0000000..6fb6f2c --- /dev/null +++ b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/12de190a2cae6f0c72854a75c03ce8488d9d0508a66fa4de0817264e458d2260 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000\x9c0000000000000000000000000000") diff --git a/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/1ecbbb90e92add091b31e3c765955d76d06074ea5dca8f6b4ce523a640d50a2d b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/1ecbbb90e92add091b31e3c765955d76d06074ea5dca8f6b4ce523a640d50a2d new file mode 100644 index 0000000..5aa6619 --- /dev/null +++ b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/1ecbbb90e92add091b31e3c765955d76d06074ea5dca8f6b4ce523a640d50a2d @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000\x180000000000") diff --git a/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/2ae21a60c209d36ee27afa2b253c5f0ae7389c05837daa91500317cec5122f2e b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/2ae21a60c209d36ee27afa2b253c5f0ae7389c05837daa91500317cec5122f2e new file mode 100644 index 0000000..68454dd --- /dev/null +++ b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/2ae21a60c209d36ee27afa2b253c5f0ae7389c05837daa91500317cec5122f2e @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000\x800000000000") diff --git a/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/7b05bfc61add24e74c86043bfd0dea657fa522bd2b161d6334effe624c847850 b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/7b05bfc61add24e74c86043bfd0dea657fa522bd2b161d6334effe624c847850 new file mode 100644 index 0000000..2576860 --- /dev/null +++ b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/7b05bfc61add24e74c86043bfd0dea657fa522bd2b161d6334effe624c847850 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000`00000000000000000000000000000000000\x00\x00\x00\x0000") diff --git a/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/990f7eb92226efeef4491768fae2b9260529446c07a34b99d7e384b5dd847d93 b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/990f7eb92226efeef4491768fae2b9260529446c07a34b99d7e384b5dd847d93 new file mode 100644 index 0000000..3f5d16a --- /dev/null +++ b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/990f7eb92226efeef4491768fae2b9260529446c07a34b99d7e384b5dd847d93 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000\xa80000000000000000000000000020000000") diff --git a/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/aff8a82fa526d6a55b4e13d9320aee5f9413109bbab24e3b2269cdd6abb97fb7 b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/aff8a82fa526d6a55b4e13d9320aee5f9413109bbab24e3b2269cdd6abb97fb7 new file mode 100644 index 0000000..b9c2ffe --- /dev/null +++ b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/aff8a82fa526d6a55b4e13d9320aee5f9413109bbab24e3b2269cdd6abb97fb7 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000`0000000000000000000000000000000000000000") diff --git a/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/c9330e1640e459c9f623fc40d643eb42f727ed9b0b744c011a39a40552417cad b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/c9330e1640e459c9f623fc40d643eb42f727ed9b0b744c011a39a40552417cad new file mode 100644 index 0000000..c117858 --- /dev/null +++ b/lib/btrfs/btrfsitem/testdata/fuzz/FuzzRoundTrip/c9330e1640e459c9f623fc40d643eb42f727ed9b0b744c011a39a40552417cad @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("00000000\f00000000000000000000") diff --git a/lib/btrfs/internal/misc.go b/lib/btrfs/internal/misc.go index d4bd768..49fe2bd 100644 --- a/lib/btrfs/internal/misc.go +++ b/lib/btrfs/internal/misc.go @@ -5,6 +5,7 @@ package internal import ( + "fmt" "time" "git.lukeshu.com/btrfs-progs-ng/lib/binstruct" @@ -20,6 +21,10 @@ type Key struct { binstruct.End `bin:"off=0x11"` } +func (k Key) String() string { + return fmt.Sprintf("{%v %v %v}", k.ObjectID, k.ItemType, k.Offset) +} + func (a Key) Cmp(b Key) int { if d := util.CmpUint(a.ObjectID, b.ObjectID); d != 0 { return d diff --git a/lib/btrfs/io4_fs.go b/lib/btrfs/io4_fs.go index cb6bd73..3099aca 100644 --- a/lib/btrfs/io4_fs.go +++ b/lib/btrfs/io4_fs.go @@ -201,42 +201,30 @@ func (ret *Dir) populate() { } ret.DotDot = &ref case btrfsitem.DIR_ITEM_KEY: - body := item.Body.(btrfsitem.DirEntries) - if len(body) != 1 { - ret.Errs = append(ret.Errs, fmt.Errorf("multiple direntries in single DIR_ITEM?")) + entry := item.Body.(btrfsitem.DirEntry) + namehash := btrfsitem.NameHash(entry.Name) + if namehash != item.Head.Key.Offset { + ret.Errs = append(ret.Errs, fmt.Errorf("direntry crc32c mismatch: key=%#x crc32c(%q)=%#x", + item.Head.Key.Offset, entry.Name, namehash)) continue } - for _, entry := range body { - namehash := btrfsitem.NameHash(entry.Name) - if namehash != item.Head.Key.Offset { - ret.Errs = append(ret.Errs, fmt.Errorf("direntry crc32c mismatch: key=%#x crc32c(%q)=%#x", - item.Head.Key.Offset, entry.Name, namehash)) - continue - } - if other, exists := ret.ChildrenByName[string(entry.Name)]; exists { - if !reflect.DeepEqual(entry, other) { - ret.Errs = append(ret.Errs, fmt.Errorf("multiple instances of direntry name %q", entry.Name)) - } - continue + if other, exists := ret.ChildrenByName[string(entry.Name)]; exists { + if !reflect.DeepEqual(entry, other) { + ret.Errs = append(ret.Errs, fmt.Errorf("multiple instances of direntry name %q", entry.Name)) } - ret.ChildrenByName[string(entry.Name)] = entry + continue } + ret.ChildrenByName[string(entry.Name)] = entry case btrfsitem.DIR_INDEX_KEY: index := item.Head.Key.Offset - body := item.Body.(btrfsitem.DirEntries) - if len(body) != 1 { - ret.Errs = append(ret.Errs, fmt.Errorf("multiple direntries in single DIR_INDEX?")) - continue - } - for _, entry := range body { - if other, exists := ret.ChildrenByIndex[index]; exists { - if !reflect.DeepEqual(entry, other) { - ret.Errs = append(ret.Errs, fmt.Errorf("multiple instances of direntry index %v", index)) - } - continue + entry := item.Body.(btrfsitem.DirEntry) + if other, exists := ret.ChildrenByIndex[index]; exists { + if !reflect.DeepEqual(entry, other) { + ret.Errs = append(ret.Errs, fmt.Errorf("multiple instances of direntry index %v", index)) } - ret.ChildrenByIndex[index] = entry + continue } + ret.ChildrenByIndex[index] = entry //case btrfsitem.XATTR_ITEM_KEY: default: panic(fmt.Errorf("TODO: handle item type %v", item.Head.Key.ItemType)) diff --git a/lib/btrfsprogs/btrfsinspect/print_tree.go b/lib/btrfsprogs/btrfsinspect/print_tree.go index a950242..cc57ae6 100644 --- a/lib/btrfsprogs/btrfsinspect/print_tree.go +++ b/lib/btrfsprogs/btrfsinspect/print_tree.go @@ -136,16 +136,14 @@ func printTree(out, errout io.Writer, fs *btrfs.FS, treeID btrfs.ObjID) error { body.Index, body.NameLen, body.Name) //case btrfsitem.INODE_EXTREF_KEY: // // TODO - case btrfsitem.DirEntries: - for _, dir := range body { - fmt.Fprintf(out, "\t\tlocation %v type %v\n", - fmtKey(dir.Location), dir.Type) - fmt.Fprintf(out, "\t\ttransid %v data_len %v name_len %v\n", - dir.TransID, dir.DataLen, dir.NameLen) - fmt.Fprintf(out, "\t\tname: %s\n", dir.Name) - if len(dir.Data) > 0 { - fmt.Fprintf(out, "\t\tdata %v\n", dir.Data) - } + case btrfsitem.DirEntry: + fmt.Fprintf(out, "\t\tlocation %v type %v\n", + fmtKey(body.Location), body.Type) + fmt.Fprintf(out, "\t\ttransid %v data_len %v name_len %v\n", + body.TransID, body.DataLen, body.NameLen) + fmt.Fprintf(out, "\t\tname: %s\n", body.Name) + if len(body.Data) > 0 { + fmt.Fprintf(out, "\t\tdata %v\n", body.Data) } //case btrfsitem.DIR_LOG_INDEX_KEY, btrfsitem.DIR_LOG_ITEM_KEY: // // TODO -- cgit v1.2.3-2-g168b