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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
// SPDX-License-Identifier: GPL-2.0+
/*
* Bounds-check tests for vbe_read_fit()
*
* vbe_read_fit() pulls a firmware-phase FIT from a trusted firmware area
* on a block device. The external-data location and size carried in the
* FIT image node are attacker-controllable when the firmware area is on
* mutable boot media, so vbe_read_fit() must reject FITs whose external
* data extends past @area_size before issuing the follow-up blk_read().
*
* These tests build small synthetic FITs with deliberately out-of-range
* values and confirm vbe_read_fit() returns -E2BIG for each.
*
* Copyright 2026 Canonical Ltd.
* Written by Aristo Chen <[email protected]>
*/
#include <blk.h>
#include <dm.h>
#include <image.h>
#include <memalign.h>
#include <mmc.h>
#include <test/test.h>
#include <test/ut.h>
#include <linux/libfdt.h>
#include "bootstd_common.h"
#include "../../boot/vbe_common.h"
/*
* The synthetic FIT is written to mmc1 starting at block TEST_FIT_BLK.
* bootstd_setup_for_tests() uses blocks 4 and 6 (see bootstd_common.h);
* block 16 leaves a comfortable gap.
*/
#define TEST_FIT_BLK 16
#define TEST_FIT_OFF ((ulong)TEST_FIT_BLK * MMC_MAX_BLOCK_LEN)
#define TEST_AREA_SIZE 0x1000
/**
* build_fit() - Build a minimal external-data FIT for vbe_read_fit()
*
* The FIT advertises a single firmware image whose @data-position and
* @data-size are passed in directly. Both values are attacker-controlled
* in the real threat model.
*
* @buf: Destination buffer (must be at least 512 bytes)
* @buf_size: Size of @buf
* @data_position: Value written to the image's data-position property
* @data_size: Value written to the image's data-size property
* Returns: 0 on success, libfdt error otherwise
*/
static int build_fit(void *buf, size_t buf_size, u32 data_position,
u32 data_size)
{
int ret;
ret = fdt_create(buf, buf_size);
if (ret)
return ret;
ret = fdt_finish_reservemap(buf);
if (ret)
return ret;
ret = fdt_begin_node(buf, "");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_DESC_PROP, "vbe-read-fit test");
if (ret)
return ret;
ret = fdt_property_u32(buf, FIT_TIMESTAMP_PROP, 0);
if (ret)
return ret;
ret = fdt_begin_node(buf, "images");
if (ret)
return ret;
ret = fdt_begin_node(buf, "u-boot");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_DESC_PROP, "U-Boot");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_TYPE_PROP, "firmware");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_ARCH_PROP, "sandbox");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_OS_PROP, "u-boot");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_PHASE_PROP, "u-boot");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_COMP_PROP, "none");
if (ret)
return ret;
ret = fdt_property_u32(buf, FIT_DATA_POSITION_PROP, data_position);
if (ret)
return ret;
ret = fdt_property_u32(buf, FIT_DATA_SIZE_PROP, data_size);
if (ret)
return ret;
ret = fdt_end_node(buf); /* u-boot */
if (ret)
return ret;
ret = fdt_end_node(buf); /* images */
if (ret)
return ret;
ret = fdt_begin_node(buf, "configurations");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_DEFAULT_PROP, "conf-1");
if (ret)
return ret;
ret = fdt_begin_node(buf, "conf-1");
if (ret)
return ret;
ret = fdt_property_string(buf, "compatible", "sandbox");
if (ret)
return ret;
ret = fdt_property_string(buf, FIT_FIRMWARE_PROP, "u-boot");
if (ret)
return ret;
ret = fdt_end_node(buf); /* conf-1 */
if (ret)
return ret;
ret = fdt_end_node(buf); /* configurations */
if (ret)
return ret;
ret = fdt_end_node(buf); /* root */
if (ret)
return ret;
return fdt_finish(buf);
}
/**
* place_fit_on_mmc() - Write a synthetic FIT to mmc1 and return its blk dev
*
* @uts: Unit test state
* @fit: FIT image to write
* @blkp: On success, receives the block udevice for mmc1
* Returns: 0 on success, -ve on error
*/
static int place_fit_on_mmc(struct unit_test_state *uts, const void *fit,
struct udevice **blkp)
{
ALLOC_CACHE_ALIGN_BUFFER(u8, blkbuf, MMC_MAX_BLOCK_LEN);
struct udevice *mmc;
struct blk_desc *desc;
size_t fit_size = fdt_totalsize(fit);
size_t pos;
int blknum = TEST_FIT_BLK;
ut_assertok(uclass_get_device(UCLASS_MMC, 1, &mmc));
desc = blk_get_by_device(mmc);
if (!desc)
return log_msg_ret("desc", -ENODEV);
for (pos = 0; pos < fit_size; pos += MMC_MAX_BLOCK_LEN, blknum++) {
size_t this_blk = min(fit_size - pos,
(size_t)MMC_MAX_BLOCK_LEN);
memset(blkbuf, '\0', MMC_MAX_BLOCK_LEN);
memcpy(blkbuf, (const u8 *)fit + pos, this_blk);
if (blk_dwrite(desc, blknum, 1, blkbuf) != 1)
return log_msg_ret("wr", -EIO);
}
*blkp = desc->bdev;
return 0;
}
/*
* data-position points past area_size: vbe_read_fit() must reject the
* FIT with -E2BIG before issuing the external-data blk_read().
*/
static int vbe_read_fit_oob_position(struct unit_test_state *uts)
{
u8 fit[1024] __aligned(8);
struct udevice *blk;
ulong load_addr = 0, len = 0;
char *name = NULL;
int ret;
ut_assertok(build_fit(fit, sizeof(fit),
TEST_AREA_SIZE + 0x10, 0x40));
ut_assertok(place_fit_on_mmc(uts, fit, &blk));
ret = vbe_read_fit(blk, TEST_FIT_OFF, TEST_AREA_SIZE,
NULL, &load_addr, &len, &name);
ut_asserteq(-E2BIG, ret);
return 0;
}
BOOTSTD_TEST(vbe_read_fit_oob_position, UTF_DM | UTF_SCAN_FDT);
/*
* data-position is inside the area but data-size pushes the end past
* area_size: vbe_read_fit() must reject the FIT with -E2BIG.
*/
static int vbe_read_fit_oversize_data(struct unit_test_state *uts)
{
u8 fit[1024] __aligned(8);
struct udevice *blk;
ulong load_addr = 0, len = 0;
char *name = NULL;
int ret;
ut_assertok(build_fit(fit, sizeof(fit),
0x400, TEST_AREA_SIZE));
ut_assertok(place_fit_on_mmc(uts, fit, &blk));
ret = vbe_read_fit(blk, TEST_FIT_OFF, TEST_AREA_SIZE,
NULL, &load_addr, &len, &name);
ut_asserteq(-E2BIG, ret);
return 0;
}
BOOTSTD_TEST(vbe_read_fit_oversize_data, UTF_DM | UTF_SCAN_FDT);
|