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, }, }, }