]>
Commit | Line | Data |
---|---|---|
b30be4dc RH |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright (C) 2020 Arm Limited | |
4 | * | |
5 | * Based on arch/arm64/kernel/machine_kexec_file.c: | |
6 | * Copyright (C) 2018 Linaro Limited | |
7 | * | |
8 | * And arch/powerpc/kexec/file_load.c: | |
9 | * Copyright (C) 2016 IBM Corporation | |
10 | */ | |
11 | ||
b69a2afd | 12 | #include <linux/ima.h> |
b30be4dc RH |
13 | #include <linux/kernel.h> |
14 | #include <linux/kexec.h> | |
fee3ff99 | 15 | #include <linux/memblock.h> |
b30be4dc RH |
16 | #include <linux/libfdt.h> |
17 | #include <linux/of.h> | |
18 | #include <linux/of_fdt.h> | |
19 | #include <linux/random.h> | |
8587ca6f | 20 | #include <linux/slab.h> |
b30be4dc RH |
21 | #include <linux/types.h> |
22 | ||
b30be4dc RH |
23 | #define RNG_SEED_SIZE 128 |
24 | ||
25 | /* | |
26 | * Additional space needed for the FDT buffer so that we can add initrd, | |
27 | * bootargs, kaslr-seed, rng-seed, useable-memory-range and elfcorehdr. | |
28 | */ | |
29 | #define FDT_EXTRA_SPACE 0x1000 | |
30 | ||
31 | /** | |
32 | * fdt_find_and_del_mem_rsv - delete memory reservation with given address and size | |
33 | * | |
34 | * @fdt: Flattened device tree for the current kernel. | |
35 | * @start: Starting address of the reserved memory. | |
36 | * @size: Size of the reserved memory. | |
37 | * | |
38 | * Return: 0 on success, or negative errno on error. | |
39 | */ | |
40 | static int fdt_find_and_del_mem_rsv(void *fdt, unsigned long start, unsigned long size) | |
41 | { | |
42 | int i, ret, num_rsvs = fdt_num_mem_rsv(fdt); | |
43 | ||
44 | for (i = 0; i < num_rsvs; i++) { | |
45 | u64 rsv_start, rsv_size; | |
46 | ||
47 | ret = fdt_get_mem_rsv(fdt, i, &rsv_start, &rsv_size); | |
48 | if (ret) { | |
49 | pr_err("Malformed device tree.\n"); | |
50 | return -EINVAL; | |
51 | } | |
52 | ||
53 | if (rsv_start == start && rsv_size == size) { | |
54 | ret = fdt_del_mem_rsv(fdt, i); | |
55 | if (ret) { | |
56 | pr_err("Error deleting device tree reservation.\n"); | |
57 | return -EINVAL; | |
58 | } | |
59 | ||
60 | return 0; | |
61 | } | |
62 | } | |
63 | ||
64 | return -ENOENT; | |
65 | } | |
66 | ||
fee3ff99 LR |
67 | /** |
68 | * get_addr_size_cells - Get address and size of root node | |
69 | * | |
70 | * @addr_cells: Return address of the root node | |
71 | * @size_cells: Return size of the root node | |
72 | * | |
73 | * Return: 0 on success, or negative errno on error. | |
74 | */ | |
75 | static int get_addr_size_cells(int *addr_cells, int *size_cells) | |
76 | { | |
77 | struct device_node *root; | |
78 | ||
79 | root = of_find_node_by_path("/"); | |
80 | if (!root) | |
81 | return -EINVAL; | |
82 | ||
83 | *addr_cells = of_n_addr_cells(root); | |
84 | *size_cells = of_n_size_cells(root); | |
85 | ||
86 | of_node_put(root); | |
87 | ||
88 | return 0; | |
89 | } | |
90 | ||
91 | /** | |
92 | * do_get_kexec_buffer - Get address and size of device tree property | |
93 | * | |
94 | * @prop: Device tree property | |
95 | * @len: Size of @prop | |
96 | * @addr: Return address of the node | |
97 | * @size: Return size of the node | |
98 | * | |
99 | * Return: 0 on success, or negative errno on error. | |
100 | */ | |
101 | static int do_get_kexec_buffer(const void *prop, int len, unsigned long *addr, | |
102 | size_t *size) | |
103 | { | |
104 | int ret, addr_cells, size_cells; | |
105 | ||
106 | ret = get_addr_size_cells(&addr_cells, &size_cells); | |
107 | if (ret) | |
108 | return ret; | |
109 | ||
110 | if (len < 4 * (addr_cells + size_cells)) | |
111 | return -ENOENT; | |
112 | ||
113 | *addr = of_read_number(prop, addr_cells); | |
114 | *size = of_read_number(prop + 4 * addr_cells, size_cells); | |
115 | ||
116 | return 0; | |
117 | } | |
118 | ||
b69a2afd | 119 | #ifdef CONFIG_HAVE_IMA_KEXEC |
fee3ff99 LR |
120 | /** |
121 | * ima_get_kexec_buffer - get IMA buffer from the previous kernel | |
122 | * @addr: On successful return, set to point to the buffer contents. | |
123 | * @size: On successful return, set to the buffer size. | |
124 | * | |
125 | * Return: 0 on success, negative errno on error. | |
126 | */ | |
b69a2afd | 127 | int __init ima_get_kexec_buffer(void **addr, size_t *size) |
fee3ff99 LR |
128 | { |
129 | int ret, len; | |
130 | unsigned long tmp_addr; | |
cbf9c4b9 | 131 | unsigned long start_pfn, end_pfn; |
fee3ff99 LR |
132 | size_t tmp_size; |
133 | const void *prop; | |
134 | ||
fee3ff99 LR |
135 | prop = of_get_property(of_chosen, "linux,ima-kexec-buffer", &len); |
136 | if (!prop) | |
137 | return -ENOENT; | |
138 | ||
139 | ret = do_get_kexec_buffer(prop, len, &tmp_addr, &tmp_size); | |
140 | if (ret) | |
141 | return ret; | |
142 | ||
cbf9c4b9 VJ |
143 | /* Do some sanity on the returned size for the ima-kexec buffer */ |
144 | if (!tmp_size) | |
145 | return -ENOENT; | |
146 | ||
147 | /* | |
148 | * Calculate the PFNs for the buffer and ensure | |
149 | * they are with in addressable memory. | |
150 | */ | |
151 | start_pfn = PHYS_PFN(tmp_addr); | |
152 | end_pfn = PHYS_PFN(tmp_addr + tmp_size - 1); | |
153 | if (!page_is_ram(start_pfn) || !page_is_ram(end_pfn)) { | |
154 | pr_warn("IMA buffer at 0x%lx, size = 0x%zx beyond memory\n", | |
155 | tmp_addr, tmp_size); | |
156 | return -EINVAL; | |
157 | } | |
158 | ||
fee3ff99 LR |
159 | *addr = __va(tmp_addr); |
160 | *size = tmp_size; | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
165 | /** | |
166 | * ima_free_kexec_buffer - free memory used by the IMA buffer | |
167 | */ | |
b69a2afd | 168 | int __init ima_free_kexec_buffer(void) |
fee3ff99 LR |
169 | { |
170 | int ret; | |
171 | unsigned long addr; | |
172 | size_t size; | |
173 | struct property *prop; | |
174 | ||
fee3ff99 LR |
175 | prop = of_find_property(of_chosen, "linux,ima-kexec-buffer", NULL); |
176 | if (!prop) | |
177 | return -ENOENT; | |
178 | ||
179 | ret = do_get_kexec_buffer(prop->value, prop->length, &addr, &size); | |
180 | if (ret) | |
181 | return ret; | |
182 | ||
183 | ret = of_remove_property(of_chosen, prop); | |
184 | if (ret) | |
185 | return ret; | |
186 | ||
f0362a25 RR |
187 | memblock_free_late(addr, size); |
188 | return 0; | |
fee3ff99 | 189 | } |
b69a2afd | 190 | #endif |
fee3ff99 LR |
191 | |
192 | /** | |
193 | * remove_ima_buffer - remove the IMA buffer property and reservation from @fdt | |
194 | * | |
195 | * @fdt: Flattened Device Tree to update | |
196 | * @chosen_node: Offset to the chosen node in the device tree | |
197 | * | |
198 | * The IMA measurement buffer is of no use to a subsequent kernel, so we always | |
199 | * remove it from the device tree. | |
200 | */ | |
201 | static void remove_ima_buffer(void *fdt, int chosen_node) | |
202 | { | |
203 | int ret, len; | |
204 | unsigned long addr; | |
205 | size_t size; | |
206 | const void *prop; | |
207 | ||
208 | if (!IS_ENABLED(CONFIG_HAVE_IMA_KEXEC)) | |
209 | return; | |
210 | ||
211 | prop = fdt_getprop(fdt, chosen_node, "linux,ima-kexec-buffer", &len); | |
212 | if (!prop) | |
213 | return; | |
214 | ||
215 | ret = do_get_kexec_buffer(prop, len, &addr, &size); | |
216 | fdt_delprop(fdt, chosen_node, "linux,ima-kexec-buffer"); | |
217 | if (ret) | |
218 | return; | |
219 | ||
220 | ret = fdt_find_and_del_mem_rsv(fdt, addr, size); | |
221 | if (!ret) | |
222 | pr_debug("Removed old IMA buffer reservation.\n"); | |
223 | } | |
224 | ||
225 | #ifdef CONFIG_IMA_KEXEC | |
fee3ff99 LR |
226 | /** |
227 | * setup_ima_buffer - add IMA buffer information to the fdt | |
228 | * @image: kexec image being loaded. | |
229 | * @fdt: Flattened device tree for the next kernel. | |
230 | * @chosen_node: Offset to the chosen node. | |
231 | * | |
232 | * Return: 0 on success, or negative errno on error. | |
233 | */ | |
234 | static int setup_ima_buffer(const struct kimage *image, void *fdt, | |
235 | int chosen_node) | |
236 | { | |
28db15d4 | 237 | int ret; |
fee3ff99 LR |
238 | |
239 | if (!image->ima_buffer_size) | |
240 | return 0; | |
241 | ||
28db15d4 LR |
242 | ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, |
243 | "linux,ima-kexec-buffer", | |
244 | image->ima_buffer_addr, | |
245 | image->ima_buffer_size); | |
fee3ff99 LR |
246 | if (ret < 0) |
247 | return -EINVAL; | |
248 | ||
249 | ret = fdt_add_mem_rsv(fdt, image->ima_buffer_addr, | |
250 | image->ima_buffer_size); | |
251 | if (ret) | |
252 | return -EINVAL; | |
253 | ||
254 | pr_debug("IMA buffer at 0x%llx, size = 0x%zx\n", | |
255 | image->ima_buffer_addr, image->ima_buffer_size); | |
256 | ||
257 | return 0; | |
258 | } | |
259 | #else /* CONFIG_IMA_KEXEC */ | |
260 | static inline int setup_ima_buffer(const struct kimage *image, void *fdt, | |
261 | int chosen_node) | |
262 | { | |
263 | return 0; | |
264 | } | |
265 | #endif /* CONFIG_IMA_KEXEC */ | |
266 | ||
b30be4dc RH |
267 | /* |
268 | * of_kexec_alloc_and_setup_fdt - Alloc and setup a new Flattened Device Tree | |
269 | * | |
270 | * @image: kexec image being loaded. | |
271 | * @initrd_load_addr: Address where the next initrd will be loaded. | |
272 | * @initrd_len: Size of the next initrd, or 0 if there will be none. | |
273 | * @cmdline: Command line for the next kernel, or NULL if there will | |
274 | * be none. | |
275 | * @extra_fdt_size: Additional size for the new FDT buffer. | |
276 | * | |
277 | * Return: fdt on success, or NULL errno on error. | |
278 | */ | |
279 | void *of_kexec_alloc_and_setup_fdt(const struct kimage *image, | |
280 | unsigned long initrd_load_addr, | |
281 | unsigned long initrd_len, | |
282 | const char *cmdline, size_t extra_fdt_size) | |
283 | { | |
284 | void *fdt; | |
e553ad8d | 285 | int ret, chosen_node, len; |
b30be4dc RH |
286 | const void *prop; |
287 | size_t fdt_size; | |
288 | ||
289 | fdt_size = fdt_totalsize(initial_boot_params) + | |
290 | (cmdline ? strlen(cmdline) : 0) + | |
291 | FDT_EXTRA_SPACE + | |
292 | extra_fdt_size; | |
293 | fdt = kvmalloc(fdt_size, GFP_KERNEL); | |
294 | if (!fdt) | |
295 | return NULL; | |
296 | ||
297 | ret = fdt_open_into(initial_boot_params, fdt, fdt_size); | |
298 | if (ret < 0) { | |
299 | pr_err("Error %d setting up the new device tree.\n", ret); | |
300 | goto out; | |
301 | } | |
302 | ||
303 | /* Remove memory reservation for the current device tree. */ | |
304 | ret = fdt_find_and_del_mem_rsv(fdt, __pa(initial_boot_params), | |
305 | fdt_totalsize(initial_boot_params)); | |
306 | if (ret == -EINVAL) { | |
307 | pr_err("Error removing memory reservation.\n"); | |
308 | goto out; | |
309 | } | |
310 | ||
311 | chosen_node = fdt_path_offset(fdt, "/chosen"); | |
312 | if (chosen_node == -FDT_ERR_NOTFOUND) | |
313 | chosen_node = fdt_add_subnode(fdt, fdt_path_offset(fdt, "/"), | |
314 | "chosen"); | |
315 | if (chosen_node < 0) { | |
316 | ret = chosen_node; | |
317 | goto out; | |
318 | } | |
319 | ||
cc6ef3d1 | 320 | ret = fdt_delprop(fdt, chosen_node, "linux,elfcorehdr"); |
b30be4dc RH |
321 | if (ret && ret != -FDT_ERR_NOTFOUND) |
322 | goto out; | |
cc6ef3d1 | 323 | ret = fdt_delprop(fdt, chosen_node, "linux,usable-memory-range"); |
b30be4dc RH |
324 | if (ret && ret != -FDT_ERR_NOTFOUND) |
325 | goto out; | |
326 | ||
327 | /* Did we boot using an initrd? */ | |
e553ad8d | 328 | prop = fdt_getprop(fdt, chosen_node, "linux,initrd-start", &len); |
b30be4dc RH |
329 | if (prop) { |
330 | u64 tmp_start, tmp_end, tmp_size; | |
331 | ||
e553ad8d | 332 | tmp_start = of_read_number(prop, len / 4); |
b30be4dc | 333 | |
e553ad8d | 334 | prop = fdt_getprop(fdt, chosen_node, "linux,initrd-end", &len); |
b30be4dc RH |
335 | if (!prop) { |
336 | ret = -EINVAL; | |
337 | goto out; | |
338 | } | |
339 | ||
e553ad8d | 340 | tmp_end = of_read_number(prop, len / 4); |
b30be4dc RH |
341 | |
342 | /* | |
343 | * kexec reserves exact initrd size, while firmware may | |
344 | * reserve a multiple of PAGE_SIZE, so check for both. | |
345 | */ | |
346 | tmp_size = tmp_end - tmp_start; | |
347 | ret = fdt_find_and_del_mem_rsv(fdt, tmp_start, tmp_size); | |
348 | if (ret == -ENOENT) | |
349 | ret = fdt_find_and_del_mem_rsv(fdt, tmp_start, | |
350 | round_up(tmp_size, PAGE_SIZE)); | |
351 | if (ret == -EINVAL) | |
352 | goto out; | |
353 | } | |
354 | ||
355 | /* add initrd-* */ | |
356 | if (initrd_load_addr) { | |
cc6ef3d1 | 357 | ret = fdt_setprop_u64(fdt, chosen_node, "linux,initrd-start", |
b30be4dc RH |
358 | initrd_load_addr); |
359 | if (ret) | |
360 | goto out; | |
361 | ||
cc6ef3d1 | 362 | ret = fdt_setprop_u64(fdt, chosen_node, "linux,initrd-end", |
b30be4dc RH |
363 | initrd_load_addr + initrd_len); |
364 | if (ret) | |
365 | goto out; | |
366 | ||
367 | ret = fdt_add_mem_rsv(fdt, initrd_load_addr, initrd_len); | |
368 | if (ret) | |
369 | goto out; | |
370 | ||
371 | } else { | |
cc6ef3d1 | 372 | ret = fdt_delprop(fdt, chosen_node, "linux,initrd-start"); |
b30be4dc RH |
373 | if (ret && (ret != -FDT_ERR_NOTFOUND)) |
374 | goto out; | |
375 | ||
cc6ef3d1 | 376 | ret = fdt_delprop(fdt, chosen_node, "linux,initrd-end"); |
b30be4dc RH |
377 | if (ret && (ret != -FDT_ERR_NOTFOUND)) |
378 | goto out; | |
379 | } | |
380 | ||
381 | if (image->type == KEXEC_TYPE_CRASH) { | |
382 | /* add linux,elfcorehdr */ | |
383 | ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, | |
cc6ef3d1 | 384 | "linux,elfcorehdr", image->elf_load_addr, |
b30be4dc RH |
385 | image->elf_headers_sz); |
386 | if (ret) | |
387 | goto out; | |
388 | ||
389 | /* | |
390 | * Avoid elfcorehdr from being stomped on in kdump kernel by | |
391 | * setting up memory reserve map. | |
392 | */ | |
393 | ret = fdt_add_mem_rsv(fdt, image->elf_load_addr, | |
394 | image->elf_headers_sz); | |
395 | if (ret) | |
396 | goto out; | |
397 | ||
398 | /* add linux,usable-memory-range */ | |
399 | ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, | |
cc6ef3d1 | 400 | "linux,usable-memory-range", crashk_res.start, |
b30be4dc RH |
401 | crashk_res.end - crashk_res.start + 1); |
402 | if (ret) | |
403 | goto out; | |
8af6b91f ZL |
404 | |
405 | if (crashk_low_res.end) { | |
406 | ret = fdt_appendprop_addrrange(fdt, 0, chosen_node, | |
407 | "linux,usable-memory-range", | |
408 | crashk_low_res.start, | |
409 | crashk_low_res.end - crashk_low_res.start + 1); | |
410 | if (ret) | |
411 | goto out; | |
412 | } | |
b30be4dc RH |
413 | } |
414 | ||
415 | /* add bootargs */ | |
416 | if (cmdline) { | |
cc6ef3d1 | 417 | ret = fdt_setprop_string(fdt, chosen_node, "bootargs", cmdline); |
b30be4dc RH |
418 | if (ret) |
419 | goto out; | |
420 | } else { | |
cc6ef3d1 | 421 | ret = fdt_delprop(fdt, chosen_node, "bootargs"); |
b30be4dc RH |
422 | if (ret && (ret != -FDT_ERR_NOTFOUND)) |
423 | goto out; | |
424 | } | |
425 | ||
426 | /* add kaslr-seed */ | |
cc6ef3d1 | 427 | ret = fdt_delprop(fdt, chosen_node, "kaslr-seed"); |
b30be4dc RH |
428 | if (ret == -FDT_ERR_NOTFOUND) |
429 | ret = 0; | |
430 | else if (ret) | |
431 | goto out; | |
432 | ||
433 | if (rng_is_initialized()) { | |
434 | u64 seed = get_random_u64(); | |
435 | ||
cc6ef3d1 | 436 | ret = fdt_setprop_u64(fdt, chosen_node, "kaslr-seed", seed); |
b30be4dc RH |
437 | if (ret) |
438 | goto out; | |
439 | } else { | |
440 | pr_notice("RNG is not initialised: omitting \"%s\" property\n", | |
cc6ef3d1 | 441 | "kaslr-seed"); |
b30be4dc RH |
442 | } |
443 | ||
444 | /* add rng-seed */ | |
445 | if (rng_is_initialized()) { | |
446 | void *rng_seed; | |
447 | ||
cc6ef3d1 | 448 | ret = fdt_setprop_placeholder(fdt, chosen_node, "rng-seed", |
b30be4dc RH |
449 | RNG_SEED_SIZE, &rng_seed); |
450 | if (ret) | |
451 | goto out; | |
452 | get_random_bytes(rng_seed, RNG_SEED_SIZE); | |
453 | } else { | |
454 | pr_notice("RNG is not initialised: omitting \"%s\" property\n", | |
cc6ef3d1 | 455 | "rng-seed"); |
b30be4dc RH |
456 | } |
457 | ||
458 | ret = fdt_setprop(fdt, chosen_node, "linux,booted-from-kexec", NULL, 0); | |
fee3ff99 LR |
459 | if (ret) |
460 | goto out; | |
461 | ||
462 | remove_ima_buffer(fdt, chosen_node); | |
463 | ret = setup_ima_buffer(image, fdt, fdt_path_offset(fdt, "/chosen")); | |
b30be4dc RH |
464 | |
465 | out: | |
466 | if (ret) { | |
467 | kvfree(fdt); | |
468 | fdt = NULL; | |
469 | } | |
470 | ||
471 | return fdt; | |
472 | } |