...

Source file src/github.com/Microsoft/hcsshim/ext4/dmverity/dmverity.go

Documentation: github.com/Microsoft/hcsshim/ext4/dmverity

     1  package dmverity
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/rand"
     7  	"crypto/sha256"
     8  	"encoding/binary"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  
    13  	"github.com/pkg/errors"
    14  
    15  	"github.com/Microsoft/hcsshim/ext4/internal/compactext4"
    16  	"github.com/Microsoft/hcsshim/internal/memory"
    17  )
    18  
    19  const (
    20  	blockSize = compactext4.BlockSize
    21  	// MerkleTreeBufioSize is a default buffer size to use with bufio.Reader
    22  	MerkleTreeBufioSize = memory.MiB // 1MB
    23  	// RecommendedVHDSizeGB is the recommended size in GB for VHDs, which is not a hard limit.
    24  	RecommendedVHDSizeGB = 128 * memory.GiB
    25  	// VeritySignature is a value written to dm-verity super-block.
    26  	VeritySignature = "verity"
    27  )
    28  
    29  var (
    30  	salt   = bytes.Repeat([]byte{0}, 32)
    31  	sbSize = binary.Size(dmveritySuperblock{})
    32  )
    33  
    34  var (
    35  	ErrSuperBlockReadFailure  = errors.New("failed to read dm-verity super block")
    36  	ErrSuperBlockParseFailure = errors.New("failed to parse dm-verity super block")
    37  	ErrRootHashReadFailure    = errors.New("failed to read dm-verity root hash")
    38  	ErrNotVeritySuperBlock    = errors.New("invalid dm-verity super-block signature")
    39  )
    40  
    41  type dmveritySuperblock struct {
    42  	/* (0) "verity\0\0" */
    43  	Signature [8]byte
    44  	/* (8) superblock version, 1 */
    45  	Version uint32
    46  	/* (12) 0 - Chrome OS, 1 - normal */
    47  	HashType uint32
    48  	/* (16) UUID of hash device */
    49  	UUID [16]byte
    50  	/* (32) Name of the hash algorithm (e.g., sha256) */
    51  	Algorithm [32]byte
    52  	/* (64) The data block size in bytes */
    53  	DataBlockSize uint32
    54  	/* (68) The hash block size in bytes */
    55  	HashBlockSize uint32
    56  	/* (72) The number of data blocks */
    57  	DataBlocks uint64
    58  	/* (80) Size of the salt */
    59  	SaltSize uint16
    60  	/* (82) Padding */
    61  	_ [6]byte
    62  	/* (88) The salt */
    63  	Salt [256]byte
    64  	/* (344) Padding */
    65  	_ [168]byte
    66  }
    67  
    68  // VerityInfo is minimal exported version of dmveritySuperblock
    69  type VerityInfo struct {
    70  	// Offset in blocks on hash device
    71  	HashOffsetInBlocks int64
    72  	// Set to true, when dm-verity super block is also written on the hash device
    73  	SuperBlock    bool
    74  	RootDigest    string
    75  	Salt          string
    76  	Algorithm     string
    77  	DataBlockSize uint32
    78  	HashBlockSize uint32
    79  	DataBlocks    uint64
    80  	Version       uint32
    81  }
    82  
    83  // MerkleTree constructs dm-verity hash-tree for a given io.Reader with a fixed salt (0-byte) and algorithm (sha256).
    84  func MerkleTree(r io.Reader) ([]byte, error) {
    85  	layers := make([][]byte, 0)
    86  	currentLevel := r
    87  
    88  	for {
    89  		nextLevel := bytes.NewBuffer(make([]byte, 0))
    90  		for {
    91  			block := make([]byte, blockSize)
    92  			if _, err := io.ReadFull(currentLevel, block); err != nil {
    93  				if err == io.EOF {
    94  					break
    95  				}
    96  				return nil, errors.Wrap(err, "failed to read data block")
    97  			}
    98  			h := hash2(salt, block)
    99  			nextLevel.Write(h)
   100  		}
   101  
   102  		if nextLevel.Len()%blockSize != 0 {
   103  			padding := bytes.Repeat([]byte{0}, blockSize-(nextLevel.Len()%blockSize))
   104  			nextLevel.Write(padding)
   105  		}
   106  
   107  		layers = append(layers, nextLevel.Bytes())
   108  		currentLevel = bufio.NewReaderSize(nextLevel, MerkleTreeBufioSize)
   109  
   110  		// This means that only root hash remains and our job is done
   111  		if nextLevel.Len() == blockSize {
   112  			break
   113  		}
   114  	}
   115  
   116  	tree := bytes.NewBuffer(make([]byte, 0))
   117  	for i := len(layers) - 1; i >= 0; i-- {
   118  		if _, err := tree.Write(layers[i]); err != nil {
   119  			return nil, errors.Wrap(err, "failed to write merkle tree")
   120  		}
   121  	}
   122  
   123  	return tree.Bytes(), nil
   124  }
   125  
   126  // RootHash computes root hash of dm-verity hash-tree
   127  func RootHash(tree []byte) []byte {
   128  	return hash2(salt, tree[:blockSize])
   129  }
   130  
   131  // NewDMVeritySuperblock returns a dm-verity superblock for a device with a given size, salt, algorithm and versions are
   132  // fixed.
   133  func NewDMVeritySuperblock(size uint64) *dmveritySuperblock {
   134  	superblock := &dmveritySuperblock{
   135  		Version:       1,
   136  		HashType:      1,
   137  		UUID:          generateUUID(),
   138  		DataBlockSize: blockSize,
   139  		HashBlockSize: blockSize,
   140  		DataBlocks:    size / blockSize,
   141  		SaltSize:      uint16(len(salt)),
   142  	}
   143  
   144  	copy(superblock.Signature[:], VeritySignature)
   145  	copy(superblock.Algorithm[:], "sha256")
   146  	copy(superblock.Salt[:], salt)
   147  
   148  	return superblock
   149  }
   150  
   151  func hash2(a, b []byte) []byte {
   152  	h := sha256.New()
   153  	h.Write(append(a, b...))
   154  	return h.Sum(nil)
   155  }
   156  
   157  func generateUUID() [16]byte {
   158  	res := [16]byte{}
   159  	if _, err := rand.Read(res[:]); err != nil {
   160  		panic(err)
   161  	}
   162  	return res
   163  }
   164  
   165  // ReadDMVerityInfo extracts dm-verity super block information and merkle tree root hash
   166  func ReadDMVerityInfo(vhdPath string, offsetInBytes int64) (*VerityInfo, error) {
   167  	vhd, err := os.OpenFile(vhdPath, os.O_RDONLY, 0)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  	defer vhd.Close()
   172  
   173  	// Skip the ext4 data to get to dm-verity super block
   174  	if s, err := vhd.Seek(offsetInBytes, io.SeekStart); err != nil || s != offsetInBytes {
   175  		if err != nil {
   176  			return nil, errors.Wrap(err, "failed to seek dm-verity super block")
   177  		}
   178  		return nil, errors.Errorf("failed to seek dm-verity super block: expected bytes=%d, actual=%d", offsetInBytes, s)
   179  	}
   180  
   181  	block := make([]byte, blockSize)
   182  	if s, err := vhd.Read(block); err != nil || s != blockSize {
   183  		if err != nil {
   184  			return nil, errors.Wrapf(err, "%s", ErrSuperBlockReadFailure)
   185  		}
   186  		return nil, errors.Wrapf(ErrSuperBlockReadFailure, "unexpected bytes read: expected=%d, actual=%d", blockSize, s)
   187  	}
   188  
   189  	dmvSB := &dmveritySuperblock{}
   190  	b := bytes.NewBuffer(block)
   191  	if err := binary.Read(b, binary.LittleEndian, dmvSB); err != nil {
   192  		return nil, errors.Wrapf(err, "%s", ErrSuperBlockParseFailure)
   193  	}
   194  	if string(bytes.Trim(dmvSB.Signature[:], "\x00")[:]) != VeritySignature {
   195  		return nil, ErrNotVeritySuperBlock
   196  	}
   197  	// read the merkle tree root
   198  	if s, err := vhd.Read(block); err != nil || s != blockSize {
   199  		if err != nil {
   200  			return nil, errors.Wrapf(err, "%s", ErrRootHashReadFailure)
   201  		}
   202  		return nil, errors.Wrapf(ErrRootHashReadFailure, "unexpected bytes read: expected=%d, actual=%d", blockSize, s)
   203  	}
   204  	rootHash := hash2(dmvSB.Salt[:dmvSB.SaltSize], block)
   205  	return &VerityInfo{
   206  		RootDigest:         fmt.Sprintf("%x", rootHash),
   207  		Algorithm:          string(bytes.Trim(dmvSB.Algorithm[:], "\x00")),
   208  		Salt:               fmt.Sprintf("%x", dmvSB.Salt[:dmvSB.SaltSize]),
   209  		HashOffsetInBlocks: int64(dmvSB.DataBlocks),
   210  		SuperBlock:         true,
   211  		DataBlocks:         dmvSB.DataBlocks,
   212  		DataBlockSize:      dmvSB.DataBlockSize,
   213  		HashBlockSize:      blockSize,
   214  		Version:            dmvSB.Version,
   215  	}, nil
   216  }
   217  
   218  // ComputeAndWriteHashDevice builds merkle tree from a given io.ReadSeeker and writes the result
   219  // hash device (dm-verity super-block combined with merkle tree) to io.WriteSeeker.
   220  func ComputeAndWriteHashDevice(r io.ReadSeeker, w io.WriteSeeker) error {
   221  	if _, err := r.Seek(0, io.SeekStart); err != nil {
   222  		return err
   223  	}
   224  	tree, err := MerkleTree(r)
   225  	if err != nil {
   226  		return errors.Wrap(err, "failed to build merkle tree")
   227  	}
   228  
   229  	devSize, err := r.Seek(0, io.SeekEnd)
   230  	if err != nil {
   231  		return err
   232  	}
   233  	dmVeritySB := NewDMVeritySuperblock(uint64(devSize))
   234  	if _, err := w.Seek(0, io.SeekEnd); err != nil {
   235  		return err
   236  	}
   237  	if err := binary.Write(w, binary.LittleEndian, dmVeritySB); err != nil {
   238  		return errors.Wrap(err, "failed to write dm-verity super-block")
   239  	}
   240  	// write super-block padding
   241  	padding := bytes.Repeat([]byte{0}, blockSize-(sbSize%blockSize))
   242  	if _, err = w.Write(padding); err != nil {
   243  		return err
   244  	}
   245  	// write tree
   246  	if _, err := w.Write(tree); err != nil {
   247  		return errors.Wrap(err, "failed to write merkle tree")
   248  	}
   249  	return nil
   250  }
   251  

View as plain text