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