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/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 + 15 files changed, 134 insertions(+), 47 deletions(-) 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/btrfs/btrfsitem') 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") -- cgit v1.2.3-2-g168b