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