Skip to content

Commit

Permalink
squashfs: fix directory listings (#195)
Browse files Browse the repository at this point in the history
This fixes directory listings sometimes giving the error:

    could not parse directory header: header was 3 bytes, less than minimum 12

This was caused by a check for "at least one directory header" failing
on empty directories.

On careful examination of the spec, it says that if a directory header
is present it must have at least one directory entry, but it is silent
on whether a directory header has to exist or not. A non-existent
header seems like the only way to sensibly implement empty
directories.

This adds a check to implement the "at least one directory entry per
directory header" check and modifies the bounds check to catch a
header with no entries.

This also introduces a test checking that we can recurse the test
squashfs file correctly - this fails without this patch.
  • Loading branch information
ncw authored Dec 15, 2023
1 parent e5b4940 commit c46802c
Show file tree
Hide file tree
Showing 6 changed files with 595 additions and 6 deletions.
1 change: 1 addition & 0 deletions filesystem/squashfs/const_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
const (
Squashfsfile = "./testdata/file.sqs"
SquashfsUncompressedfile = "./testdata/file_uncompressed.sqs"
SquashfsfileListing = "./testdata/list.txt"
)

// first header
Expand Down
9 changes: 4 additions & 5 deletions filesystem/squashfs/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,15 @@ type directoryEntryGroup struct {

// parse raw bytes of a directory to get the contents
func parseDirectory(b []byte) (*directory, error) {
// must have at least one header
if _, err := parseDirectoryHeader(b); err != nil {
return nil, fmt.Errorf("could not parse directory header: %v", err)
}
entries := make([]*directoryEntryRaw, 0)
for pos := 0; pos+dirHeaderSize < len(b); {
for pos := 0; pos+dirHeaderSize <= len(b); {
directoryHeader, err := parseDirectoryHeader(b[pos:])
if err != nil {
return nil, fmt.Errorf("could not parse directory header: %v", err)
}
if directoryHeader.count == 0 {
return nil, fmt.Errorf("corrupted directory, must have at least one entry")
}
if directoryHeader.count > maxDirEntries {
return nil, fmt.Errorf("corrupted directory, had %d entries instead of max %d", directoryHeader.count, maxDirEntries)
}
Expand Down
6 changes: 5 additions & 1 deletion filesystem/squashfs/directory_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ var (
testDirectory = &directory{
entries: testDirectoryEntries,
}
testDirectoryTableWithNoEntries = []byte{
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
}
)

func TestParseDirectoryHeader(t *testing.T) {
Expand Down Expand Up @@ -115,7 +118,8 @@ func TestParseDirectory(t *testing.T) {
err error
}{
{testDirectoryTable, testDirectory, nil},
{testDirectoryTable[:10], nil, fmt.Errorf("could not parse directory header: header was 10 bytes, less than minimum 12")},
{testDirectoryTable[:10], &directory{}, nil},
{testDirectoryTableWithNoEntries, nil, fmt.Errorf("corrupted directory, must have at least one entry")},
}
for i, tt := range tests {
dir, err := parseDirectory(tt.b)
Expand Down
66 changes: 66 additions & 0 deletions filesystem/squashfs/squashfs_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package squashfs_test

import (
"bufio"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -294,6 +295,71 @@ func TestSquashfsRead(t *testing.T) {
}
}

// Check the directory listing is correct
func TestSquashfsCheckListing(t *testing.T) {
// read the directory listing in
var listing = map[string]struct{}{}
flist, err := os.Open(squashfs.SquashfsfileListing)
if err != nil {
t.Fatal(err)
}
defer flist.Close()
scanner := bufio.NewScanner(flist)
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimPrefix(line, ".")
if line == "/" {
continue
}
listing[line] = struct{}{}
}
if err := scanner.Err(); err != nil {
t.Fatal(err)
}

// Open the squash file
f, err := os.Open(squashfs.Squashfsfile)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatal(err)
}
// create the filesystem
fs, err := squashfs.Read(f, fi.Size(), 0, 0)
if err != nil {
t.Fatal(err)
}

var list func(dir string)
list = func(dir string) {
fis, err := fs.ReadDir(dir)
if err != nil {
t.Fatal(err)
}
for _, fi := range fis {
p := path.Join(dir, fi.Name())
if _, found := listing[p]; found {
delete(listing, p)
} else {
t.Errorf("Found unexpected path %q in listing", p)
}
if fi.IsDir() {
list(p)
}
}
}

list("/")

// listing should be empty now
for p := range listing {
t.Errorf("Didn't find %q in listing", p)
}
}

func TestSquashfsCreate(t *testing.T) {
tests := []struct {
blocksize int64
Expand Down
3 changes: 3 additions & 0 deletions filesystem/squashfs/testdata/buildtestsqs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ mksquashfs . /data/file.sqs
# uncompressed version
mksquashfs . /data/file_uncompressed.sqs -noI -noD -noF
# create listing to check
find . > /data/list.txt
EOF
Loading

0 comments on commit c46802c

Please sign in to comment.