Print this page
10907 hot_patch_kernel_text() has no respect for boundaries
Reviewed by: Jerry Jelinek <jerry.jelinek@joyent.com>
Reviewed by: Patrick Mooney <patrick.mooney@joyent.com>
Reviewed by: Robert Mustacchi <rm@joyent.com>

*** 21,30 **** --- 21,34 ---- /* * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ + /* + * Copyright 2019 Joyent, Inc. + */ + /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #include <sys/types.h> #include <sys/sysmacros.h>
*** 306,355 **** 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, }; /* * Hot-patch a single instruction in the kernel's text. ! * If you want to patch multiple instructions you must ! * arrange to do it so that all intermediate stages are ! * sane -- we don't stop other cpus while doing this. * Size must be 1, 2, or 4 bytes with iaddr aligned accordingly. */ void hot_patch_kernel_text(caddr_t iaddr, uint32_t new_instr, uint_t size) { caddr_t vaddr; page_t **ppp; - uintptr_t off = (uintptr_t)iaddr & PAGEOFFSET; ! vaddr = vmem_alloc(heap_arena, PAGESIZE, VM_SLEEP); ! (void) as_pagelock(&kas, &ppp, iaddr - off, PAGESIZE, S_WRITE); hat_devload(kas.a_hat, vaddr, PAGESIZE, ! hat_getpfnum(kas.a_hat, iaddr - off), PROT_READ | PROT_WRITE, HAT_LOAD_LOCK | HAT_LOAD_NOCONSIST); switch (size) { case 1: ! *(uint8_t *)(vaddr + off) = new_instr; break; case 2: ! *(uint16_t *)(vaddr + off) = new_instr; break; case 4: ! *(uint32_t *)(vaddr + off) = new_instr; break; default: panic("illegal hot-patch"); } membar_enter(); ! sync_icache(vaddr + off, size); sync_icache(iaddr, size); ! as_pageunlock(&kas, ppp, iaddr - off, PAGESIZE, S_WRITE); ! hat_unload(kas.a_hat, vaddr, PAGESIZE, HAT_UNLOAD_UNLOCK); ! vmem_free(heap_arena, vaddr, PAGESIZE); } /* * Routine to report an attempt to execute non-executable data. If the * address executed lies in the stack, explicitly say so. --- 310,373 ---- 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, }; /* * Hot-patch a single instruction in the kernel's text. ! * ! * If you want to patch multiple instructions you must arrange to do it so that ! * all intermediate stages are sane -- we don't stop other cpus while doing ! * this. ! * * Size must be 1, 2, or 4 bytes with iaddr aligned accordingly. + * + * The instruction itself might straddle a page boundary, so we have to account + * for that. */ void hot_patch_kernel_text(caddr_t iaddr, uint32_t new_instr, uint_t size) { + const uintptr_t pageoff = (uintptr_t)iaddr & PAGEOFFSET; + const boolean_t straddles = (pageoff + size > PAGESIZE); + const size_t mapsize = straddles ? PAGESIZE * 2 : PAGESIZE; + caddr_t ipageaddr = iaddr - pageoff; caddr_t vaddr; page_t **ppp; ! vaddr = vmem_alloc(heap_arena, mapsize, VM_SLEEP); ! (void) as_pagelock(&kas, &ppp, ipageaddr, mapsize, S_WRITE); hat_devload(kas.a_hat, vaddr, PAGESIZE, ! hat_getpfnum(kas.a_hat, ipageaddr), PROT_READ | PROT_WRITE, ! HAT_LOAD_LOCK | HAT_LOAD_NOCONSIST); ! ! if (straddles) { ! hat_devload(kas.a_hat, vaddr + PAGESIZE, PAGESIZE, ! hat_getpfnum(kas.a_hat, ipageaddr + PAGESIZE), PROT_READ | PROT_WRITE, HAT_LOAD_LOCK | HAT_LOAD_NOCONSIST); + } switch (size) { case 1: ! *(uint8_t *)(vaddr + pageoff) = new_instr; break; case 2: ! *(uint16_t *)(vaddr + pageoff) = new_instr; break; case 4: ! *(uint32_t *)(vaddr + pageoff) = new_instr; break; default: panic("illegal hot-patch"); } membar_enter(); ! sync_icache(vaddr + pageoff, size); sync_icache(iaddr, size); ! as_pageunlock(&kas, ppp, ipageaddr, mapsize, S_WRITE); ! hat_unload(kas.a_hat, vaddr, mapsize, HAT_UNLOAD_UNLOCK); ! vmem_free(heap_arena, vaddr, mapsize); } /* * Routine to report an attempt to execute non-executable data. If the * address executed lies in the stack, explicitly say so.