Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ext4/dmverity/dmverity.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -191,5 +192,6 @@ func ReadDMVerityInfo(vhdPath string, offsetInBytes int64) (*VerityInfo, error)
DataBlocks: dmvSB.DataBlocks,
DataBlockSize: dmvSB.DataBlockSize,
HashBlockSize: blockSize,
Version: dmvSB.Version,
}, nil
}
27 changes: 23 additions & 4 deletions internal/guestrequest/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
70 changes: 66 additions & 4 deletions internal/uvm/vpmem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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{
Expand All @@ -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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't error out the call though right, so we'd still make a guest request? I thought the only time we wanted to silently continue was when we get a ErrSuperBlockReadFailure error

Copy link
Copy Markdown
Contributor Author

@anmaxvl anmaxvl Jun 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skipping the error here ensures we always have a fallback to the original behavior. whether it's the way to go about is debatable of course. what do you think?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so the idea is that if we fail to read the verity block then we just mount it as a normal vpmem device? But verity wouldn't be setup properly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that's correct. we could also introduce something that will tell us to always enforce it, for example or vice-versa (i.e. ignore failure)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so we always try to use dm-verity if possible. I think that might be fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup

} else {
if v != nil {
log.G(ctx).WithFields(logrus.Fields{
"hostPath": hostPath,
"rootDigest": v.RootDigest,
}).Debug("adding VPMem with dm-verity")
}
guestSettings.VerityInfo = v
Comment thread
katiewasnothere marked this conversation as resolved.
}

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 {
Expand All @@ -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),
Expand All @@ -144,6 +205,7 @@ func (uvm *UtilityVM) removeVPMemDefault(ctx context.Context, hostPath string) e
Settings: guestrequest.LCOWMappedVPMemDevice{
DeviceNumber: deviceNumber,
MountPath: device.uvmPath,
VerityInfo: verity,
},
},
}
Expand Down