From 9dc13ebafb2f9c0af4c501d7cd3848d12fe4cd34 Mon Sep 17 00:00:00 2001 From: Maksim An Date: Tue, 20 Apr 2021 23:18:40 -0700 Subject: [PATCH] add logic to parse dm-verity footer from layer VHDs this builds on top of the dm-verity footer feature that has been previously added. changes to opengcs have already been made where the verity info (root hash, merkle tree etc) is expected to be appended to the ext4 data and this change enables passing in the actual verity information. If dm-verity footer read fails, fallback to the original behavior as if the footer wasn't present at all. Signed-off-by: Maksim An --- ext4/dmverity/dmverity.go | 2 + internal/guestrequest/types.go | 27 +++++++++++-- internal/uvm/vpmem.go | 70 ++++++++++++++++++++++++++++++++-- 3 files changed, 91 insertions(+), 8 deletions(-) diff --git a/ext4/dmverity/dmverity.go b/ext4/dmverity/dmverity.go index 1b914b6e44..2fe201bebc 100644 --- a/ext4/dmverity/dmverity.go +++ b/ext4/dmverity/dmverity.go @@ -65,6 +65,7 @@ type VerityInfo struct { DataBlockSize uint32 HashBlockSize uint32 DataBlocks uint64 + Version uint32 } // MerkleTree constructs dm-verity hash-tree for a given byte array with a fixed salt (0-byte) and algorithm (sha256). @@ -191,5 +192,6 @@ func ReadDMVerityInfo(vhdPath string, offsetInBytes int64) (*VerityInfo, error) DataBlocks: dmvSB.DataBlocks, DataBlockSize: dmvSB.DataBlockSize, HashBlockSize: blockSize, + Version: dmvSB.Version, }, nil } diff --git a/internal/guestrequest/types.go b/internal/guestrequest/types.go index 5ac526102d..548a8b43fd 100644 --- a/internal/guestrequest/types.go +++ b/internal/guestrequest/types.go @@ -51,12 +51,31 @@ type LCOWMappedLayer struct { DeviceSizeInBytes uint64 `json:"DeviceSizeInBytes,omitempty"` } +// DeviceVerityInfo represents dm-verity metadata of a block device. +// Most of the fields can be directly mapped to table entries https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html +type DeviceVerityInfo struct { + // Ext4SizeInBytes is the size of ext4 file system + Ext4SizeInBytes int64 `json:",omitempty"` + // Version is the on-disk hash format + Version int `json:",omitempty"` + // Algorithm is the algo used to produce the hashes for dm-verity hash tree + Algorithm string `json:",omitempty"` + // SuperBlock is set to true if dm-verity super block is present on the device + SuperBlock bool `json:",omitempty"` + // RootDigest is the root hash of the dm-verity hash tree + RootDigest string `json:",omitempty"` + // Salt is the salt used to compute the root hash + Salt string `json:",omitempty"` + // BlockSize is the data device block size + BlockSize int `json:",omitempty"` +} + // Read-only layers over VPMem type LCOWMappedVPMemDevice struct { - DeviceNumber uint32 `json:"DeviceNumber,omitempty"` - MountPath string `json:"MountPath,omitempty"` - // Mapping is ignored when MountPath is not empty - MappingInfo *LCOWMappedLayer `json:"MappingInfo,omitempty"` + DeviceNumber uint32 `json:"DeviceNumber,omitempty"` + MountPath string `json:"MountPath,omitempty"` + MappingInfo *LCOWMappedLayer `json:"MappingInfo,omitempty"` + VerityInfo *DeviceVerityInfo `json:"VerityInfo,omitempty"` } type LCOWMappedVPCIDevice struct { diff --git a/internal/uvm/vpmem.go b/internal/uvm/vpmem.go index 061ea489b9..dc9609863f 100644 --- a/internal/uvm/vpmem.go +++ b/internal/uvm/vpmem.go @@ -8,6 +8,8 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" + "github.com/Microsoft/hcsshim/ext4/dmverity" + "github.com/Microsoft/hcsshim/ext4/tar2ext4" "github.com/Microsoft/hcsshim/internal/guestrequest" "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths" hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2" @@ -39,6 +41,42 @@ func newDefaultVPMemInfo(hostPath, uvmPath string) *vPMemInfoDefault { } } +// readVeritySuperBlock reads ext4 super block for a given VHD to then further read the dm-verity super block +// and root hash +func readVeritySuperBlock(ctx context.Context, layerPath string) (*guestrequest.DeviceVerityInfo, error) { + ext4sb, err := tar2ext4.ReadExt4SuperBlock(layerPath) + if err != nil { + return nil, errors.Wrap(err, "failed to read ext4 super block") + } + // Calculate the size of ext4 file system based on the information from ext4 super block, since + // the dm-verity information is expected to be appended, the size of ext4 data will be the offset + // of the dm-verity super block, followed by merkle hash tree + ext4BlockSize := 1024 * (1 << ext4sb.LogBlockSize) + ext4SizeInBytes := int64(ext4BlockSize) * int64(ext4sb.BlocksCountLow) + dmvsb, err := dmverity.ReadDMVerityInfo(layerPath, ext4SizeInBytes) + if err != nil { + return nil, errors.Wrap(err, "failed to read dm-verity super block") + } + log.G(ctx).WithFields(logrus.Fields{ + "layerPath": layerPath, + "rootHash": dmvsb.RootDigest, + "algorithm": dmvsb.Algorithm, + "salt": dmvsb.Salt, + "dataBlocks": dmvsb.DataBlocks, + "dataBlockSize": dmvsb.DataBlockSize, + }).Debug("dm-verity information") + + return &guestrequest.DeviceVerityInfo{ + Ext4SizeInBytes: ext4SizeInBytes, + BlockSize: ext4BlockSize, + RootDigest: dmvsb.RootDigest, + Algorithm: dmvsb.Algorithm, + Salt: dmvsb.Salt, + Version: int(dmvsb.Version), + SuperBlock: true, + }, nil +} + // findNextVPMemSlot finds next available VPMem slot. // // Lock MUST be held when calling this function. @@ -94,6 +132,7 @@ func (uvm *UtilityVM) addVPMemDefault(ctx context.Context, hostPath string) (_ s if err != nil { return "", err } + modification := &hcsschema.ModifySettingRequest{ RequestType: requesttype.Add, Settings: hcsschema.VirtualPMemDevice{ @@ -103,14 +142,28 @@ func (uvm *UtilityVM) addVPMemDefault(ctx context.Context, hostPath string) (_ s }, ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, deviceNumber), } + uvmPath := fmt.Sprintf(lcowDefaultVPMemLayerFmt, deviceNumber) + guestSettings := guestrequest.LCOWMappedVPMemDevice{ + DeviceNumber: deviceNumber, + MountPath: uvmPath, + } + if v, iErr := readVeritySuperBlock(ctx, hostPath); iErr != nil { + log.G(ctx).WithError(err).WithField("hostPath", hostPath).Debug("unable to read dm-verity information from VHD") + } else { + if v != nil { + log.G(ctx).WithFields(logrus.Fields{ + "hostPath": hostPath, + "rootDigest": v.RootDigest, + }).Debug("adding VPMem with dm-verity") + } + guestSettings.VerityInfo = v + } + modification.GuestRequest = guestrequest.GuestRequest{ ResourceType: guestrequest.ResourceTypeVPMemDevice, RequestType: requesttype.Add, - Settings: guestrequest.LCOWMappedVPMemDevice{ - DeviceNumber: deviceNumber, - MountPath: uvmPath, - }, + Settings: guestSettings, } if err := uvm.modify(ctx, modification); err != nil { @@ -135,6 +188,14 @@ func (uvm *UtilityVM) removeVPMemDefault(ctx context.Context, hostPath string) e return nil } + var verity *guestrequest.DeviceVerityInfo + if v, _ := readVeritySuperBlock(ctx, hostPath); v != nil { + log.G(ctx).WithFields(logrus.Fields{ + "hostPath": hostPath, + "rootDigest": v.RootDigest, + }).Debug("removing VPMem with dm-verity") + verity = v + } modification := &hcsschema.ModifySettingRequest{ RequestType: requesttype.Remove, ResourcePath: fmt.Sprintf(resourcepaths.VPMemControllerResourceFormat, deviceNumber), @@ -144,6 +205,7 @@ func (uvm *UtilityVM) removeVPMemDefault(ctx context.Context, hostPath string) e Settings: guestrequest.LCOWMappedVPMemDevice{ DeviceNumber: deviceNumber, MountPath: device.uvmPath, + VerityInfo: verity, }, }, }