Commit b3fd8e83 authored by Nadav Amit's avatar Nadav Amit Committed by Ingo Molnar
Browse files

x86/alternatives: Use temporary mm for text poking

text_poke() can potentially compromise security as it sets temporary
PTEs in the fixmap. These PTEs might be used to rewrite the kernel code
from other cores accidentally or maliciously, if an attacker gains the
ability to write onto kernel memory.

Moreover, since remote TLBs are not flushed after the temporary PTEs are
removed, the time-window in which the code is writable is not limited if
the fixmap PTEs - maliciously or accidentally - are cached in the TLB.
To address these potential security hazards, use a temporary mm for
patching the code.

Finally, text_poke() is also not conservative enough when mapping pages,
as it always tries to map 2 pages, even when a single one is sufficient.
So try to be more conservative, and do not map more than needed.

Signed-off-by: default avatarNadav Amit <>
Signed-off-by: default avatarRick Edgecombe <>
Signed-off-by: default avatarPeter Zijlstra (Intel) <>
Cc: <>
Cc: <>
Cc: <>
Cc: <>
Cc: <>
Cc: <>
Cc: <>
Cc: Andy Lutomirski <>
Cc: Borislav Petkov <>
Cc: Dave Hansen <>
Cc: H. Peter Anvin <>
Cc: Kees Cook <>
Cc: Linus Torvalds <>
Cc: Masami Hiramatsu <>
Cc: Rik van Riel <>
Cc: Thomas Gleixner <>

Signed-off-by: default avatarIngo Molnar <>
parent 4fc19708
......@@ -103,8 +103,6 @@ enum fixed_addresses {
FIX_TEXT_POKE1, /* reserve 2 pages for text_poke() */
FIX_TEXT_POKE0, /* first page is last, because allocation is backward */
......@@ -12,6 +12,7 @@
#include <linux/slab.h>
#include <linux/kdebug.h>
#include <linux/kprobes.h>
#include <linux/mmu_context.h>
#include <asm/text-patching.h>
#include <asm/alternative.h>
#include <asm/sections.h>
......@@ -684,41 +685,104 @@ __ro_after_init unsigned long poking_addr;
static void *__text_poke(void *addr, const void *opcode, size_t len)
bool cross_page_boundary = offset_in_page(addr) + len > PAGE_SIZE;
struct page *pages[2] = {NULL};
temp_mm_state_t prev;
unsigned long flags;
char *vaddr;
struct page *pages[2];
int i;
pte_t pte, *ptep;
spinlock_t *ptl;
pgprot_t pgprot;
* While boot memory allocator is runnig we cannot use struct
* pages as they are not yet initialized.
* While boot memory allocator is running we cannot use struct pages as
* they are not yet initialized. There is no way to recover.
if (!core_kernel_text((unsigned long)addr)) {
pages[0] = vmalloc_to_page(addr);
pages[1] = vmalloc_to_page(addr + PAGE_SIZE);
if (cross_page_boundary)
pages[1] = vmalloc_to_page(addr + PAGE_SIZE);
} else {
pages[0] = virt_to_page(addr);
pages[1] = virt_to_page(addr + PAGE_SIZE);
if (cross_page_boundary)
pages[1] = virt_to_page(addr + PAGE_SIZE);
* If something went wrong, crash and burn since recovery paths are not
* implemented.
BUG_ON(!pages[0] || (cross_page_boundary && !pages[1]));
set_fixmap(FIX_TEXT_POKE0, page_to_phys(pages[0]));
if (pages[1])
set_fixmap(FIX_TEXT_POKE1, page_to_phys(pages[1]));
vaddr = (char *)fix_to_virt(FIX_TEXT_POKE0);
memcpy(&vaddr[(unsigned long)addr & ~PAGE_MASK], opcode, len);
if (pages[1])
/* Could also do a CLFLUSH here to speed up CPU recovery; but
that causes hangs on some VIA CPUs. */
for (i = 0; i < len; i++)
BUG_ON(((char *)addr)[i] != ((char *)opcode)[i]);
* Map the page without the global bit, as TLB flushing is done with
* flush_tlb_mm_range(), which is intended for non-global PTEs.
pgprot = __pgprot(pgprot_val(PAGE_KERNEL) & ~_PAGE_GLOBAL);
* The lock is not really needed, but this allows to avoid open-coding.
ptep = get_locked_pte(poking_mm, poking_addr, &ptl);
* This must not fail; preallocated in poking_init().
pte = mk_pte(pages[0], pgprot);
set_pte_at(poking_mm, poking_addr, ptep, pte);
if (cross_page_boundary) {
pte = mk_pte(pages[1], pgprot);
set_pte_at(poking_mm, poking_addr + PAGE_SIZE, ptep + 1, pte);
* Loading the temporary mm behaves as a compiler barrier, which
* guarantees that the PTE will be set at the time memcpy() is done.
prev = use_temporary_mm(poking_mm);
memcpy((u8 *)poking_addr + offset_in_page(addr), opcode, len);
* Ensure that the PTE is only cleared after the instructions of memcpy
* were issued by using a compiler barrier.
pte_clear(poking_mm, poking_addr, ptep);
if (cross_page_boundary)
pte_clear(poking_mm, poking_addr + PAGE_SIZE, ptep + 1);
* Loading the previous page-table hierarchy requires a serializing
* instruction that already allows the core to see the updated version.
* Xen-PV is assumed to serialize execution in a similar manner.
* Flushing the TLB might involve IPIs, which would require enabled
* IRQs, but not if the mm is not used, as it is in this point.
flush_tlb_mm_range(poking_mm, poking_addr, poking_addr +
(cross_page_boundary ? 2 : 1) * PAGE_SIZE,
PAGE_SHIFT, false);
* If the text does not match what we just wrote then something is
* fundamentally screwy; there's nothing we can really do about that.
BUG_ON(memcmp(addr, opcode, len));
pte_unmap_unlock(ptep, ptl);
return addr;
......@@ -2318,8 +2318,6 @@ static void xen_set_fixmap(unsigned idx, phys_addr_t phys, pgprot_t prot)
/* All local page mappings */
pte = pfn_pte(phys, prot);
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment