aboutsummaryrefslogtreecommitdiffstats
path: root/meta-oe/classes/image_types_verity.bbclass
blob: b42217c453f23550c62a6836859301a1cb57170c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# SPDX-License-Identifier: MIT
#
# Copyright Pengutronix <yocto@pengutronix.de>
#

# Support generating a dm-verity image and the parameters required to assemble
# the corresponding table for the device-mapper driver. The latter will be
# stored in the file ${DEPLOY_DIR_IMAGE}/<IMAGE_LINK_NAME>.verity-params. Note
# that in the resulting image the hash tree data is appended to the contents of
# the original image without an explicit superblock to keep things simple and
# compact.
#
# The above mentioned parameter file can be sourced by a shell to finally create
# the desired blockdevice via "dmsetup" (found in meta-oe's recipe
# "libdevmapper"), e.g.
#
#   . <IMAGE_LINK_NAME>.verity-params
#   dmsetup create <dm_dev_name> --readonly --table "0 $VERITY_DATA_SECTORS \
#       verity 1 <dev> <hash_dev> \
#       $VERITY_DATA_BLOCK_SIZE  $VERITY_HASH_BLOCK_SIZE \
#       $VERITY_DATA_BLOCKS  $VERITY_DATA_BLOCKS \
#       $VERITY_HASH_ALGORITHM  $VERITY_ROOT_HASH  $VERITY_SALT \
#       1 ignore_zero_blocks"
#
# As the hash tree data is found at the end of the image, <dev> and <hash_dev>
# should be the same blockdevice in the command shown above while <dm_dev_name>
# is the name of the to be created dm-verity-device.
#
# The root hash is calculated using a salt to make attacks more difficult. Thus,
# please grant each image recipe its own salt which could be generated e.g. via
#
#   dd if=/dev/random bs=1k count=1 | sha256sum
#
# and assign it to the parameter VERITY_SALT.

inherit image-artifact-names

do_image_verity[depends] += "cryptsetup-native:do_populate_sysroot"

CLASS_VERITY_SALT = "4e5f0d9b6ccac5e843598d4e4545046232b48451a399acb2106822b43679b375"
VERITY_SALT ?= "${CLASS_VERITY_SALT}"
VERITY_BLOCK_SIZE ?= "4096"
VERITY_IMAGE_FSTYPE ?= "ext4"
VERITY_IMAGE_SUFFIX ?= ".verity"
VERITY_INPUT_IMAGE ?= "${IMGDEPLOYDIR}/${IMAGE_LINK_NAME}.${VERITY_IMAGE_FSTYPE}"

IMAGE_TYPEDEP:verity = "${VERITY_IMAGE_FSTYPE}"
IMAGE_TYPES_MASKED += "verity"

python __anonymous() {
    if 'verity' not in d.getVar('IMAGE_FSTYPES'):
        return

    dep_task = 'do_image_{}'.format(d.getVar('VERITY_IMAGE_FSTYPE').replace('-', '_'))
    bb.build.addtask('do_image_verity', 'do_image_complete', dep_task, d)
}

python do_image_verity () {
    import os
    import subprocess
    import shutil

    link = d.getVar('VERITY_INPUT_IMAGE')
    image = os.path.realpath(link)

    verity_image_suffix = d.getVar('VERITY_IMAGE_SUFFIX')
    verity = '{}{}'.format(image, verity_image_suffix)

    # For better readability the parameter VERITY_BLOCK_SIZE is specified in
    # bytes. It must be a multiple of the logical sector size which is 512 bytes
    # in Linux. Make sure that this is the case as otherwise the resulting
    # issues would be hard to debug later.
    block_size = int(d.getVar('VERITY_BLOCK_SIZE'))
    if block_size % 512 != 0:
        bb.fatal("VERITY_BLOCK_SIZE must be a multiple of 512!")

    salt = d.getVar('VERITY_SALT')
    if salt == d.getVar('CLASS_VERITY_SALT'):
        bb.warn("Please overwrite VERITY_SALT with an image specific one!")

    shutil.copyfile(image, verity)

    data_size_blocks, data_size_rest = divmod(os.stat(verity).st_size, block_size)
    data_blocks = data_size_blocks + (1 if data_size_rest else 0)
    data_size = data_blocks * block_size

    bb.debug(1, f"data_size_blocks: {data_size_blocks}, {data_size_rest}")
    bb.debug(1, f"data_size: {data_size}")

    # Create verity image
    try:
        output = subprocess.check_output([
            'veritysetup', 'format',
            '--no-superblock',
            '--salt={}'.format(salt),
            '--data-blocks={}'.format(data_blocks),
            '--data-block-size={}'.format(block_size),
            '--hash-block-size={}'.format(block_size),
            '--hash-offset={}'.format(data_size),
            verity, verity,
            ])
    except subprocess.CalledProcessError as err:
        bb.fatal('%s returned with %s (%s)' % (err.cmd, err.returncode, err.output))

    try:
        with open(image + '.verity-info', 'wb') as f:
            f.write(output)
    except Exception as err:
        bb.fatal('Unexpected error %s' % err)

    # Create verity params
    params = []
    for line in output.decode('ASCII').splitlines():
        if not ':' in line:
            continue
        k, v = line.split(':', 1)
        k = k.strip().upper().replace(' ', '_')
        v = v.strip()
        bb.debug(1, f"{k} {v}")
        params.append('VERITY_{}={}'.format(k, v))

    params.append('VERITY_DATA_SECTORS={}'.format(data_size//512))

    try:
        with open(image + '.verity-params', 'w') as f:
            f.write('\n'.join(params))
    except Exception as err:
        bb.fatal('Unexpected error %s' % err)

    # Create symlinks
    for suffix in [ verity_image_suffix, '.verity-info', '.verity-params' ]:
        try:
            os.remove(link + suffix)
        except FileNotFoundError:
            pass
        os.symlink(os.path.basename(image) + suffix, link + suffix)
}