cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

kexec.c (11089B)


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