Skip to content

Commit

Permalink
update ext4 and dmverity superblock parsing logic
Browse files Browse the repository at this point in the history
Add another `ReadExt4SuperBlockReadSeeker` implementation
to work with `io.ReadSeeker` in addition to working with
files directly. This way we'll be able to work with e.g.
ext4 GPT partitions. The existing `ReadExt4SuperBlock` works
the same, but has been updated to call `ReadExt4SuperBlockReadSeeker`.

Add `ReadDMVerityInfoReader` that reads dmverity superblock
from an `io.Reader` and update `ReadDMVerityInfo` accordingly.

Additionally write verity superblock directly without `io.SeekEnd`,
assumming that the writer is already set at correct offset.
`ComputeAndWriteHashDevice` parameters have been updated
accordingly.

Add `Ext4FileSystemSize` function that reads ext4 superblock
from a given `io.ReadSeeker` and returns the underlying
ext4 filesystem size and its superblock.

Signed-off-by: Maksim An <maksiman@microsoft.com>
  • Loading branch information
anmaxvl committed May 19, 2023
1 parent 55f8c42 commit 12e71f5
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 26 deletions.
44 changes: 31 additions & 13 deletions ext4/dmverity/dmverity.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,29 +178,35 @@ func ReadDMVerityInfo(vhdPath string, offsetInBytes int64) (*VerityInfo, error)
return nil, errors.Errorf("failed to seek dm-verity super block: expected bytes=%d, actual=%d", offsetInBytes, s)
}

return ReadDMVerityInfoReader(vhd)
}

