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

squashfs: fix directory listings #195

Merged
merged 1 commit into from
Dec 15, 2023
Merged

squashfs: fix directory listings #195

merged 1 commit into from
Dec 15, 2023

Conversation

ncw
Copy link
Contributor

@ncw ncw commented Dec 12, 2023

This fixes directory listings sometimes giving the error

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

This appears to be because empty directories can indeed have no
entries despite what the spec says.

This introduces a test checking that we can recurse the test squashfs
file correctly - this fails without this patch.

@ncw
Copy link
Contributor Author

ncw commented Dec 12, 2023

Note that the test introduced here won't pass until #193 is merged. Maybe I should have put both commits in the same PR.

@deitch
Copy link
Collaborator

deitch commented Dec 13, 2023

Maybe I should have put both commits in the same PR.

Don't worry about it. We can merge them in order, and when that one is in, rebase this.

@ncw
Copy link
Contributor Author

ncw commented Dec 14, 2023

Note that I'm working on a larger patch to fix up the reading of files out of squashfs which is quite broken in places. Its looking a lot better but it isn't there quite yet!

@deitch
Copy link
Collaborator

deitch commented Dec 14, 2023

Do you need to rebase this on #193 now that it is merged in?

@deitch
Copy link
Collaborator

deitch commented Dec 14, 2023

Note that I'm working on a larger patch to fix up the reading of files out of squashfs which is quite broken in places. It's looking a lot better but it isn't there quite yet!

Thank you, I am looking forward to it. Also to how the tests change and see what they missed (or just weren't implemented 😆 )

@ncw
Copy link
Contributor Author

ncw commented Dec 14, 2023

Do you need to rebase this on #193 now that it is merged in?

I've rebased this and I've also fixed the test I broke!

Note that I'm working on a larger patch to fix up the reading of files out of squashfs which is quite broken in places. It's looking a lot better but it isn't there quite yet!

Thank you, I am looking forward to it. Also to how the tests change and see what they missed (or just weren't implemented 😆 )

I think the tests are very well implemented :-) I've been working on more end-to-end tests so unpacking actual archives created by mksquashfs and there are a lot of corner cases!

I'm hoping to get this all passing shortly but if I get stuck I may push it anyway so you can see the progress and maybe give hints!

@deitch
Copy link
Collaborator

deitch commented Dec 14, 2023

I am confused. Don't we need to parse the header? You are saying it is possible to have an empty directory without any header at all (even though the spec says otherwise). Then ho would we know if the directory is valid?

@ncw
Copy link
Contributor Author

ncw commented Dec 14, 2023

I am confused. Don't we need to parse the header?

I think that the lines of code I deleted 57-60 are effectively repeated at 63-66 when pos == 0 so this bit of code is just a check to see whether there is a header or not.

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); {
directoryHeader, err := parseDirectoryHeader(b[pos:])
if err != nil {
return nil, fmt.Errorf("could not parse directory header: %v", err)
}

You are saying it is possible to have an empty directory without any header at all (even though the spec says otherwise). Then ho would we know if the directory is valid?

If you look in the testdata/file.sqs you'll find such an empty directory at /a/b/c/d/ . If you undo my fix by putting those lines back in then the new test fails with:

squashfs_test.go:340: error reading directory /a/b/c/d: could not read directory at path /a/b/c/d: could not get entries: could not get entries: could not get entries: could not get entries: unable to read directory from table: could not parse directory header: header was 3 bytes, less than minimum 12

I don't know for sure whether that fix is correct, there could be a problem somewhere else, but that is what I came up with to fix the error.

The spec I've been looking at says

A header must have at least one entry. A header must not be followed by more than 256 entries. If there are more entries, a new header is emitted.

Which I took to mean that the directory shouldn't be empty, but looking at it at face value it says if a header exists then it must have at least one entry. Where the case under discussion here is if the header does not exist.

A check for "A header must have at least one entry" would look something like this I think

diff --git a/filesystem/squashfs/directory.go b/filesystem/squashfs/directory.go
index b559af5..1e8628f 100644
--- a/filesystem/squashfs/directory.go
+++ b/filesystem/squashfs/directory.go
@@ -60,6 +60,9 @@ func parseDirectory(b []byte) (*directory, error) {
 		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)
 		}

However that is almost pointless since directoryHeader.count is a uint32 that gets 1 added to it here but I guess it checks something useful so I could add it to the patch if you want.

count: binary.LittleEndian.Uint32(b[0:4]) + 1,

I think the directoryHeader being missing is the only way to encode an empty directory otherwise (which squashfs clearly can as the /a/b/c/d/ directory unpacks as an empty directory) - what do you think?

@deitch
Copy link
Collaborator

deitch commented Dec 15, 2023

I think that the lines of code I deleted 57-60 are effectively repeated at 63-66 when pos == 0 so this bit of code is just a check to see whether there is a header or not.

Rereading the code, I see that makes sense. Our only code after the (deleted) check is looping through the dir entries, for pos := 0; pos+dirHeaderSize < len(b); but if len(b) is less than 12, it will have no loops, and then just return. So this works.

So, yeah, I think you are right. Let's say, "this is the fault of unclear spec writers, we did our best, and now @ncw fixed it for us." 😄

Is this ready to go in?

deitch
deitch previously approved these changes Dec 15, 2023
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.
@ncw
Copy link
Contributor Author

ncw commented Dec 15, 2023

So, yeah, I think you are right. Let's say, "this is the fault of unclear spec writers, we did our best, and now @ncw fixed it for us." 😄

:-)

Is this ready to go in?

I've updated the patch to add the extra check in and add a test for that check. While doing that I noticed that the for loop range needed fixing too! I updated the commit message with a summary of what we learnt above (I hadn't thought it through properly until writing the above so I thought it deserved to go in the commit message!

It is ready to go now if you are happy :-)

@deitch deitch merged commit c46802c into diskfs:master Dec 15, 2023
19 checks passed
@deitch
Copy link
Collaborator

deitch commented Dec 15, 2023

Looks great. Thanks!

@ncw ncw deleted the fix-listings branch December 15, 2023 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants