123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- // SPDX-License-Identifier: GPL-2.0-only
- //
- // Copyright (C) 2019 Jason Yan <[email protected]>
- #include <linux/kernel.h>
- #include <linux/errno.h>
- #include <linux/string.h>
- #include <linux/types.h>
- #include <linux/mm.h>
- #include <linux/swap.h>
- #include <linux/stddef.h>
- #include <linux/init.h>
- #include <linux/delay.h>
- #include <linux/memblock.h>
- #include <linux/libfdt.h>
- #include <linux/crash_core.h>
- #include <linux/of.h>
- #include <linux/of_fdt.h>
- #include <asm/cacheflush.h>
- #include <asm/kdump.h>
- #include <mm/mmu_decl.h>
- #include <generated/utsrelease.h>
- struct regions {
- unsigned long pa_start;
- unsigned long pa_end;
- unsigned long kernel_size;
- unsigned long dtb_start;
- unsigned long dtb_end;
- unsigned long initrd_start;
- unsigned long initrd_end;
- unsigned long crash_start;
- unsigned long crash_end;
- int reserved_mem;
- int reserved_mem_addr_cells;
- int reserved_mem_size_cells;
- };
- struct regions __initdata regions;
- static __init void kaslr_get_cmdline(void *fdt)
- {
- early_init_dt_scan_chosen(boot_command_line);
- }
- static unsigned long __init rotate_xor(unsigned long hash, const void *area,
- size_t size)
- {
- size_t i;
- const unsigned long *ptr = area;
- for (i = 0; i < size / sizeof(hash); i++) {
- /* Rotate by odd number of bits and XOR. */
- hash = (hash << ((sizeof(hash) * 8) - 7)) | (hash >> 7);
- hash ^= ptr[i];
- }
- return hash;
- }
- /* Attempt to create a simple starting entropy. This can make it defferent for
- * every build but it is still not enough. Stronger entropy should
- * be added to make it change for every boot.
- */
- static unsigned long __init get_boot_seed(void *fdt)
- {
- unsigned long hash = 0;
- /* build-specific string for starting entropy. */
- hash = rotate_xor(hash, linux_banner, strlen(linux_banner));
- hash = rotate_xor(hash, fdt, fdt_totalsize(fdt));
- return hash;
- }
- static __init u64 get_kaslr_seed(void *fdt)
- {
- int node, len;
- fdt64_t *prop;
- u64 ret;
- node = fdt_path_offset(fdt, "/chosen");
- if (node < 0)
- return 0;
- prop = fdt_getprop_w(fdt, node, "kaslr-seed", &len);
- if (!prop || len != sizeof(u64))
- return 0;
- ret = fdt64_to_cpu(*prop);
- *prop = 0;
- return ret;
- }
- static __init bool regions_overlap(u32 s1, u32 e1, u32 s2, u32 e2)
- {
- return e1 >= s2 && e2 >= s1;
- }
- static __init bool overlaps_reserved_region(const void *fdt, u32 start,
- u32 end)
- {
- int subnode, len, i;
- u64 base, size;
- /* check for overlap with /memreserve/ entries */
- for (i = 0; i < fdt_num_mem_rsv(fdt); i++) {
- if (fdt_get_mem_rsv(fdt, i, &base, &size) < 0)
- continue;
- if (regions_overlap(start, end, base, base + size))
- return true;
- }
- if (regions.reserved_mem < 0)
- return false;
- /* check for overlap with static reservations in /reserved-memory */
- for (subnode = fdt_first_subnode(fdt, regions.reserved_mem);
- subnode >= 0;
- subnode = fdt_next_subnode(fdt, subnode)) {
- const fdt32_t *reg;
- u64 rsv_end;
- len = 0;
- reg = fdt_getprop(fdt, subnode, "reg", &len);
- while (len >= (regions.reserved_mem_addr_cells +
- regions.reserved_mem_size_cells)) {
- base = fdt32_to_cpu(reg[0]);
- if (regions.reserved_mem_addr_cells == 2)
- base = (base << 32) | fdt32_to_cpu(reg[1]);
- reg += regions.reserved_mem_addr_cells;
- len -= 4 * regions.reserved_mem_addr_cells;
- size = fdt32_to_cpu(reg[0]);
- if (regions.reserved_mem_size_cells == 2)
- size = (size << 32) | fdt32_to_cpu(reg[1]);
- reg += regions.reserved_mem_size_cells;
- len -= 4 * regions.reserved_mem_size_cells;
- if (base >= regions.pa_end)
- continue;
- rsv_end = min(base + size, (u64)U32_MAX);
- if (regions_overlap(start, end, base, rsv_end))
- return true;
- }
- }
- return false;
- }
- static __init bool overlaps_region(const void *fdt, u32 start,
- u32 end)
- {
- if (regions_overlap(start, end, __pa(_stext), __pa(_end)))
- return true;
- if (regions_overlap(start, end, regions.dtb_start,
- regions.dtb_end))
- return true;
- if (regions_overlap(start, end, regions.initrd_start,
- regions.initrd_end))
- return true;
- if (regions_overlap(start, end, regions.crash_start,
- regions.crash_end))
- return true;
- return overlaps_reserved_region(fdt, start, end);
- }
- static void __init get_crash_kernel(void *fdt, unsigned long size)
- {
- #ifdef CONFIG_CRASH_CORE
- unsigned long long crash_size, crash_base;
- int ret;
- ret = parse_crashkernel(boot_command_line, size, &crash_size,
- &crash_base);
- if (ret != 0 || crash_size == 0)
- return;
- if (crash_base == 0)
- crash_base = KDUMP_KERNELBASE;
- regions.crash_start = (unsigned long)crash_base;
- regions.crash_end = (unsigned long)(crash_base + crash_size);
- pr_debug("crash_base=0x%llx crash_size=0x%llx\n", crash_base, crash_size);
- #endif
- }
- static void __init get_initrd_range(void *fdt)
- {
- u64 start, end;
- int node, len;
- const __be32 *prop;
- node = fdt_path_offset(fdt, "/chosen");
- if (node < 0)
- return;
- prop = fdt_getprop(fdt, node, "linux,initrd-start", &len);
- if (!prop)
- return;
- start = of_read_number(prop, len / 4);
- prop = fdt_getprop(fdt, node, "linux,initrd-end", &len);
- if (!prop)
- return;
- end = of_read_number(prop, len / 4);
- regions.initrd_start = (unsigned long)start;
- regions.initrd_end = (unsigned long)end;
- pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n", start, end);
- }
- static __init unsigned long get_usable_address(const void *fdt,
- unsigned long start,
- unsigned long offset)
- {
- unsigned long pa;
- unsigned long pa_end;
- for (pa = offset; (long)pa > (long)start; pa -= SZ_16K) {
- pa_end = pa + regions.kernel_size;
- if (overlaps_region(fdt, pa, pa_end))
- continue;
- return pa;
- }
- return 0;
- }
- static __init void get_cell_sizes(const void *fdt, int node, int *addr_cells,
- int *size_cells)
- {
- const int *prop;
- int len;
- /*
- * Retrieve the #address-cells and #size-cells properties
- * from the 'node', or use the default if not provided.
- */
- *addr_cells = *size_cells = 1;
- prop = fdt_getprop(fdt, node, "#address-cells", &len);
- if (len == 4)
- *addr_cells = fdt32_to_cpu(*prop);
- prop = fdt_getprop(fdt, node, "#size-cells", &len);
- if (len == 4)
- *size_cells = fdt32_to_cpu(*prop);
- }
- static unsigned long __init kaslr_legal_offset(void *dt_ptr, unsigned long index,
- unsigned long offset)
- {
- unsigned long koffset = 0;
- unsigned long start;
- while ((long)index >= 0) {
- offset = memstart_addr + index * SZ_64M + offset;
- start = memstart_addr + index * SZ_64M;
- koffset = get_usable_address(dt_ptr, start, offset);
- if (koffset)
- break;
- index--;
- }
- if (koffset != 0)
- koffset -= memstart_addr;
- return koffset;
- }
- static inline __init bool kaslr_disabled(void)
- {
- return strstr(boot_command_line, "nokaslr") != NULL;
- }
- static unsigned long __init kaslr_choose_location(void *dt_ptr, phys_addr_t size,
- unsigned long kernel_sz)
- {
- unsigned long offset, random;
- unsigned long ram, linear_sz;
- u64 seed;
- unsigned long index;
- kaslr_get_cmdline(dt_ptr);
- if (kaslr_disabled())
- return 0;
- random = get_boot_seed(dt_ptr);
- seed = get_tb() << 32;
- seed ^= get_tb();
- random = rotate_xor(random, &seed, sizeof(seed));
- /*
- * Retrieve (and wipe) the seed from the FDT
- */
- seed = get_kaslr_seed(dt_ptr);
- if (seed)
- random = rotate_xor(random, &seed, sizeof(seed));
- else
- pr_warn("KASLR: No safe seed for randomizing the kernel base.\n");
- ram = min_t(phys_addr_t, __max_low_memory, size);
- ram = map_mem_in_cams(ram, CONFIG_LOWMEM_CAM_NUM, true, true);
- linear_sz = min_t(unsigned long, ram, SZ_512M);
- /* If the linear size is smaller than 64M, do not randomize */
- if (linear_sz < SZ_64M)
- return 0;
- /* check for a reserved-memory node and record its cell sizes */
- regions.reserved_mem = fdt_path_offset(dt_ptr, "/reserved-memory");
- if (regions.reserved_mem >= 0)
- get_cell_sizes(dt_ptr, regions.reserved_mem,
- ®ions.reserved_mem_addr_cells,
- ®ions.reserved_mem_size_cells);
- regions.pa_start = memstart_addr;
- regions.pa_end = memstart_addr + linear_sz;
- regions.dtb_start = __pa(dt_ptr);
- regions.dtb_end = __pa(dt_ptr) + fdt_totalsize(dt_ptr);
- regions.kernel_size = kernel_sz;
- get_initrd_range(dt_ptr);
- get_crash_kernel(dt_ptr, ram);
- /*
- * Decide which 64M we want to start
- * Only use the low 8 bits of the random seed
- */
- index = random & 0xFF;
- index %= linear_sz / SZ_64M;
- /* Decide offset inside 64M */
- offset = random % (SZ_64M - kernel_sz);
- offset = round_down(offset, SZ_16K);
- return kaslr_legal_offset(dt_ptr, index, offset);
- }
- /*
- * To see if we need to relocate the kernel to a random offset
- * void *dt_ptr - address of the device tree
- * phys_addr_t size - size of the first memory block
- */
- notrace void __init kaslr_early_init(void *dt_ptr, phys_addr_t size)
- {
- unsigned long tlb_virt;
- phys_addr_t tlb_phys;
- unsigned long offset;
- unsigned long kernel_sz;
- kernel_sz = (unsigned long)_end - (unsigned long)_stext;
- offset = kaslr_choose_location(dt_ptr, size, kernel_sz);
- if (offset == 0)
- return;
- kernstart_virt_addr += offset;
- kernstart_addr += offset;
- is_second_reloc = 1;
- if (offset >= SZ_64M) {
- tlb_virt = round_down(kernstart_virt_addr, SZ_64M);
- tlb_phys = round_down(kernstart_addr, SZ_64M);
- /* Create kernel map to relocate in */
- create_kaslr_tlb_entry(1, tlb_virt, tlb_phys);
- }
- /* Copy the kernel to it's new location and run */
- memcpy((void *)kernstart_virt_addr, (void *)_stext, kernel_sz);
- flush_icache_range(kernstart_virt_addr, kernstart_virt_addr + kernel_sz);
- reloc_kernel_entry(dt_ptr, kernstart_virt_addr);
- }
- void __init kaslr_late_init(void)
- {
- /* If randomized, clear the original kernel */
- if (kernstart_virt_addr != KERNELBASE) {
- unsigned long kernel_sz;
- kernel_sz = (unsigned long)_end - kernstart_virt_addr;
- memzero_explicit((void *)KERNELBASE, kernel_sz);
- }
- }
|