accel/tcg: Clear PAGE_WRITE before translation

translate_insn() implementations fetch instruction bytes piecemeal,
which can cause qemu-user to generate inconsistent translations if
another thread modifies them concurrently [1].

Fix by making pages containing translated instruction non-writable
right before loading instruction bytes from them.

[1] https://lists.nongnu.org/archive/html/qemu-devel/2021-08/msg00644.html

Signed-off-by: Ilya Leoshkevich <iii@linux.ibm.com>
Message-Id: <20210805204835.158918-1-iii@linux.ibm.com>
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Ilya Leoshkevich 2021-08-05 22:48:35 +02:00 committed by Richard Henderson
parent 4e116893c6
commit f025692c99
4 changed files with 97 additions and 41 deletions

View File

@ -1297,31 +1297,8 @@ static inline void tb_page_add(PageDesc *p, TranslationBlock *tb,
invalidate_page_bitmap(p); invalidate_page_bitmap(p);
#if defined(CONFIG_USER_ONLY) #if defined(CONFIG_USER_ONLY)
if (p->flags & PAGE_WRITE) { /* translator_loop() must have made all TB pages non-writable */
target_ulong addr; assert(!(p->flags & PAGE_WRITE));
PageDesc *p2;
int prot;
/* force the host page as non writable (writes will have a
page fault + mprotect overhead) */
page_addr &= qemu_host_page_mask;
prot = 0;
for (addr = page_addr; addr < page_addr + qemu_host_page_size;
addr += TARGET_PAGE_SIZE) {
p2 = page_find(addr >> TARGET_PAGE_BITS);
if (!p2) {
continue;
}
prot |= p2->flags;
p2->flags &= ~PAGE_WRITE;
}
mprotect(g2h_untagged(page_addr), qemu_host_page_size,
(prot & PAGE_BITS) & ~PAGE_WRITE);
if (DEBUG_TB_INVALIDATE_GATE) {
printf("protecting code page: 0x" TB_PAGE_ADDR_FMT "\n", page_addr);
}
}
#else #else
/* if some code is already present, then the pages are already /* if some code is already present, then the pages are already
protected. So we handle the case where only the first TB is protected. So we handle the case where only the first TB is
@ -2394,6 +2371,38 @@ int page_check_range(target_ulong start, target_ulong len, int flags)
return 0; return 0;
} }
void page_protect(tb_page_addr_t page_addr)
{
target_ulong addr;
PageDesc *p;
int prot;
p = page_find(page_addr >> TARGET_PAGE_BITS);
if (p && (p->flags & PAGE_WRITE)) {
/*
* Force the host page as non writable (writes will have a page fault +
* mprotect overhead).
*/
page_addr &= qemu_host_page_mask;
prot = 0;
for (addr = page_addr; addr < page_addr + qemu_host_page_size;
addr += TARGET_PAGE_SIZE) {
p = page_find(addr >> TARGET_PAGE_BITS);
if (!p) {
continue;
}
prot |= p->flags;
p->flags &= ~PAGE_WRITE;
}
mprotect(g2h_untagged(page_addr), qemu_host_page_size,
(prot & PAGE_BITS) & ~PAGE_WRITE);
if (DEBUG_TB_INVALIDATE_GATE) {
printf("protecting code page: 0x" TB_PAGE_ADDR_FMT "\n", page_addr);
}
}
}
/* called from signal handler: invalidate the code and unprotect the /* called from signal handler: invalidate the code and unprotect the
* page. Return 0 if the fault was not handled, 1 if it was handled, * page. Return 0 if the fault was not handled, 1 if it was handled,
* and 2 if it was handled but the caller must cause the TB to be * and 2 if it was handled but the caller must cause the TB to be

View File

@ -42,6 +42,15 @@ bool translator_use_goto_tb(DisasContextBase *db, target_ulong dest)
return ((db->pc_first ^ dest) & TARGET_PAGE_MASK) == 0; return ((db->pc_first ^ dest) & TARGET_PAGE_MASK) == 0;
} }
static inline void translator_page_protect(DisasContextBase *dcbase,
target_ulong pc)
{
#ifdef CONFIG_USER_ONLY
dcbase->page_protect_end = pc | ~TARGET_PAGE_MASK;
page_protect(pc);
#endif
}
void translator_loop(const TranslatorOps *ops, DisasContextBase *db, void translator_loop(const TranslatorOps *ops, DisasContextBase *db,
CPUState *cpu, TranslationBlock *tb, int max_insns) CPUState *cpu, TranslationBlock *tb, int max_insns)
{ {
@ -56,6 +65,7 @@ void translator_loop(const TranslatorOps *ops, DisasContextBase *db,
db->num_insns = 0; db->num_insns = 0;
db->max_insns = max_insns; db->max_insns = max_insns;
db->singlestep_enabled = cflags & CF_SINGLE_STEP; db->singlestep_enabled = cflags & CF_SINGLE_STEP;
translator_page_protect(db, db->pc_next);
ops->init_disas_context(db, cpu); ops->init_disas_context(db, cpu);
tcg_debug_assert(db->is_jmp == DISAS_NEXT); /* no early exit */ tcg_debug_assert(db->is_jmp == DISAS_NEXT); /* no early exit */
@ -137,3 +147,32 @@ void translator_loop(const TranslatorOps *ops, DisasContextBase *db,
} }
#endif #endif
} }
static inline void translator_maybe_page_protect(DisasContextBase *dcbase,
target_ulong pc, size_t len)
{
#ifdef CONFIG_USER_ONLY
target_ulong end = pc + len - 1;
if (end > dcbase->page_protect_end) {
translator_page_protect(dcbase, end);
}
#endif
}
#define GEN_TRANSLATOR_LD(fullname, type, load_fn, swap_fn) \
type fullname ## _swap(CPUArchState *env, DisasContextBase *dcbase, \
abi_ptr pc, bool do_swap) \
{ \
translator_maybe_page_protect(dcbase, pc, sizeof(type)); \
type ret = load_fn(env, pc); \
if (do_swap) { \
ret = swap_fn(ret); \
} \
plugin_insn_append(&ret, sizeof(ret)); \
return ret; \
}
FOR_EACH_TRANSLATOR_LD(GEN_TRANSLATOR_LD)
#undef GEN_TRANSLATOR_LD