func ReadDMVerityInfoReader(r io.Reader) (*VerityInfo, error) {
block := make([]byte, blockSize)
if s, err := vhd.Read(block); err != nil || s != blockSize {
if s, err := r.Read(block); err != nil || s != blockSize {
if err != nil {
return nil, errors.Wrapf(err, "%s", ErrSuperBlockReadFailure)
return nil, fmt.Errorf("%s: %w", ErrSuperBlockReadFailure, err)
}
return nil, errors.Wrapf(ErrSuperBlockReadFailure, "unexpected bytes read: expected=%d, actual=%d", blockSize, s)
return nil, fmt.Errorf("unexpected bytes read expected=%d actual=%d: %w", blockSize, s, ErrSuperBlockReadFailure)
}

dmvSB := &dmveritySuperblock{}
b := bytes.NewBuffer(block)
if err := binary.Read(b, binary.LittleEndian, dmvSB); err != nil {
return nil, errors.Wrapf(err, "%s", ErrSuperBlockParseFailure)
return nil, fmt.Errorf("%s: %w", ErrSuperBlockParseFailure, err)
}

if string(bytes.Trim(dmvSB.Signature[:], "\x00")[:]) != VeritySignature {
return nil, ErrNotVeritySuperBlock
}
// read the merkle tree root
if s, err := vhd.Read(block); err != nil || s != blockSize {

if s, err := r.Read(block); err != nil || s != blockSize {
if err != nil {
return nil, errors.Wrapf(err, "%s", ErrRootHashReadFailure)
return nil, fmt.Errorf("%s: %w", ErrRootHashReadFailure, err)
}
return nil, errors.Wrapf(ErrRootHashReadFailure, "unexpected bytes read: expected=%d, actual=%d", blockSize, s)
return nil, fmt.Errorf("unexpected bytes read expected=%d, actual=%d: %w", blockSize, s, ErrRootHashReadFailure)
}

rootHash := hash2(dmvSB.Salt[:dmvSB.SaltSize], block)
return &VerityInfo{
RootDigest: fmt.Sprintf("%x", rootHash),
Expand All @@ -215,12 +221,21 @@ func ReadDMVerityInfo(vhdPath string, offsetInBytes int64) (*VerityInfo, error)
}, nil
}

// ComputeAndWriteHashDevice builds merkle tree from a given io.ReadSeeker and writes the result
// hash device (dm-verity super-block combined with merkle tree) to io.WriteSeeker.
func ComputeAndWriteHashDevice(r io.ReadSeeker, w io.WriteSeeker) error {
// ComputeAndWriteHashDevice builds merkle tree from a given io.ReadSeeker and
// writes the result hash device (dm-verity super-block combined with merkle
// tree) to io.Writer.
func ComputeAndWriteHashDevice(r io.ReadSeeker, w io.Writer) error {
// save current reader position
currBytePos, err := r.Seek(0, io.SeekCurrent)
if err != nil {
return err
}

// reset to the beginning to find the device size
if _, err := r.Seek(0, io.SeekStart); err != nil {
return err
}

tree, err := MerkleTree(r)
if err != nil {
return errors.Wrap(err, "failed to build merkle tree")
Expand All @@ -230,10 +245,13 @@ func ComputeAndWriteHashDevice(r io.ReadSeeker, w io.WriteSeeker) error {
if err != nil {
return err
}
dmVeritySB := NewDMVeritySuperblock(uint64(devSize))
if _, err := w.Seek(0, io.SeekEnd); err != nil {

// reset reader to initial position
if _, err := r.Seek(currBytePos, io.SeekStart); err != nil {
return err
}

dmVeritySB := NewDMVeritySuperblock(uint64(devSize))
if err := binary.Write(w, binary.LittleEndian, dmVeritySB); err != nil {
return errors.Wrap(err, "failed to write dm-verity super-block")
}
Expand Down
44 changes: 36 additions & 8 deletions ext4/tar2ext4/tar2ext4.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,19 @@ func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error {
return nil
}

// ReadExt4SuperBlock reads and returns ext4 super block from VHD
// ReadExt4SuperBlock reads and returns ext4 super block from given device.
func ReadExt4SuperBlock(devicePath string) (*format.SuperBlock, error) {
dev, err := os.OpenFile(devicePath, os.O_RDONLY, 0)
if err != nil {
return nil, err
}
defer dev.Close()

return ReadExt4SuperBlockReadSeeker(dev)
}

// ReadExt4SuperBlockReadSeeker reads and returns ext4 super block given
// an io.ReadSeeker.
//
// The layout on disk is as follows:
// | Group 0 padding | - 1024 bytes
Expand All @@ -215,22 +227,26 @@ func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error {
// More details can be found here https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout
//
// Our goal is to skip the Group 0 padding, read and return the ext4 SuperBlock
func ReadExt4SuperBlock(devicePath string) (*format.SuperBlock, error) {
dev, err := os.OpenFile(devicePath, os.O_RDONLY, 0)
func ReadExt4SuperBlockReadSeeker(rsc io.ReadSeeker) (*format.SuperBlock, error) {
// save current reader position
currBytePos, err := rsc.Seek(0, io.SeekCurrent)
if err != nil {
return nil, err
}
defer dev.Close()

// Skip padding at the start
if _, err := dev.Seek(1024, io.SeekStart); err != nil {
if _, err := rsc.Seek(1024, io.SeekCurrent); err != nil {
return nil, err
}
var sb format.SuperBlock
if err := binary.Read(dev, binary.LittleEndian, &sb); err != nil {
if err := binary.Read(rsc, binary.LittleEndian, &sb); err != nil {
return nil, err
}

// reset the reader to initial position
if _, err := rsc.Seek(currBytePos, io.SeekStart); err != nil {
return nil, err
}
// Make sure the magic bytes are correct.

if sb.Magic != format.SuperBlockMagic {
return nil, errors.New("not an ext4 file system")
}
Expand All @@ -246,6 +262,18 @@ func IsDeviceExt4(devicePath string) bool {
return err == nil
}

// Ext4FileSystemSize reads ext4 superblock and returns the size of the underlying
// ext4 file system and its block size.
func Ext4FileSystemSize(r io.ReadSeeker) (int64, int, error) {
sb, err := ReadExt4SuperBlockReadSeeker(r)
if err != nil {
return 0, 0, fmt.Errorf("failed to read ext4 superblock: %w", err)
}
blockSize := 1024 * (1 << sb.LogBlockSize)
fsSize := int64(blockSize) * int64(sb.BlocksCountLow)
return fsSize, blockSize, nil
}

// ConvertAndComputeRootDigest writes a compact ext4 file system image that contains the files in the
// input tar stream, computes the resulting file image's cryptographic hashes (merkle tree) and returns
// merkle tree root digest. Convert is called with minimal options: ConvertWhiteout and MaximumDiskSize
Expand Down
12 changes: 7 additions & 5 deletions internal/verity/verity.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package verity

import (
"context"
"fmt"
"os"

"github.com/Microsoft/hcsshim/ext4/dmverity"
"github.com/Microsoft/hcsshim/ext4/tar2ext4"
Expand All @@ -13,13 +15,13 @@ import (

// fileSystemSize retrieves ext4 fs SuperBlock and returns the file system size and block size
func fileSystemSize(vhdPath string) (int64, int, error) {
sb, err := tar2ext4.ReadExt4SuperBlock(vhdPath)
vhd, err := os.Open(vhdPath)
if err != nil {
return 0, 0, errors.Wrap(err, "failed to read ext4 super block")
return 0, 0, fmt.Errorf("failed to open VHD file: %w", err)
}
blockSize := 1024 * (1 << sb.LogBlockSize)
fsSize := int64(blockSize) * int64(sb.BlocksCountLow)
return fsSize, blockSize, nil
defer vhd.Close()

return tar2ext4.Ext4FileSystemSize(vhd)
}

// ReadVeritySuperBlock reads ext4 super block for a given VHD to then further read the dm-verity super block
Expand Down

0 comments on commit 12e71f5

Please sign in to comment.