blob: 5a968cd24c6832aae7923862942d114f548e0b9e [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Mateusz Zalega356b8962021-08-10 17:27:15 +02002// SPDX-License-Identifier: Apache-2.0
Mateusz Zalega356b8962021-08-10 17:27:15 +02003
4// This package implements the minimum of functionality needed to generate and
5// map dm-verity images. It's provided in order to avoid a perceived higher
6// long term cost of packaging, linking against and maintaining the original C
7// veritysetup tool.
8//
9// dm-verity is a Linux device mapper target that allows integrity verification of
10// a read-only block device. The block device whose integrity should be checked
11// (the 'data device') must be first processed by a tool like veritysetup to
12// generate a hash device and root hash.
13// The original data device, hash device and root hash are then set up as a device
14// mapper target, and any read performed from the data device through the verity
15// target will be verified for integrity by Linux using the hash device and root
16// hash.
17//
18// Internally, the hash device is a Merkle tree of all the bytes in the data
19// device, layed out as layers of 'hash blocks'. Starting with data bytes, layers
20// are built recursively, with each layer's output hash blocks becoming the next
21// layer's data input, ending with the single root hash.
22//
23// For more information about the internals, see the Linux and cryptsetup
24// upstream code:
25//
26// https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity
27package verity
28
29import (
30 "bytes"
31 "crypto/rand"
32 "crypto/sha256"
33 "encoding/binary"
34 "encoding/hex"
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +020035 "errors"
Mateusz Zalega356b8962021-08-10 17:27:15 +020036 "fmt"
37 "io"
38 "strconv"
39 "strings"
40)
41
42// superblock represents data layout inside of a dm-verity hash block
43// device superblock. It follows a preexisting verity implementation:
44//
45// https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity#verity-superblock-format
46type superblock struct {
47 // signature is the magic signature of a verity hash device superblock,
48 // "verity\0\0".
49 signature [8]byte
50 // version specifies a superblock format. This structure describes version
51 // '1'.
52 version uint32
53 // hashType defaults to '1' outside Chrome OS, according to scarce dm-verity
54 // documentation.
55 hashType uint32
56 // uuid contains a UUID of the hash device.
57 uuid [16]byte
58 // algorithm stores an ASCII-encoded name of the hash function used.
59 algorithm [32]byte
60
61 // dataBlockSize specifies a size of a single data device block, in bytes.
62 dataBlockSize uint32
63 // hashBlockSize specifies a size of a single hash device block, in bytes.
64 hashBlockSize uint32
65 // dataBlocks contains a count of blocks available on the data device.
66 dataBlocks uint64
67
68 // saltSize encodes the size of hash block salt, up to the maximum of 256 bytes.
69 saltSize uint16
70
71 // padding
72 _ [6]byte
73 // exactly saltSize bytes of salt are prepended to data blocks before hashing.
74 saltBuffer [256]byte
75 // padding
76 _ [168]byte
77}
78
79// newSuperblock builds a dm-verity hash device superblock based on
80// hardcoded defaults. dataBlocks is the only field left for later
81// initialization.
82// It returns either a partially initialized superblock, or an error.
83func newSuperblock() (*superblock, error) {
84 // This implementation only handles SHA256-based verity hash images
85 // with a specific 4096-byte block size.
86 // Block sizes can be updated by adjusting the struct literal below.
87 // A change of a hashing algorithm would require a refactor of
88 // saltedDigest, and references to sha256.Size.
89 //
90 // Fill in the defaults (compare with superblock definition).
91 sb := superblock{
92 signature: [8]byte{'v', 'e', 'r', 'i', 't', 'y', 0, 0},
93 version: 1,
94 hashType: 1,
95 algorithm: [32]byte{'s', 'h', 'a', '2', '5', '6'},
96 saltSize: 64,
97 dataBlockSize: 4096,
98 hashBlockSize: 4096,
99 }
100
101 // Fill in the superblock UUID and cryptographic salt.
102 if _, err := rand.Read(sb.uuid[:]); err != nil {
103 return nil, fmt.Errorf("when generating UUID: %w", err)
104 }
105 if _, err := rand.Read(sb.saltBuffer[:]); err != nil {
106 return nil, fmt.Errorf("when generating salt: %w", err)
107 }
108
109 return &sb, nil
110}
111
112// salt returns a slice of sb.saltBuffer actually occupied by
113// salt bytes, of sb.saltSize length.
114func (sb *superblock) salt() []byte {
115 return sb.saltBuffer[:int(sb.saltSize)]
116}
117
118// algorithmName returns a name of the algorithm used to hash data block
119// digests.
120func (sb *superblock) algorithmName() string {
121 size := bytes.IndexByte(sb.algorithm[:], 0x00)
122 return string(sb.algorithm[:size])
123}
124
125// saltedDigest computes and returns a SHA256 sum of a block prepended
126// with a Superblock-defined salt.
127func (sb *superblock) saltedDigest(data []byte) (digest [sha256.Size]byte) {
128 h := sha256.New()
129 h.Write(sb.salt())
130 h.Write(data)
131 copy(digest[:], h.Sum(nil))
132 return
133}
134
135// dataBlocksPerHashBlock returns the count of hash operation outputs that
136// fit in a hash device block. This is also the amount of data device
137// blocks it takes to populate a hash device block.
138func (sb *superblock) dataBlocksPerHashBlock() uint64 {
139 return uint64(sb.hashBlockSize) / sha256.Size
140}
141
142// computeHashBlock reads at most sb.dataBlocksPerHashBlock blocks from
143// the given reader object, returning a padded hash block of length
144// defined by sb.hashBlockSize, the count of digests output, and an
145// error, if encountered.
146// In case a non-nil block is returned, it's guaranteed to contain at
147// least one hash. An io.EOF signals that there is no more to be read.
148func (sb *superblock) computeHashBlock(r io.Reader) ([]byte, uint64, error) {
149 // dcnt stores the total count of data blocks processed, which is the
150 // as the count of digests output.
151 var dcnt uint64
152 // Preallocate a whole hash block.
153 hblk := bytes.NewBuffer(make([]byte, 0, sb.hashBlockSize))
154
155 // For every data block, compute a hash and place it in hblk. Continue
156 // till EOF.
157 for b := uint64(0); b < sb.dataBlocksPerHashBlock(); b++ {
158 dbuf := make([]byte, sb.dataBlockSize)
159 // Attempt to read enough data blocks to make a complete hash block.
160 n, err := io.ReadFull(r, dbuf)
161 // If any data was read, make a hash and add it to the hash buffer.
162 if n != 0 {
163 hash := sb.saltedDigest(dbuf)
164 hblk.Write(hash[:])
165 dcnt++
166 }
167 // Handle the read errors.
Tim Windelschmidtd5f851b2024-04-23 14:59:37 +0200168 switch {
169 case err == nil:
170 case errors.Is(err, io.ErrUnexpectedEOF), errors.Is(err, io.EOF):
Mateusz Zalega356b8962021-08-10 17:27:15 +0200171 // io.ReadFull returns io.ErrUnexpectedEOF after a partial read,
172 // and io.EOF if no bytes were read. In both cases it's possible
173 // to end up with a partially filled hash block.
174 if hblk.Len() != 0 {
175 // Return a zero-padded hash block if any hashes were written
176 // to it, and signal that no more blocks can be built.
177 res := hblk.Bytes()
178 return res[:cap(res)], dcnt, io.EOF
179 }
180 // Return nil if the block doesn't contain any hashes.
181 return nil, 0, io.EOF
182 default:
183 // Wrap unhandled read errors.
184 return nil, 0, fmt.Errorf("while computing a hash block: %w", err)
185 }
186 }
187 // Return a completely filled hash block.
188 res := hblk.Bytes()
189 return res[:cap(res)], dcnt, nil
190}
191
192// WriteTo writes a verity superblock to a given writer object.
193// It returns the count of bytes written, and a write error, if
194// encountered.
195func (sb *superblock) WriteTo(w io.Writer) (int64, error) {
196 // Write the superblock.
197 if err := binary.Write(w, binary.LittleEndian, sb); err != nil {
198 return -1, fmt.Errorf("while writing a header: %w", err)
199 }
200
201 // Get the padding size by substracting current offset from a hash block
202 // size.
Tim Windelschmidt5e460a92024-04-11 01:33:09 +0200203 co := binary.Size(sb)
204 pbc := int(sb.hashBlockSize) - co
Mateusz Zalega356b8962021-08-10 17:27:15 +0200205 if pbc <= 0 {
206 return int64(co), fmt.Errorf("hash device block size smaller than dm-verity superblock")
207 }
208
209 // Write the padding bytes at the end of the block.
210 n, err := w.Write(bytes.Repeat([]byte{0}, pbc))
211 co += n
212 if err != nil {
213 return int64(co), fmt.Errorf("while writing padding: %w", err)
214 }
215 return int64(co), nil
216}
217
218// computeLevel produces a verity hash tree level based on data read from
219// a given reader object.
220// It returns a byte slice containing one or more hash blocks, or an
221// error.
222// BUG(mz): Current implementation requires a 1/128th of the data image
223// size to be allocatable on the heap.
224func (sb *superblock) computeLevel(r io.Reader) ([]byte, error) {
225 // hbuf will store all the computed hash blocks.
226 var hbuf bytes.Buffer
227 // Compute one or more hash blocks, reading all data available in the
228 // 'r' reader object, and write them into hbuf.
229 for {
230 hblk, _, err := sb.computeHashBlock(r)
231 if err != nil && err != io.EOF {
232 return nil, fmt.Errorf("while building a hash tree level: %w", err)
233 }
234 if hblk != nil {
235 _, err := hbuf.Write(hblk)
236 if err != nil {
237 return nil, fmt.Errorf("while writing to hash block buffer: %w", err)
238 }
239 }
240 if err == io.EOF {
241 break
242 }
243 }
244 return hbuf.Bytes(), nil
245}
246
247// hashTree stores hash tree levels, each level comprising one or more
248// Verity hash blocks. Levels are ordered from bottom to top.
249type hashTree [][]byte
250
251// push appends a level to the hash tree.
252func (ht *hashTree) push(nl []byte) {
253 *ht = append(*ht, nl)
254}
255
256// top returns the topmost level of the hash tree.
257func (ht *hashTree) top() []byte {
258 if len(*ht) == 0 {
259 return nil
260 }
261 return (*ht)[len(*ht)-1]
262}
263
264// WriteTo writes a verity-formatted hash tree to the given writer
265// object.
266// It returns a write error, if encountered.
267func (ht *hashTree) WriteTo(w io.Writer) (int64, error) {
268 // t keeps the count of bytes written to w.
269 var t int64
270 // Write the hash tree levels from top to bottom.
271 for l := len(*ht) - 1; l >= 0; l-- {
272 level := (*ht)[l]
273 // Call w.Write until a whole level is written.
274 for len(level) != 0 {
275 n, err := w.Write(level)
276 if err != nil {
277 return t, fmt.Errorf("while writing a level: %w", err)
278 }
279 level = level[n:]
280 t += int64(n)
281 }
282 }
283 return t, nil
284}
285
286// MappingTable aggregates data needed to generate a complete Verity
287// mapping table.
288type MappingTable struct {
289 // superblock defines the following elements of the mapping table:
290 // - data device block size
291 // - hash device block size
292 // - total count of data blocks
293 // - hash algorithm used
294 // - cryptographic salt used
295 superblock *superblock
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100296 // DataDevicePath is the filesystem path of the data device used as part
Mateusz Zalega356b8962021-08-10 17:27:15 +0200297 // of the Verity Device Mapper target.
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100298 DataDevicePath string
299 // HashDevicePath is the filesystem path of the hash device used as part
Mateusz Zalega356b8962021-08-10 17:27:15 +0200300 // of the Verity Device Mapper target.
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100301 HashDevicePath string
302 // HashStart marks the starting block of the Verity hash tree.
303 HashStart int64
Mateusz Zalega356b8962021-08-10 17:27:15 +0200304 // rootHash stores a cryptographic hash of the top hash tree block.
305 rootHash []byte
306}
307
308// VerityParameterList returns a list of Verity target parameters, ordered
309// as they would appear in a parameter string.
310func (t *MappingTable) VerityParameterList() []string {
311 return []string{
312 "1",
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100313 t.DataDevicePath,
314 t.HashDevicePath,
Mateusz Zalega356b8962021-08-10 17:27:15 +0200315 strconv.FormatUint(uint64(t.superblock.dataBlockSize), 10),
316 strconv.FormatUint(uint64(t.superblock.hashBlockSize), 10),
Tim Windelschmidt5e460a92024-04-11 01:33:09 +0200317 strconv.FormatUint(t.superblock.dataBlocks, 10),
318 strconv.FormatInt(t.HashStart, 10),
Mateusz Zalega356b8962021-08-10 17:27:15 +0200319 t.superblock.algorithmName(),
320 hex.EncodeToString(t.rootHash),
321 hex.EncodeToString(t.superblock.salt()),
322 }
323}
324
325// TargetParameters returns the mapping table as a list of Device Mapper
326// target parameters, ordered as they would appear in a parameter string
327// (see: String).
328func (t *MappingTable) TargetParameters() []string {
329 return append(
330 []string{
331 "0",
332 strconv.FormatUint(t.Length(), 10),
333 "verity",
334 },
335 t.VerityParameterList()...,
336 )
337}
338
339// String returns a string-formatted mapping table for use with Device
340// Mapper.
341// BUG(mz): unescaped whitespace can appear in block device paths
342func (t *MappingTable) String() string {
343 return strings.Join(t.TargetParameters(), " ")
344}
345
346// Length returns the data device length, represented as a number of
347// 512-byte sectors.
348func (t *MappingTable) Length() uint64 {
349 return t.superblock.dataBlocks * uint64(t.superblock.dataBlockSize) / 512
350}
351
352// encoder transforms data blocks written into it into a verity hash
353// tree. It writes out the hash tree only after Close is called on it.
354type encoder struct {
355 // out is the writer object Encoder will write to.
356 out io.Writer
357 // writeSb, if true, will cause a Verity superblock to be written to the
358 // writer object.
359 writeSb bool
360 // sb contains the most of information needed to build a mapping table.
361 sb *superblock
362 // bottom stands for the bottom level of the hash tree. It contains
363 // complete hash blocks of data written to the encoder.
364 bottom bytes.Buffer
365 // dataBuffer stores incoming data for later processing.
366 dataBuffer bytes.Buffer
367 // rootHash stores the verity root hash set on Close.
368 rootHash []byte
369}
370
371// computeHashTree builds a complete hash tree based on the encoder's
372// state. Levels are appended to the returned hash tree starting from the
373// bottom, with the top level written last.
374// e.sb.dataBlocks is set according to the bottom level's length, which
375// must be divisible by e.sb.hashBlockSize.
376// e.rootHash is set on success.
377// It returns an error, if encountered.
378func (e *encoder) computeHashTree() (*hashTree, error) {
379 // Put b at the bottom of the tree. Don't perform a deep copy.
380 ht := hashTree{e.bottom.Bytes()}
381
382 // Other levels are built by hashing the hash blocks comprising a level
383 // below.
384 for {
385 if len(ht.top()) == int(e.sb.hashBlockSize) {
386 // The last level to compute has a size of exactly one hash block.
387 // That's the root level. Its hash serves as a cryptographic root of
388 // trust and is saved into a encoder for later use.
389 // In case the bottom level consists of only one hash block, no more
390 // levels are computed.
391 sd := e.sb.saltedDigest(ht.top())
392 e.rootHash = sd[:]
393 return &ht, nil
394 }
395
396 // Create the next level by hashing the previous one.
397 nl, err := e.sb.computeLevel(bytes.NewReader(ht.top()))
398 if err != nil {
399 return nil, fmt.Errorf("while computing a level: %w", err)
400 }
401 // Append the resulting next level to a tree.
402 ht.push(nl)
403 }
404}
405
406// processDataBuffer processes data blocks contained in e.dataBuffer
407// until no more data is available to form a completely filled hash block.
408// If 'incomplete' is true, all remaining data in e.dataBuffer will be
409// processed, producing a terminating incomplete block.
410// It returns the count of data blocks processed, or an error, if
411// encountered.
412func (e *encoder) processDataBuffer(incomplete bool) (uint64, error) {
413 // tdcnt stores the total count of data blocks processed.
414 var tdcnt uint64
415 // Compute the count of bytes needed to produce a complete hash block.
416 bph := e.sb.dataBlocksPerHashBlock() * uint64(e.sb.dataBlockSize)
417
418 // Iterate until no more data is available in e.dbuf.
419 for uint64(e.dataBuffer.Len()) >= bph || incomplete && e.dataBuffer.Len() != 0 {
420 hb, dcnt, err := e.sb.computeHashBlock(&e.dataBuffer)
421 if err != nil && err != io.EOF {
422 return 0, fmt.Errorf("while processing a data buffer: %w", err)
423 }
424 // Increment the total count of data blocks processed.
425 tdcnt += dcnt
426 // Write the resulting hash block into the level-zero buffer.
427 e.bottom.Write(hb[:])
428 }
429 return tdcnt, nil
430}
431
432// NewEncoder returns a fully initialized encoder, or an error. The
433// encoder will write to the given io.Writer object.
434// A verity superblock will be written, preceding the hash tree, if
435// writeSb is true.
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100436func NewEncoder(out io.Writer, dataBlockSize, hashBlockSize uint32, writeSb bool) (*encoder, error) {
Mateusz Zalega356b8962021-08-10 17:27:15 +0200437 sb, err := newSuperblock()
438 if err != nil {
439 return nil, fmt.Errorf("while creating a superblock: %w", err)
440 }
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100441 sb.dataBlockSize = dataBlockSize
442 sb.hashBlockSize = hashBlockSize
Mateusz Zalega356b8962021-08-10 17:27:15 +0200443
444 e := encoder{
445 out: out,
446 writeSb: writeSb,
447 sb: sb,
448 }
449 return &e, nil
450}
451
452// Write hashes raw data to form the bottom hash tree level.
453// It returns the number of bytes written, and an error, if encountered.
454func (e *encoder) Write(data []byte) (int, error) {
455 // Copy the input into the data buffer.
456 n, _ := e.dataBuffer.Write(data)
457 // Process only enough data to form a complete hash block. This may
458 // leave excess data in e.dbuf to be processed later on.
459 dcnt, err := e.processDataBuffer(false)
460 if err != nil {
461 return n, fmt.Errorf("while processing the data buffer: %w", err)
462 }
463 // Update the superblock with the count of data blocks written.
464 e.sb.dataBlocks += dcnt
465 return n, nil
466}
467
468// Close builds a complete hash tree based on cached bottom level blocks,
469// then writes it to a preconfigured io.Writer object. A Verity superblock
470// is written, if e.writeSb is true. No data, nor the superblock is written
471// if the encoder is empty.
472// It returns an error, if one was encountered.
473func (e *encoder) Close() error {
474 // Process all buffered data, including data blocks that may not form
475 // a complete hash block.
476 dcnt, err := e.processDataBuffer(true)
477 if err != nil {
478 return fmt.Errorf("while processing the data buffer: %w", err)
479 }
480 // Update the superblock with the count of data blocks written.
481 e.sb.dataBlocks += dcnt
482
483 // Don't write anything if nothing was written to the encoder.
484 if e.bottom.Len() == 0 {
485 return nil
486 }
487
488 // Compute remaining hash tree levels based on the bottom level: e.bottom.
489 ht, err := e.computeHashTree()
490 if err != nil {
491 return fmt.Errorf("while encoding a hash tree: %w", err)
492 }
493
494 // Write the Verity superblock if the encoder was configured to do so.
495 if e.writeSb {
496 if _, err = e.sb.WriteTo(e.out); err != nil {
497 return fmt.Errorf("while writing a superblock: %w", err)
498 }
499 }
500 // Write the hash tree.
501 _, err = ht.WriteTo(e.out)
502 if err != nil {
503 return fmt.Errorf("while writing a hash tree: %w", err)
504 }
505
Mateusz Zalega356b8962021-08-10 17:27:15 +0200506 return nil
507}
508
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100509// MappingTable returns a complete, string-convertible Verity target mapping
510// table for use with Device Mapper, or an error. Close must be called on the
511// encoder before calling this function. dataDevicePath, hashDevicePath, and
512// hashStart parameters are parts of the mapping table. See:
513// https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html
514func (e *encoder) MappingTable(dataDevicePath, hashDevicePath string, hashStart int64) (*MappingTable, error) {
Mateusz Zalega356b8962021-08-10 17:27:15 +0200515 if e.rootHash == nil {
516 if e.bottom.Len() != 0 {
Tim Windelschmidt73e98822024-04-18 23:13:49 +0200517 return nil, fmt.Errorf("encoder wasn't closed")
Mateusz Zalega356b8962021-08-10 17:27:15 +0200518 }
Tim Windelschmidt73e98822024-04-18 23:13:49 +0200519 return nil, fmt.Errorf("encoder is empty")
Mateusz Zalega356b8962021-08-10 17:27:15 +0200520 }
521
Mateusz Zalega356b8962021-08-10 17:27:15 +0200522 if e.writeSb {
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100523 // Account for the superblock.
524 hashStart += 1
Mateusz Zalega356b8962021-08-10 17:27:15 +0200525 }
526 return &MappingTable{
527 superblock: e.sb,
Mateusz Zalegaba1da9d2022-01-25 19:12:02 +0100528 DataDevicePath: dataDevicePath,
529 HashDevicePath: hashDevicePath,
530 HashStart: hashStart,
Mateusz Zalega356b8962021-08-10 17:27:15 +0200531 rootHash: e.rootHash,
532 }, nil
533}