View File

@ -33,6 +33,7 @@ void tb_invalidate_phys_page_range(tb_page_addr_t start, tb_page_addr_t end);
void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr); void tb_check_watchpoint(CPUState *cpu, uintptr_t retaddr);
#ifdef CONFIG_USER_ONLY #ifdef CONFIG_USER_ONLY
void page_protect(tb_page_addr_t page_addr);
int page_unprotect(target_ulong address, uintptr_t pc); int page_unprotect(target_ulong address, uintptr_t pc);
#endif #endif

View File

@ -23,6 +23,7 @@
#include "exec/exec-all.h" #include "exec/exec-all.h"
#include "exec/cpu_ldst.h" #include "exec/cpu_ldst.h"
#include "exec/plugin-gen.h" #include "exec/plugin-gen.h"
#include "exec/translate-all.h"
#include "tcg/tcg.h" #include "tcg/tcg.h"
@ -74,6 +75,17 @@ typedef struct DisasContextBase {
int num_insns; int num_insns;
int max_insns; int max_insns;
bool singlestep_enabled; bool singlestep_enabled;
#ifdef CONFIG_USER_ONLY
/*
* Guest address of the last byte of the last protected page.
*
* Pages containing the translated instructions are made non-writable in
* order to achieve consistency in case another thread is modifying the
* code while translate_insn() fetches the instruction bytes piecemeal.
* Such writer threads are blocked on mmap_lock() in page_unprotect().
*/
target_ulong page_protect_end;
#endif
} DisasContextBase; } DisasContextBase;
/** /**
@ -156,28 +168,23 @@ bool translator_use_goto_tb(DisasContextBase *db, target_ulong dest);
*/ */
#define GEN_TRANSLATOR_LD(fullname, type, load_fn, swap_fn) \ #define GEN_TRANSLATOR_LD(fullname, type, load_fn, swap_fn) \
static inline type \ type fullname ## _swap(CPUArchState *env, DisasContextBase *dcbase, \
fullname ## _swap(CPUArchState *env, DisasContextBase *dcbase, \ abi_ptr pc, bool do_swap); \
abi_ptr pc, bool do_swap) \
{ \
type ret = load_fn(env, pc); \
if (do_swap) { \
ret = swap_fn(ret); \
} \
plugin_insn_append(&ret, sizeof(ret)); \
return ret; \
} \
static inline type fullname(CPUArchState *env, \ static inline type fullname(CPUArchState *env, \
DisasContextBase *dcbase, abi_ptr pc) \ DisasContextBase *dcbase, abi_ptr pc) \
{ \ { \
return fullname ## _swap(env, dcbase, pc, false); \ return fullname ## _swap(env, dcbase, pc, false); \
} }
GEN_TRANSLATOR_LD(translator_ldub, uint8_t, cpu_ldub_code, /* no swap */) #define FOR_EACH_TRANSLATOR_LD(F) \
GEN_TRANSLATOR_LD(translator_ldsw, int16_t, cpu_ldsw_code, bswap16) F(translator_ldub, uint8_t, cpu_ldub_code, /* no swap */) \
GEN_TRANSLATOR_LD(translator_lduw, uint16_t, cpu_lduw_code, bswap16) F(translator_ldsw, int16_t, cpu_ldsw_code, bswap16) \
GEN_TRANSLATOR_LD(translator_ldl, uint32_t, cpu_ldl_code, bswap32) F(translator_lduw, uint16_t, cpu_lduw_code, bswap16) \
GEN_TRANSLATOR_LD(translator_ldq, uint64_t, cpu_ldq_code, bswap64) F(translator_ldl, uint32_t, cpu_ldl_code, bswap32) \
F(translator_ldq, uint64_t, cpu_ldq_code, bswap64)
FOR_EACH_TRANSLATOR_LD(GEN_TRANSLATOR_LD)
#undef GEN_TRANSLATOR_LD #undef GEN_TRANSLATOR_LD
#endif /* EXEC__TRANSLATOR_H */ #endif /* EXEC__TRANSLATOR_H */