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 | |
12 | #include <linux/ima.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/kexec.h> |
15 | #include <linux/memblock.h> |
16 | #include <linux/libfdt.h> |
17 | #include <linux/of.h> |
18 | #include <linux/of_fdt.h> |
19 | #include <linux/random.h> |
20 | #include <linux/slab.h> |
21 | #include <linux/types.h> |
22 | |
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 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, n: i, address: &rsv_start, size: &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, n: 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 | |
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(path: "/" ); |
80 | if (!root) |
81 | return -EINVAL; |
82 | |
83 | *addr_cells = of_n_addr_cells(np: root); |
84 | *size_cells = of_n_size_cells(np: root); |
85 | |
86 | of_node_put(node: 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: &addr_cells, size_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(cell: prop, size: addr_cells); |
114 | *size = of_read_number(cell: prop + 4 * addr_cells, size: size_cells); |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | #ifdef CONFIG_HAVE_IMA_KEXEC |
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 | */ |
127 | int __init ima_get_kexec_buffer(void **addr, size_t *size) |
128 | { |
129 | int ret, len; |
130 | unsigned long tmp_addr; |
131 | unsigned long start_pfn, end_pfn; |
132 | size_t tmp_size; |
133 | const void *prop; |
134 | |
135 | prop = of_get_property(node: of_chosen, name: "linux,ima-kexec-buffer" , lenp: &len); |
136 | if (!prop) |
137 | return -ENOENT; |
138 | |
139 | ret = do_get_kexec_buffer(prop, len, addr: &tmp_addr, size: &tmp_size); |
140 | if (ret) |
141 | return ret; |
142 | |
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(pfn: start_pfn) || !page_is_ram(pfn: 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 | |
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 | */ |
168 | int __init ima_free_kexec_buffer(void) |
169 | { |
170 | int ret; |
171 | unsigned long addr; |
172 | size_t size; |
173 | struct property *prop; |
174 | |
175 | prop = of_find_property(np: of_chosen, name: "linux,ima-kexec-buffer" , NULL); |
176 | if (!prop) |
177 | return -ENOENT; |
178 | |
179 | ret = do_get_kexec_buffer(prop: prop->value, len: prop->length, addr: &addr, size: &size); |
180 | if (ret) |
181 | return ret; |
182 | |
183 | ret = of_remove_property(np: of_chosen, prop); |
184 | if (ret) |
185 | return ret; |
186 | |
187 | memblock_free_late(base: addr, size); |
188 | return 0; |
189 | } |
190 | #endif |
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, nodeoffset: chosen_node, name: "linux,ima-kexec-buffer" , lenp: &len); |
212 | if (!prop) |
213 | return; |
214 | |
215 | ret = do_get_kexec_buffer(prop, len, addr: &addr, size: &size); |
216 | fdt_delprop(fdt, nodeoffset: chosen_node, name: "linux,ima-kexec-buffer" ); |
217 | if (ret) |
218 | return; |
219 | |
220 | ret = fdt_find_and_del_mem_rsv(fdt, start: addr, size); |
221 | if (!ret) |
222 | pr_debug("Removed old IMA buffer reservation.\n" ); |
223 | } |
224 | |
225 | #ifdef CONFIG_IMA_KEXEC |
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 | { |
237 | int ret; |
238 | |
239 | if (!image->ima_buffer_size) |
240 | return 0; |
241 | |
242 | ret = fdt_appendprop_addrrange(fdt, parent: 0, nodeoffset: chosen_node, |
243 | name: "linux,ima-kexec-buffer" , |
244 | addr: image->ima_buffer_addr, |
245 | size: image->ima_buffer_size); |
246 | if (ret < 0) |
247 | return -EINVAL; |
248 | |
249 | ret = fdt_add_mem_rsv(fdt, address: image->ima_buffer_addr, |
250 | size: 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 | |
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 ) |
283 | { |
284 | void *fdt; |
285 | int ret, chosen_node, len; |
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(size: fdt_size, GFP_KERNEL); |
294 | if (!fdt) |
295 | return NULL; |
296 | |
297 | ret = fdt_open_into(fdt: initial_boot_params, buf: fdt, bufsize: 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, path: "/chosen" ); |
312 | if (chosen_node == -FDT_ERR_NOTFOUND) |
313 | chosen_node = fdt_add_subnode(fdt, parentoffset: fdt_path_offset(fdt, path: "/" ), |
314 | name: "chosen" ); |
315 | if (chosen_node < 0) { |
316 | ret = chosen_node; |
317 | goto out; |
318 | } |
319 | |
320 | ret = fdt_delprop(fdt, nodeoffset: chosen_node, name: "linux,elfcorehdr" ); |
321 | if (ret && ret != -FDT_ERR_NOTFOUND) |
322 | goto out; |
323 | ret = fdt_delprop(fdt, nodeoffset: chosen_node, name: "linux,usable-memory-range" ); |
324 | if (ret && ret != -FDT_ERR_NOTFOUND) |
325 | goto out; |
326 | |
327 | /* Did we boot using an initrd? */ |
328 | prop = fdt_getprop(fdt, nodeoffset: chosen_node, name: "linux,initrd-start" , lenp: &len); |
329 | if (prop) { |
330 | u64 tmp_start, tmp_end, tmp_size; |
331 | |
332 | tmp_start = of_read_number(cell: prop, size: len / 4); |
333 | |
334 | prop = fdt_getprop(fdt, nodeoffset: chosen_node, name: "linux,initrd-end" , lenp: &len); |
335 | if (!prop) { |
336 | ret = -EINVAL; |
337 | goto out; |
338 | } |
339 | |
340 | tmp_end = of_read_number(cell: prop, size: len / 4); |
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, start: tmp_start, size: tmp_size); |
348 | if (ret == -ENOENT) |
349 | ret = fdt_find_and_del_mem_rsv(fdt, start: 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) { |
357 | ret = fdt_setprop_u64(fdt, nodeoffset: chosen_node, name: "linux,initrd-start" , |
358 | val: initrd_load_addr); |
359 | if (ret) |
360 | goto out; |
361 | |
362 | ret = fdt_setprop_u64(fdt, nodeoffset: chosen_node, name: "linux,initrd-end" , |
363 | val: initrd_load_addr + initrd_len); |
364 | if (ret) |
365 | goto out; |
366 | |
367 | ret = fdt_add_mem_rsv(fdt, address: initrd_load_addr, size: initrd_len); |
368 | if (ret) |
369 | goto out; |
370 | |
371 | } else { |
372 | ret = fdt_delprop(fdt, nodeoffset: chosen_node, name: "linux,initrd-start" ); |
373 | if (ret && (ret != -FDT_ERR_NOTFOUND)) |
374 | goto out; |
375 | |
376 | ret = fdt_delprop(fdt, nodeoffset: chosen_node, name: "linux,initrd-end" ); |
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, parent: 0, nodeoffset: chosen_node, |
384 | name: "linux,elfcorehdr" , addr: image->elf_load_addr, |
385 | size: 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, address: image->elf_load_addr, |
394 | size: image->elf_headers_sz); |
395 | if (ret) |
396 | goto out; |
397 | |
398 | #ifdef CONFIG_CRASH_DUMP |
399 | /* add linux,usable-memory-range */ |
400 | ret = fdt_appendprop_addrrange(fdt, parent: 0, nodeoffset: chosen_node, |
401 | name: "linux,usable-memory-range" , addr: crashk_res.start, |
402 | size: crashk_res.end - crashk_res.start + 1); |
403 | if (ret) |
404 | goto out; |
405 | |
406 | if (crashk_low_res.end) { |
407 | ret = fdt_appendprop_addrrange(fdt, parent: 0, nodeoffset: chosen_node, |
408 | name: "linux,usable-memory-range" , |
409 | addr: crashk_low_res.start, |
410 | size: crashk_low_res.end - crashk_low_res.start + 1); |
411 | if (ret) |
412 | goto out; |
413 | } |
414 | #endif |
415 | } |
416 | |
417 | /* add bootargs */ |
418 | if (cmdline) { |
419 | ret = fdt_setprop_string(fdt, chosen_node, "bootargs" , cmdline); |
420 | if (ret) |
421 | goto out; |
422 | } else { |
423 | ret = fdt_delprop(fdt, nodeoffset: chosen_node, name: "bootargs" ); |
424 | if (ret && (ret != -FDT_ERR_NOTFOUND)) |
425 | goto out; |
426 | } |
427 | |
428 | /* add kaslr-seed */ |
429 | ret = fdt_delprop(fdt, nodeoffset: chosen_node, name: "kaslr-seed" ); |
430 | if (ret == -FDT_ERR_NOTFOUND) |
431 | ret = 0; |
432 | else if (ret) |
433 | goto out; |
434 | |
435 | if (rng_is_initialized()) { |
436 | u64 seed = get_random_u64(); |
437 | |
438 | ret = fdt_setprop_u64(fdt, nodeoffset: chosen_node, name: "kaslr-seed" , val: seed); |
439 | if (ret) |
440 | goto out; |
441 | } else { |
442 | pr_notice("RNG is not initialised: omitting \"%s\" property\n" , |
443 | "kaslr-seed" ); |
444 | } |
445 | |
446 | /* add rng-seed */ |
447 | if (rng_is_initialized()) { |
448 | void *rng_seed; |
449 | |
450 | ret = fdt_setprop_placeholder(fdt, nodeoffset: chosen_node, name: "rng-seed" , |
451 | RNG_SEED_SIZE, prop_data: &rng_seed); |
452 | if (ret) |
453 | goto out; |
454 | get_random_bytes(buf: rng_seed, RNG_SEED_SIZE); |
455 | } else { |
456 | pr_notice("RNG is not initialised: omitting \"%s\" property\n" , |
457 | "rng-seed" ); |
458 | } |
459 | |
460 | ret = fdt_setprop(fdt, nodeoffset: chosen_node, name: "linux,booted-from-kexec" , NULL, len: 0); |
461 | if (ret) |
462 | goto out; |
463 | |
464 | remove_ima_buffer(fdt, chosen_node); |
465 | ret = setup_ima_buffer(image, fdt, chosen_node: fdt_path_offset(fdt, path: "/chosen" )); |
466 | |
467 | out: |
468 | if (ret) { |
469 | kvfree(addr: fdt); |
470 | fdt = NULL; |
471 | } |
472 | |
473 | return fdt; |
474 | } |
475 | |