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

snap: minimal read-only squashfs library to read squashfs images #11170

Closed
Closed
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/snapcore/bolt v1.3.2-0.20210908134111-63c8bfcf7af8
github.com/snapcore/go-gettext v0.0.0-20191107141714-82bbea49e785
github.com/snapcore/secboot v0.0.0-20211018143212-802bb19ca263
github.com/ulikunitz/xz v0.5.10 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ github.com/snapcore/secboot v0.0.0-20210909111405-e3a397e2da90/go.mod h1:72paVOk
github.com/snapcore/secboot v0.0.0-20211018143212-802bb19ca263 h1:cq2rG4JcNBCwHvo7iNdJL4nb8Ns7L/aOUd1EFs2toFs=
github.com/snapcore/secboot v0.0.0-20211018143212-802bb19ca263/go.mod h1:72paVOkm4sJugXt+v9ItmnjXgO921D8xqsbH2OekouY=
github.com/snapcore/snapd v0.0.0-20201005140838-501d14ac146e/go.mod h1:3xrn7QDDKymcE5VO2rgWEQ5ZAUGb9htfwlXnoel6Io8=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
133 changes: 133 additions & 0 deletions snap/squashfs2/directory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* https://www.kernel.org/doc/html/v5.8/filesystems/squashfs.html
*/

package squashfs2

import (
"fmt"

"github.com/snapcore/snapd/snap/squashfs2/internal"
)

const (
// directories when empty has the size 3 to include virtual entries
// like '.' and '..'
directoryEmptySize = 3
directoryMaxEntryCount = 256

directoryHeaderSize = 12
directoryEntrySize = 8
)

func (d *directory) readHeader() (internal.DirectoryHeader, error) {
data := make([]byte, directoryHeaderSize)
if err := d.reader.read(data); err != nil {
return internal.DirectoryHeader{}, err
}

header := internal.DirectoryHeader{}
if err := header.Parse(data); err != nil {
return internal.DirectoryHeader{}, err
}
return header, nil
}

func (d *directory) readEntry(header *internal.DirectoryHeader) (internal.DirectoryEntry, int, error) {
buffer := make([]byte, directoryEntrySize)
if err := d.reader.read(buffer); err != nil {
return internal.DirectoryEntry{}, 0, err
}

// the parser does not parse the name, so we have to do it here, as
// we need to know the size of the name before reading it
entry := internal.DirectoryEntry{}
if err := entry.Parse(buffer); err != nil {
return internal.DirectoryEntry{}, 0, err
}

name := make([]byte, entry.Size+1)
if err := d.reader.read(name); err != nil {
return internal.DirectoryEntry{}, 0, err
}

entry.StartBlock = header.StartBlock
entry.Name = string(name)

// We've read the name length, 8 bytes for the directory entry
// and 1 extra byte for the null terminator
bytesRead := int(entry.Size) + directoryEntrySize + 1
return entry, bytesRead, nil
}

func (d *directory) loadEntries() error {
if d.node.Size == directoryEmptySize {
// directory is empty
return nil
}

if err := d.reader.seek(int64(d.node.StartBlock), int(d.node.Offset)); err != nil {
return err
}

bytesRead := 0
for bytesRead < int(d.node.Size)-directoryEmptySize {
dirHeader, err := d.readHeader()
if err != nil {
return err
}

bytesRead += directoryHeaderSize

if dirHeader.Count > directoryMaxEntryCount {
return fmt.Errorf("squashfs: invalid number of directory entries: %d", dirHeader.Count)
}

// squashfs is littered with magic arethmetics, count is
Meulengracht marked this conversation as resolved.
Show resolved Hide resolved
// actually one less than specified in count
for i := 0; i < int(dirHeader.Count)+1; i++ {
entry, size, err := d.readEntry(&dirHeader)
if err != nil {
return err
}

d.entries = append(d.entries, entry)
bytesRead += size
}
}

d.loaded = true
return nil
}

func (d *directory) lookupDirectoryEntry(name string) (*internal.DirectoryEntry, error) {
if !d.loaded {
err := d.loadEntries()
if err != nil {
return nil, err
}
}

for _, entry := range d.entries {
if entry.Name == name {
return &entry, nil
}
}
return nil, fmt.Errorf("squashfs: entry not found: %s", name)
}
111 changes: 111 additions & 0 deletions snap/squashfs2/inode_regular.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* https://www.kernel.org/doc/html/v5.8/filesystems/squashfs.html
*/
package squashfs2

import (
"fmt"

"github.com/snapcore/snapd/snap/squashfs2/internal"
)

func inodeRegularRead(reader *metaBlockReader) ([]byte, error) {
// Read the rest of the base inode
baseData := make([]byte, 30)
Meulengracht marked this conversation as resolved.
Show resolved Hide resolved
if err := reader.read(baseData); err != nil {
return nil, err
}

// Get size of file, usually offset 28, but the type flags
// are already read, so it offsets us 2 bytes into the structure
size := internal.ReadUint32(baseData[26:])

// Read the blocksize table, the blocksizes vary in their meaning
// based on whether or not the fragment table are used. Currently we
// have no fragment table support, so we assume the data blocks just mean
// the sizes of the data blocks, excluding bit 24 that tells us whether or
// not the block is uncompressed.
var blockData []byte
for i := uint32(0); i < size; {
data := make([]byte, 4)
if err := reader.read(data); err != nil {
return nil, err
}

// add the data to the block data buffer so we can
// parse it later as well
blockData = append(blockData, data...)

// ... but parse it already as we need to know the size
blockSize := internal.ReadUint32(data)
i += (blockSize & 0xFEFFFFFF)
}
return append(baseData, blockData...), nil
}

func (sfs *SquashFileSystem) readInodeFileData(n *internal.InodeReg) ([]byte, error) {
// seek to the start of the file
if n.Fragment != 0xFFFFFFFF {
return nil, fmt.Errorf("squashfs: inode uses the fragment table, and we do not support this yet")
}

// we should read in block chunks, so allocate a buffer that can hold
// the number of blocks that cover the entire file size
blockCount := n.Size / sfs.superBlock.BlockSize
Meulengracht marked this conversation as resolved.
Show resolved Hide resolved
if n.Size%sfs.superBlock.BlockSize != 0 {
blockCount++
}
buffer := make([]byte, blockCount*sfs.superBlock.BlockSize)

_, err := sfs.stream.Seek(int64(n.StartBlock), 0)
if err != nil {
return nil, err
}

// Handle the case where compression is turned off for data
if sfs.superBlock.Flags&internal.SuperBlockUncompressedData != 0 {
_, err := sfs.stream.Read(buffer)
if err != nil {
return nil, err
}
return buffer[:n.Size], nil
}

decompressedBuffer := make([]byte, n.Size)
for _, block := range n.BlockSizes {
if block&0x1000000 == 0 {
// compressed block
compressedBuffer := make([]byte, block&0xFEFFFFFF)
if _, err := sfs.stream.Read(compressedBuffer); err != nil {
return nil, err
}

_, err := sfs.compression.Decompress(compressedBuffer, decompressedBuffer)
if err != nil {
return nil, err
}
} else {
// uncompressed block
if _, err := sfs.stream.Read(decompressedBuffer); err != nil {
return nil, err
}
}
}
return decompressedBuffer, nil
}
Loading