| 1 | |
| 2 | * This file is subject to the terms and conditions of the GNU General Public
|
| 3 | * License. See the file "COPYING" in the main directory of this archive
|
| 4 | * for more details.
|
| 5 | *
|
| 6 | * Support for Kernel relocation at boot time
|
| 7 | *
|
| 8 | * Copyright (C) 2015, Imagination Technologies Ltd.
|
| 9 | * Authors: Matt Redfearn ([email protected])
|
| 10 |
|
| 11 | #include <asm/bootinfo.h>
|
| 12 | #include <asm/cacheflush.h>
|
| 13 | #include <asm/fw/fw.h>
|
| 14 | #include <asm/sections.h>
|
| 15 | #include <asm/setup.h>
|
| 16 | #include <asm/timex.h>
|
| 17 | #include <linux/elf.h>
|
| 18 | #include <linux/kernel.h>
|
| 19 | #include <linux/libfdt.h>
|
| 20 | #include <linux/of_fdt.h>
|
| 21 | #include <linux/panic_notifier.h>
|
| 22 | #include <linux/sched/task.h>
|
| 23 | #include <linux/start_kernel.h>
|
| 24 | #include <linux/string.h>
|
| 25 | #include <linux/printk.h>
|
| 26 |
|
| 27 | #define RELOCATED(x) ((void *)((long)x + offset))
|
| 28 |
|
| 29 | extern u32 _relocation_start[];
|
| 30 | extern u32 _relocation_end[];
|
| 31 |
|
| 32 | extern long __start___ex_table;
|
| 33 | extern long __stop___ex_table;
|
| 34 |
|
| 35 | extern void __weak plat_fdt_relocated(void *new_location);
|
| 36 |
|
| 37 | |
| 38 | * This function may be defined for a platform to perform any post-relocation
|
| 39 | * fixup necessary.
|
| 40 | * Return non-zero to abort relocation
|
| 41 |
|
| 42 | int __weak plat_post_relocation(long offset)
|
| 43 | {
|
| 44 | return 0;
|
| 45 | }
|
| 46 |
|
| 47 | static inline u32 __init get_synci_step(void)
|
| 48 | {
|
| 49 | u32 res;
|
| 50 |
|
| 51 | __asm__("rdhwr %0, $1" : "=r" (res));
|
| 52 |
|
| 53 | return res;
|
| 54 | }
|
| 55 |
|
| 56 | static void __init sync_icache(void *kbase, unsigned long kernel_length)
|
| 57 | {
|
| 58 | void *kend = kbase + kernel_length;
|
| 59 | u32 step = get_synci_step();
|
| 60 |
|
| 61 | do {
|
| 62 | __asm__ __volatile__(
|
| 63 | "synci 0(%0)"
|
| 64 | :
|
| 65 | : "r" (kbase));
|
| 66 |
|
| 67 | kbase += step;
|
| 68 | } while (step && kbase < kend);
|
| 69 |
|
| 70 |
|
| 71 | __sync();
|
| 72 | }
|
| 73 |
|
| 74 | static void __init apply_r_mips_64_rel(u32 *loc_new, long offset)
|
| 75 | {
|
| 76 | *(u64 *)loc_new += offset;
|
| 77 | }
|
| 78 |
|
| 79 | static void __init apply_r_mips_32_rel(u32 *loc_new, long offset)
|
| 80 | {
|
| 81 | *loc_new += offset;
|
| 82 | }
|
| 83 |
|
| 84 | static int __init apply_r_mips_26_rel(u32 *loc_orig, u32 *loc_new, long offset)
|
| 85 | {
|
| 86 | unsigned long target_addr = (*loc_orig) & 0x03ffffff;
|
| 87 |
|
| 88 | if (offset % 4) {
|
| 89 | pr_err("Dangerous R_MIPS_26 REL relocation\n");
|
| 90 | return -ENOEXEC;
|
| 91 | }
|
| 92 |
|
| 93 |
|
| 94 | target_addr <<= 2;
|
| 95 | target_addr += (unsigned long)loc_orig & 0xf0000000;
|
| 96 |
|
| 97 |
|
| 98 | target_addr += offset;
|
| 99 |
|
| 100 | if ((target_addr & 0xf0000000) != ((unsigned long)loc_new & 0xf0000000)) {
|
| 101 | pr_err("R_MIPS_26 REL relocation overflow\n");
|
| 102 | return -ENOEXEC;
|
| 103 | }
|
| 104 |
|
| 105 | target_addr -= (unsigned long)loc_new & 0xf0000000;
|
| 106 | target_addr >>= 2;
|
| 107 |
|
| 108 | *loc_new = (*loc_new & ~0x03ffffff) | (target_addr & 0x03ffffff);
|
| 109 |
|
| 110 | return 0;
|
| 111 | }
|
| 112 |
|
| 113 |
|
| 114 | static void __init apply_r_mips_hi16_rel(u32 *loc_orig, u32 *loc_new,
|
| 115 | long offset)
|
| 116 | {
|
| 117 | unsigned long insn = *loc_orig;
|
| 118 | unsigned long target = (insn & 0xffff) << 16;
|
| 119 |
|
| 120 | target += offset;
|
| 121 |
|
| 122 | *loc_new = (insn & ~0xffff) | ((target >> 16) & 0xffff);
|
| 123 | }
|
| 124 |
|
| 125 | static int __init reloc_handler(u32 type, u32 *loc_orig, u32 *loc_new,
|
| 126 | long offset)
|
| 127 | {
|
| 128 | switch (type) {
|
| 129 | case R_MIPS_64:
|
| 130 | apply_r_mips_64_rel(loc_new, offset);
|
| 131 | break;
|
| 132 | case R_MIPS_32:
|
| 133 | apply_r_mips_32_rel(loc_new, offset);
|
| 134 | break;
|
| 135 | case R_MIPS_26:
|
| 136 | return apply_r_mips_26_rel(loc_orig, loc_new, offset);
|
| 137 | case R_MIPS_HI16:
|
| 138 | apply_r_mips_hi16_rel(loc_orig, loc_new, offset);
|
| 139 | break;
|
| 140 | default:
|
| 141 | pr_err("Unhandled relocation type %d at 0x%p\n", type,
|
| 142 | loc_orig);
|
| 143 | return -ENOEXEC;
|
| 144 | }
|
| 145 |
|
| 146 | return 0;
|
| 147 | }
|
| 148 |
|
| 149 | static int __init do_relocations(void *kbase_old, void *kbase_new, long offset)
|
| 150 | {
|
| 151 | u32 *r;
|
| 152 | u32 *loc_orig;
|
| 153 | u32 *loc_new;
|
| 154 | int type;
|
| 155 | int res;
|
| 156 |
|
| 157 | for (r = _relocation_start; r < _relocation_end; r++) {
|
| 158 |
|
| 159 | if (*r == 0)
|
| 160 | break;
|
| 161 |
|
| 162 | type = (*r >> 24) & 0xff;
|
| 163 | loc_orig = kbase_old + ((*r & 0x00ffffff) << 2);
|
| 164 | loc_new = RELOCATED(loc_orig);
|
| 165 |
|
| 166 | res = reloc_handler(type, loc_orig, loc_new, offset);
|
| 167 | if (res)
|
| 168 | return res;
|
| 169 | }
|
| 170 |
|
| 171 | return 0;
|
| 172 | }
|
| 173 |
|
| 174 | |
| 175 | * The exception table is filled in by the relocs tool after vmlinux is linked.
|
| 176 | * It must be relocated separately since there will not be any relocation
|
| 177 | * information for it filled in by the linker.
|
| 178 |
|
| 179 | static int __init relocate_exception_table(long offset)
|
| 180 | {
|
| 181 | unsigned long *etable_start, *etable_end, *e;
|
| 182 |
|
| 183 | etable_start = RELOCATED(&__start___ex_table);
|
| 184 | etable_end = RELOCATED(&__stop___ex_table);
|
| 185 |
|
| 186 | for (e = etable_start; e < etable_end; e++)
|
| 187 | *e += offset;
|
| 188 |
|
| 189 | return 0;
|
| 190 | }
|
| 191 |
|
| 192 | #ifdef CONFIG_RANDOMIZE_BASE
|
| 193 |
|
| 194 | static inline __init unsigned long rotate_xor(unsigned long hash,
|
| 195 | const void *area, size_t size)
|
| 196 | {
|
| 197 | const typeof(hash) *ptr = PTR_ALIGN(area, sizeof(hash));
|
| 198 | size_t diff, i;
|
| 199 |
|
| 200 | diff = (void *)ptr - area;
|
| 201 | if (unlikely(size < diff + sizeof(hash)))
|
| 202 | return hash;
|
| 203 |
|
| 204 | size = ALIGN_DOWN(size - diff, sizeof(hash));
|
| 205 |
|
| 206 | for (i = 0; i < size / sizeof(hash); i++) {
|
| 207 |
|
| 208 | hash = (hash << ((sizeof(hash) * 8) - 7)) | (hash >> 7);
|
| 209 | hash ^= ptr[i];
|
| 210 | }
|
| 211 |
|
| 212 | return hash;
|
| 213 | }
|
| 214 |
|
| 215 | static inline __init unsigned long get_random_boot(void)
|
| 216 | {
|
| 217 | unsigned long entropy = random_get_entropy();
|
| 218 | unsigned long hash = 0;
|
| 219 |
|
| 220 |
|
| 221 | hash = rotate_xor(hash, linux_banner, strlen(linux_banner));
|
| 222 |
|
| 223 |
|
| 224 | hash = rotate_xor(hash, &entropy, sizeof(entropy));
|
| 225 |
|
| 226 | #if defined(CONFIG_USE_OF)
|
| 227 |
|
| 228 | if (initial_boot_params) {
|
| 229 | int node, len;
|
| 230 | u64 *prop;
|
| 231 |
|
| 232 | node = fdt_path_offset(initial_boot_params, "/chosen");
|
| 233 | if (node >= 0) {
|
| 234 | prop = fdt_getprop_w(initial_boot_params, node,
|
| 235 | "kaslr-seed", &len);
|
| 236 | if (prop && (len == sizeof(u64)))
|
| 237 | hash = rotate_xor(hash, prop, sizeof(*prop));
|
| 238 | }
|
| 239 | }
|
| 240 | #endif
|
| 241 |
|
| 242 | return hash;
|
| 243 | }
|
| 244 |
|
| 245 | static inline __init bool kaslr_disabled(void)
|
| 246 | {
|
| 247 | char *str;
|
| 248 |
|
| 249 | #if defined(CONFIG_CMDLINE_BOOL)
|
| 250 | const char *builtin_cmdline = CONFIG_CMDLINE;
|
| 251 |
|
| 252 | str = strstr(builtin_cmdline, "nokaslr");
|
| 253 | if (str == builtin_cmdline ||
|
| 254 | (str > builtin_cmdline && *(str - 1) == ' '))
|
| 255 | return true;
|
| 256 | #endif
|
| 257 | str = strstr(arcs_cmdline, "nokaslr");
|
| 258 | if (str == arcs_cmdline || (str > arcs_cmdline && *(str - 1) == ' '))
|
| 259 | return true;
|
| 260 |
|
| 261 | return false;
|
| 262 | }
|
| 263 |
|
| 264 | static inline void __init *determine_relocation_address(void)
|
| 265 | {
|
| 266 |
|
| 267 | unsigned long kernel_length;
|
| 268 | void *dest = &_text;
|
| 269 | unsigned long offset;
|
| 270 |
|
| 271 | if (kaslr_disabled())
|
| 272 | return dest;
|
| 273 |
|
| 274 | kernel_length = (long)_end - (long)(&_text);
|
| 275 |
|
| 276 | offset = get_random_boot() << 16;
|
| 277 | offset &= (CONFIG_RANDOMIZE_BASE_MAX_OFFSET - 1);
|
| 278 | if (offset < kernel_length)
|
| 279 | offset += ALIGN(kernel_length, 0xffff);
|
| 280 |
|
| 281 | return RELOCATED(dest);
|
| 282 | }
|
| 283 |
|
| 284 | #else
|
| 285 |
|
| 286 | static inline void __init *determine_relocation_address(void)
|
| 287 | {
|
| 288 | |
| 289 | * Choose a new address for the kernel
|
| 290 | * For now we'll hard code the destination
|
| 291 |
|
| 292 | return (void *)0xffffffff81000000;
|
| 293 | }
|
| 294 |
|
| 295 | #endif
|
| 296 |
|
| 297 | static inline int __init relocation_addr_valid(void *loc_new)
|
| 298 | {
|
| 299 | if ((unsigned long)loc_new & 0x0000ffff) {
|
| 300 |
|
| 301 | return 0;
|
| 302 | }
|
| 303 | if ((unsigned long)loc_new < (unsigned long)&_end) {
|
| 304 |
|
| 305 | return 0;
|
| 306 | }
|
| 307 | return 1;
|
| 308 | }
|
| 309 |
|
| 310 | static inline void __init update_kaslr_offset(unsigned long *addr, long offset)
|
| 311 | {
|
| 312 | unsigned long *new_addr = (unsigned long *)RELOCATED(addr);
|
| 313 |
|
| 314 | *new_addr = (unsigned long)offset;
|
| 315 | }
|
| 316 |
|
| 317 | #if defined(CONFIG_USE_OF)
|
| 318 | void __weak *plat_get_fdt(void)
|
| 319 | {
|
| 320 | return NULL;
|
| 321 | }
|
| 322 | #endif
|
| 323 |
|
| 324 | void *__init relocate_kernel(void)
|
| 325 | {
|
| 326 | void *loc_new;
|
| 327 | unsigned long kernel_length;
|
| 328 | unsigned long bss_length;
|
| 329 | long offset = 0;
|
| 330 | int res = 1;
|
| 331 |
|
| 332 | void *kernel_entry = start_kernel;
|
| 333 | void *fdt = NULL;
|
| 334 |
|
| 335 |
|
| 336 | fw_init_cmdline();
|
| 337 | #if defined(CONFIG_USE_OF)
|
| 338 |
|
| 339 | fdt = plat_get_fdt();
|
| 340 | early_init_dt_scan(fdt, __pa(fdt));
|
| 341 | if (boot_command_line[0]) {
|
| 342 |
|
| 343 | strscpy(arcs_cmdline, boot_command_line, COMMAND_LINE_SIZE);
|
| 344 | }
|
| 345 | #endif
|
| 346 |
|
| 347 | kernel_length = (long)(&_relocation_start) - (long)(&_text);
|
| 348 | bss_length = (long)&__bss_stop - (long)&__bss_start;
|
| 349 |
|
| 350 | loc_new = determine_relocation_address();
|
| 351 |
|
| 352 |
|
| 353 | if (relocation_addr_valid(loc_new))
|
| 354 | offset = (unsigned long)loc_new - (unsigned long)(&_text);
|
| 355 |
|
| 356 |
|
| 357 | arcs_cmdline[0] = '\0';
|
| 358 |
|
| 359 | if (offset) {
|
| 360 | void (*fdt_relocated_)(void *) = NULL;
|
| 361 | #if defined(CONFIG_USE_OF)
|
| 362 | unsigned long fdt_phys = virt_to_phys(fdt);
|
| 363 |
|
| 364 | |
| 365 | * If built-in dtb is used then it will have been relocated
|
| 366 | * during kernel _text relocation. If appended DTB is used
|
| 367 | * then it will not be relocated, but it should remain
|
| 368 | * intact in the original location. If dtb is loaded by
|
| 369 | * the bootloader then it may need to be moved if it crosses
|
| 370 | * the target memory area
|
| 371 |
|
| 372 |
|
| 373 | if (fdt_phys >= virt_to_phys(RELOCATED(&_text)) &&
|
| 374 | fdt_phys <= virt_to_phys(RELOCATED(&_end))) {
|
| 375 | void *fdt_relocated =
|
| 376 | RELOCATED(ALIGN((long)&_end, PAGE_SIZE));
|
| 377 | memcpy(fdt_relocated, fdt, fdt_totalsize(fdt));
|
| 378 | fdt = fdt_relocated;
|
| 379 | fdt_relocated_ = RELOCATED(&plat_fdt_relocated);
|
| 380 | }
|
| 381 | #endif
|
| 382 |
|
| 383 |
|
| 384 | memcpy(loc_new, &_text, kernel_length);
|
| 385 |
|
| 386 |
|
| 387 | res = do_relocations(&_text, loc_new, offset);
|
| 388 | if (res < 0)
|
| 389 | goto out;
|
| 390 |
|
| 391 |
|
| 392 | sync_icache(loc_new, kernel_length);
|
| 393 |
|
| 394 | res = relocate_exception_table(offset);
|
| 395 | if (res < 0)
|
| 396 | goto out;
|
| 397 |
|
| 398 | |
| 399 | * The original .bss has already been cleared, and
|
| 400 | * some variables such as command line parameters
|
| 401 | * stored to it so make a copy in the new location.
|
| 402 |
|
| 403 | memcpy(RELOCATED(&__bss_start), &__bss_start, bss_length);
|
| 404 |
|
| 405 | |
| 406 | * If fdt was stored outside of the kernel image and
|
| 407 | * had to be moved then update platform's state data
|
| 408 | * with the new fdt location
|
| 409 |
|
| 410 | if (fdt_relocated_)
|
| 411 | fdt_relocated_(fdt);
|
| 412 |
|
| 413 | |
| 414 | * Last chance for the platform to abort relocation.
|
| 415 | * This may also be used by the platform to perform any
|
| 416 | * initialisation required now that the new kernel is
|
| 417 | * resident in memory and ready to be executed.
|
| 418 |
|
| 419 | if (plat_post_relocation(offset))
|
| 420 | goto out;
|
| 421 |
|
| 422 |
|
| 423 | #ifndef CONFIG_CC_IS_CLANG
|
| 424 | __current_thread_info = RELOCATED(&init_thread_union);
|
| 425 | #else
|
| 426 | |
| 427 | * LLVM may wrongly restore $gp ($28) in epilog even if it's
|
| 428 | * intentionally modified. Work around this by using inline
|
| 429 | * assembly to assign $gp. $gp couldn't be listed as output or
|
| 430 | * clobber, or LLVM will still restore its original value.
|
| 431 | * See also LLVM upstream issue
|
| 432 | * https://github.com/llvm/llvm-project/issues/176546
|
| 433 |
|
| 434 | asm volatile("move $28, %0" : :
|
| 435 | "r" (RELOCATED(&init_thread_union)));
|
| 436 | #endif
|
| 437 |
|
| 438 |
|
| 439 | kernel_entry = RELOCATED(start_kernel);
|
| 440 |
|
| 441 |
|
| 442 | update_kaslr_offset(&__kaslr_offset, offset);
|
| 443 | }
|
| 444 | out:
|
| 445 | return kernel_entry;
|
| 446 | }
|
| 447 |
|
| 448 | |
| 449 | * Show relocation information on panic.
|
| 450 |
|
| 451 | static void show_kernel_relocation(const char *level)
|
| 452 | {
|
| 453 | if (__kaslr_offset > 0) {
|
| 454 | printk(level);
|
| 455 | pr_cont("Kernel relocated by 0x%p\n", (void *)__kaslr_offset);
|
| 456 | pr_cont(" .text @ 0x%p\n", _text);
|
| 457 | pr_cont(" .data @ 0x%p\n", _sdata);
|
| 458 | pr_cont(" .bss @ 0x%p\n", __bss_start);
|
| 459 | }
|
| 460 | }
|
| 461 |
|
| 462 | static int kernel_location_notifier_fn(struct notifier_block *self,
|
| 463 | unsigned long v, void *p)
|
| 464 | {
|
| 465 | show_kernel_relocation(KERN_EMERG);
|
| 466 | return NOTIFY_DONE;
|
| 467 | }
|
| 468 |
|
| 469 | static struct notifier_block kernel_location_notifier = {
|
| 470 | .notifier_call = kernel_location_notifier_fn
|
| 471 | };
|
| 472 |
|
| 473 | static int __init register_kernel_offset_dumper(void)
|
| 474 | {
|
| 475 | atomic_notifier_chain_register(&panic_notifier_list,
|
| 476 | &kernel_location_notifier);
|
| 477 | return 0;
|
| 478 | }
|
| 479 | __initcall(register_kernel_offset_dumper);
|