Skip to content

Commit e1c8739

Browse files
authored
Carry over settings for reinit verity. (#695)
When reinitializing verity, carry over the original verity format settings. While Image Customizer doesn't provide the option to change the verity format settings yet, it is expected to customize images not built by itself. Also, when calling `veritysetup format`, hardcode the defaults we want IC to use. This will ensure consistency even if the defaults inside `veritysetup` ever change. Also, move the `verityutils.go` file into its own Go package. This preparing for a future change, where the `imagecustomizerapi` pacakge will need access to these functions. This change is being done in preparation for a future change where support for inline verity will be added.
1 parent 20809f9 commit e1c8739

8 files changed

Lines changed: 247 additions & 164 deletions

File tree

toolkit/tools/imagecustomizerapi/verity.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ const (
1919

2020
VerityRootDevicePath = DeviceMapperPath + "/" + VerityRootDeviceName
2121
VerityUsrDevicePath = DeviceMapperPath + "/" + VerityUsrDeviceName
22+
23+
// Default settings for verity format.
24+
DefaultVerityHashAlgorithm = "sha256"
25+
DefaultVerityDataBlockSize = 4096
26+
DefaultVerityHashBlockSize = 4096
2227
)
2328

2429
var (
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package verityutils
5+
6+
import (
7+
"bytes"
8+
)
9+
10+
// From: https://gitlab.com/cryptsetup/cryptsetup/-/wikis/DMVerity
11+
type VeritySuperBlock struct {
12+
Signature [8]uint8 // "verity\0\0"
13+
Version uint32 // Superblock version: 1
14+
HashType uint32 // 0: Chrome OS, 1: normal
15+
Uuid [16]uint8 // UUID of hash device
16+
Algorithm [32]uint8 // Hash algorithm name
17+
DataBlockSize uint32 // Data block in bytes
18+
HashBlockSize uint32 // Hash block in bytes
19+
DataBlocks uint64 // Number of data blocks
20+
SaltSize uint16 // Salt size
21+
Pad1 [6]uint8 // Padding
22+
Salt [256]uint8 // Salt
23+
Pad2 [168]uint8 // Padding
24+
}
25+
26+
func (s *VeritySuperBlock) GetAlgorithm() string {
27+
algorithmBytes, _, _ := bytes.Cut(s.Algorithm[:], []byte{0})
28+
algorithm := string(algorithmBytes)
29+
return algorithm
30+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package verityutils
5+
6+
import (
7+
"encoding/binary"
8+
"fmt"
9+
"os"
10+
)
11+
12+
func CalculateHashFileSizeInBytes(hashPartitionPath string) (uint64, error) {
13+
superblock, err := ReadVeritySuperblock(hashPartitionPath)
14+
if err != nil {
15+
return 0, err
16+
}
17+
18+
sizeInBytes, err := calculateHashFileSizeInBytesFromSuperBlock(superblock)
19+
if err != nil {
20+
return 0, fmt.Errorf("hash partition's (%s) superblock is invalid:\n%w", hashPartitionPath, err)
21+
}
22+
23+
return sizeInBytes, nil
24+
}
25+
26+
func ReadVeritySuperblock(hashPartitionPath string) (VeritySuperBlock, error) {
27+
hashPartition, err := os.Open(hashPartitionPath)
28+
if err != nil {
29+
return VeritySuperBlock{}, fmt.Errorf("failed to open hash partition (%s) block device:\n%w", hashPartitionPath, err)
30+
}
31+
defer hashPartition.Close()
32+
33+
superblock := VeritySuperBlock{}
34+
err = binary.Read(hashPartition, binary.LittleEndian, &superblock)
35+
if err != nil {
36+
return VeritySuperBlock{}, fmt.Errorf("failed to read hash partition's (%s) superblock:\n%w", hashPartitionPath, err)
37+
}
38+
39+
err = verifySuperblock(superblock)
40+
if err != nil {
41+
return VeritySuperBlock{}, err
42+
}
43+
44+
return superblock, nil
45+
}
46+
47+
func verifySuperblock(superblock VeritySuperBlock) error {
48+
if string(superblock.Signature[:]) != "verity\x00\x00" {
49+
return fmt.Errorf("wrong superblock signature")
50+
}
51+
52+
if superblock.Version != 1 {
53+
return fmt.Errorf("unsupported version (%d)", superblock.Version)
54+
}
55+
56+
if superblock.HashType != 1 {
57+
return fmt.Errorf("unsupported hash type (%d)", superblock.HashType)
58+
}
59+
60+
hashSize, err := getAlgorithmHashSize(superblock.GetAlgorithm())
61+
if err != nil {
62+
return err
63+
}
64+
65+
if !isPowerOf2(superblock.DataBlockSize) {
66+
return fmt.Errorf("invalid data block size (%d)", superblock.DataBlockSize)
67+
}
68+
69+
if !isPowerOf2(superblock.HashBlockSize) || superblock.HashBlockSize < hashSize {
70+
return fmt.Errorf("invalid hash block size (%d)", superblock.HashBlockSize)
71+
}
72+
73+
return nil
74+
}
75+
76+
func getAlgorithmHashSize(algorithm string) (uint32, error) {
77+
switch algorithm {
78+
case "sha256":
79+
return 32, nil
80+
81+
case "sha384":
82+
return 48, nil
83+
84+
case "sha512":
85+
return 64, nil
86+
87+
default:
88+
return 0, fmt.Errorf("unknown hash algorithm (%s)", algorithm)
89+
}
90+
}
91+
92+
func calculateHashFileSizeInBytesFromSuperBlock(superblock VeritySuperBlock) (uint64, error) {
93+
var err error
94+
95+
hashSize, err := getAlgorithmHashSize(superblock.GetAlgorithm())
96+
if err != nil {
97+
return 0, err
98+
}
99+
100+
sizeInBytes, err := calculateHashFileSizeInBytesHelper(superblock.DataBlocks, superblock.HashBlockSize, hashSize)
101+
if err != nil {
102+
return 0, err
103+
}
104+
105+
return sizeInBytes, nil
106+
}
107+
108+
func calculateHashFileSizeInBytesHelper(dataBlocksCount uint64, hashBlockSize uint32, hashSize uint32) (uint64, error) {
109+
// dm-verity pads each hash to the nearest power-of-2 to make the math easier.
110+
hashSizeFull := roundUpToPowerOf2(hashSize)
111+
112+
hashesPerBlock := uint64(hashBlockSize / hashSizeFull)
113+
114+
totalTreeBlocks := uint64(0)
115+
prevLevelTreeBlocks := dataBlocksCount
116+
for prevLevelTreeBlocks > 1 {
117+
levelTreeBlocks := prevLevelTreeBlocks / hashesPerBlock
118+
rem := prevLevelTreeBlocks % hashesPerBlock
119+
if rem != 0 {
120+
// Round up the nearest whole block.
121+
levelTreeBlocks += 1
122+
}
123+
124+
totalTreeBlocks += levelTreeBlocks
125+
prevLevelTreeBlocks = levelTreeBlocks
126+
}
127+
128+
totalBlocks := totalTreeBlocks + 1 // add superblock
129+
totalBytes := totalBlocks * uint64(hashBlockSize)
130+
return totalBytes, nil
131+
}
132+
133+
func isPowerOf2(n uint32) bool {
134+
return (n & (n - 1)) == 0
135+
}
136+
137+
func roundUpToPowerOf2(n uint32) uint32 {
138+
res := uint32(1)
139+
for res < n {
140+
res *= 2
141+
}
142+
return res
143+
}

toolkit/tools/pkg/imagecustomizerlib/customizeverity.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/safemount"
2424
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/shell"
2525
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/sliceutils"
26+
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/verityutils"
2627
"github.com/sirupsen/logrus"
2728
"go.opentelemetry.io/otel"
2829
"go.opentelemetry.io/otel/attribute"
@@ -620,7 +621,7 @@ func findIdentifiedPartition(partitions []diskutils.PartitionInfo, ref imagecust
620621
return partition, nil
621622
}
622623

623-
func customizeVerityImageHelper(ctx context.Context, buildDir string, rc *ResolvedConfig,
624+
func customizeVerityImage(ctx context.Context, buildDir string, rc *ResolvedConfig,
624625
buildImageFile string, partIdToPartUuid map[string]string, shrinkHashPartition bool,
625626
baseImageVerity []verityDeviceMetadata, readonlyPartUuids []string,
626627
partitionsLayout []fstabEntryPartNum,
@@ -670,7 +671,7 @@ func customizeVerityImageHelper(ctx context.Context, buildDir string, rc *Resolv
670671

671672
// Format hash partition.
672673
rootHash, err := verityFormat(loopback.DevicePath(), dataPartition.Path, hashPartition.Path,
673-
shrinkHashPartition, sectorSize, metadata.name)
674+
shrinkHashPartition, sectorSize, metadata.name, metadata.formatSettings)
674675
if err != nil {
675676
return nil, err
676677
}
@@ -696,8 +697,14 @@ func customizeVerityImageHelper(ctx context.Context, buildDir string, rc *Resolv
696697
}
697698

698699
// Format hash partition.
700+
formatSettings := verityFormatSettings{
701+
hashAlgorithm: imagecustomizerapi.DefaultVerityHashAlgorithm,
702+
dataBlockSizeBytes: imagecustomizerapi.DefaultVerityDataBlockSize,
703+
hashBlockSizeBytes: imagecustomizerapi.DefaultVerityHashBlockSize,
704+
}
705+
699706
rootHash, err := verityFormat(loopback.DevicePath(), dataPartition.Path, hashPartition.Path,
700-
shrinkHashPartition, sectorSize, verityConfig.Name)
707+
shrinkHashPartition, sectorSize, verityConfig.Name, formatSettings)
701708
if err != nil {
702709
return nil, err
703710
}
@@ -711,6 +718,7 @@ func customizeVerityImageHelper(ctx context.Context, buildDir string, rc *Resolv
711718
hashDeviceMountIdType: verityConfig.HashDeviceMountIdType,
712719
corruptionOption: verityConfig.CorruptionOption,
713720
hashSignaturePath: verityConfig.HashSignaturePath,
721+
formatSettings: formatSettings,
714722
}
715723
verityMetadata = append(verityMetadata, metadata)
716724
verityUpdated = true
@@ -752,10 +760,17 @@ func customizeVerityImageHelper(ctx context.Context, buildDir string, rc *Resolv
752760
}
753761

754762
func verityFormat(diskDevicePath string, dataPartitionPath string, hashPartitionPath string, shrinkHashPartition bool,
755-
sectorSize uint64, name string,
763+
sectorSize uint64, name string, formatSettings verityFormatSettings,
756764
) (string, error) {
757765
// Write hash partition.
758-
verityOutput, _, err := shell.NewExecBuilder("veritysetup", "format", dataPartitionPath, hashPartitionPath).
766+
formatArgs := []string{
767+
"format", dataPartitionPath, hashPartitionPath,
768+
"--hash", formatSettings.hashAlgorithm,
769+
"--data-block-size", fmt.Sprintf("%d", formatSettings.dataBlockSizeBytes),
770+
"--hash-block-size", fmt.Sprintf("%d", formatSettings.hashBlockSizeBytes),
771+
}
772+
773+
verityOutput, _, err := shell.NewExecBuilder("veritysetup", formatArgs...).
759774
LogLevel(logrus.DebugLevel, logrus.DebugLevel).
760775
ErrorStderrLines(1).
761776
ExecuteCaptureOutput()
@@ -781,10 +796,10 @@ func verityFormat(diskDevicePath string, dataPartitionPath string, hashPartition
781796
return "", fmt.Errorf("%w (device='%s'):\n%w", ErrUpdateDisk, diskDevicePath, err)
782797
}
783798

784-
// Calculate the size of the hash partition from it's superblock.
799+
// Calculate the size of the hash partition from its superblock.
785800
// In newer `veritysetup` versions, `veritysetup format` returns the size in its output. But that feature
786801
// is too new for now.
787-
hashPartitionSizeInBytes, err := calculateHashFileSizeInBytes(hashPartitionPath)
802+
hashPartitionSizeInBytes, err := verityutils.CalculateHashFileSizeInBytes(hashPartitionPath)
788803
if err != nil {
789804
return "", fmt.Errorf("%w (partition='%s'):\n%w", ErrCalculateHashSize, hashPartitionPath, err)
790805
}
@@ -859,3 +874,11 @@ func updateKernelArgsForVerity(buildDir string, diskPartitions []diskutils.Parti
859874

860875
return nil
861876
}
877+
878+
func getVerityNames(verity []verityDeviceMetadata) []string {
879+
verityNames := make([]string, len(verity))
880+
for i, v := range verity {
881+
verityNames[i] = v.name
882+
}
883+
return verityNames
884+
}

toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,6 @@ type imageMetadata struct {
115115
partitionOriginalSizes map[string]uint64
116116
}
117117

118-
type verityDeviceMetadata struct {
119-
name string
120-
rootHash string
121-
dataPartUuid string
122-
hashPartUuid string
123-
dataDeviceMountIdType imagecustomizerapi.MountIdentifierType
124-
hashDeviceMountIdType imagecustomizerapi.MountIdentifierType
125-
corruptionOption imagecustomizerapi.CorruptionOption
126-
hashSignaturePath string
127-
}
128-
129118
func CustomizeImageWithConfigFile(ctx context.Context, buildDir string, configFile string, inputImageFile string,
130119
rpmsSources []string, outputImageFile string, outputImageFormat string,
131120
useBaseImageRpmRepos bool, packageSnapshotTime string,
@@ -524,7 +513,7 @@ func customizeOSContents(ctx context.Context, rc *ResolvedConfig) (imageMetadata
524513

525514
if len(rc.Storage.Verity) > 0 || len(im.baseImageVerityMetadata) > 0 {
526515
// Customize image for dm-verity, setting up verity metadata and security features.
527-
verityMetadata, err := customizeVerityImageHelper(ctx, rc.BuildDirAbs, rc, rc.RawImageFile,
516+
verityMetadata, err := customizeVerityImage(ctx, rc.BuildDirAbs, rc, rc.RawImageFile,
528517
partIdToPartUuid, shrinkPartitions, im.baseImageVerityMetadata, readonlyPartUuids, partitionsLayout)
529518
if err != nil {
530519
return im, fmt.Errorf("%w:\n%w", ErrCustomizeProvisionVerity, err)

toolkit/tools/pkg/imagecustomizerlib/partitionutils.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/safemount"
2727
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/shell"
2828
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/sliceutils"
29+
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/verityutils"
2930
"github.com/sirupsen/logrus"
3031
"golang.org/x/sys/unix"
3132
)
@@ -654,6 +655,12 @@ func findVerityPartitionsFromCmdline(partitions []diskutils.PartitionInfo, cmdli
654655
return diskutils.PartitionInfo{}, 0, verityDeviceMetadata{}, err
655656
}
656657

658+
veritySuperblock, err := verityutils.ReadVeritySuperblock(hashPartition.Path)
659+
if err != nil {
660+
err = fmt.Errorf("failed to read verity superblock:\n%w", err)
661+
return diskutils.PartitionInfo{}, 0, verityDeviceMetadata{}, err
662+
}
663+
657664
verityMetadata := verityDeviceMetadata{
658665
name: name,
659666
rootHash: hash,
@@ -663,6 +670,11 @@ func findVerityPartitionsFromCmdline(partitions []diskutils.PartitionInfo, cmdli
663670
hashDeviceMountIdType: hashIdType,
664671
corruptionOption: corruptionOption,
665672
hashSignaturePath: hashSigPath,
673+
formatSettings: verityFormatSettings{
674+
hashAlgorithm: veritySuperblock.GetAlgorithm(),
675+
dataBlockSizeBytes: veritySuperblock.DataBlockSize,
676+
hashBlockSizeBytes: veritySuperblock.HashBlockSize,
677+
},
666678
}
667679

668680
return dataPartition, dataPartitionIndex, verityMetadata, nil
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package imagecustomizerlib
5+
6+
import (
7+
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagecustomizerapi"
8+
)
9+
10+
type verityDeviceMetadata struct {
11+
name string
12+
rootHash string
13+
dataPartUuid string
14+
hashPartUuid string
15+
dataDeviceMountIdType imagecustomizerapi.MountIdentifierType
16+
hashDeviceMountIdType imagecustomizerapi.MountIdentifierType
17+
corruptionOption imagecustomizerapi.CorruptionOption
18+
hashSignaturePath string
19+
formatSettings verityFormatSettings
20+
}
21+
22+
type verityFormatSettings struct {
23+
hashAlgorithm string
24+
dataBlockSizeBytes uint32
25+
hashBlockSizeBytes uint32
26+
}

0 commit comments

Comments
 (0)