[XEN] Make the spurious page-fault detection logic

more robust. In particular it must be able to handle
spurious write faults on mappings that have been
changed from read-only to writable. If a CPU has a stale
read-only entry in its TLB, it is allowed to fault on
the next write access without re-walking the page table.
Signed-off-by: Keir Fraser <keir@xensource.com>
This commit is contained in:
kfraser@dhcp93.uk.xensource.com committed 2006-06-16 18:18:55 +01:00
1 parent 8d9ab5812a
commit 12794ba7df
4 files changed
+142 -118

No files matched your search

+135 -49
View File
@@ -511,9 +511,9 @@ void propagate_page_fault(unsigned long addr, u16 error_code)
v->vcpu_info->arch.cr2 = addr;
/* Re-set error_code.user flag appropriately for the guest. */
error_code &= ~4;
error_code &= ~PGERR_user_mode;
if ( !guest_kernel_mode(v, guest_cpu_user_regs()) )
error_code |= 4;
error_code |= PGERR_user_mode;
ti = &v->arch.guest_context.trap_ctxt[TRAP_page_fault];
tb->flags = TBF_EXCEPTION | TBF_EXCEPTION_ERRCODE;
@@ -578,10 +578,125 @@ static int handle_gdt_ldt_mapping_fault(
(((va) >= HYPERVISOR_VIRT_START))
#endif
static int __spurious_page_fault(
unsigned long addr, struct cpu_user_regs *regs)
{
unsigned long mfn = read_cr3() >> PAGE_SHIFT;
#if CONFIG_PAGING_LEVELS >= 4
l4_pgentry_t l4e, *l4t;
#endif
#if CONFIG_PAGING_LEVELS >= 3
l3_pgentry_t l3e, *l3t;
#endif
l2_pgentry_t l2e, *l2t;
l1_pgentry_t l1e, *l1t;
unsigned int required_flags, disallowed_flags;
required_flags = _PAGE_PRESENT;
if ( regs->error_code & PGERR_write_access )
required_flags |= _PAGE_RW;
if ( regs->error_code & PGERR_user_mode )
required_flags |= _PAGE_USER;
disallowed_flags = 0;
if ( regs->error_code & PGERR_instr_fetch )
disallowed_flags |= _PAGE_NX;
#if CONFIG_PAGING_LEVELS >= 4
l4t = map_domain_page(mfn);
l4e = l4t[l4_table_offset(addr)];
mfn = l4e_get_pfn(l4e);
unmap_domain_page(l4t);
if ( !(l4e_get_flags(l4e) & required_flags) ||
(l4e_get_flags(l4e) & disallowed_flags) )
return 0;
#endif
#if CONFIG_PAGING_LEVELS >= 3
l3t = map_domain_page(mfn);
l3e = l3t[l3_table_offset(addr)];
mfn = l3e_get_pfn(l3e);
unmap_domain_page(l3t);
#ifdef CONFIG_X86_PAE
if ( !(l3e_get_flags(l3e) & _PAGE_PRESENT) )
return 0;
#else
if ( !(l3e_get_flags(l3e) & required_flags) ||
(l3e_get_flags(l3e) & disallowed_flags) )
return 0;
#endif
#endif
l2t = map_domain_page(mfn);
l2e = l2t[l2_table_offset(addr)];
mfn = l2e_get_pfn(l2e);
unmap_domain_page(l2t);
if ( !(l2e_get_flags(l2e) & required_flags) ||
(l2e_get_flags(l2e) & disallowed_flags) )
return 0;
if ( l2e_get_flags(l2e) & _PAGE_PSE )
return 1;
l1t = map_domain_page(mfn);
l1e = l1t[l1_table_offset(addr)];
mfn = l1e_get_pfn(l1e);
unmap_domain_page(l1t);
if ( !(l1e_get_flags(l1e) & required_flags) ||
(l1e_get_flags(l1e) & disallowed_flags) )
return 0;
return 1;
}
static int spurious_page_fault(
unsigned long addr, struct cpu_user_regs *regs)
{
struct vcpu *v = current;
struct domain *d = v->domain;
int is_spurious;
/* Reserved bit violations are never spurious faults. */
if ( regs->error_code & PGERR_reserved_bit )
return 0;
LOCK_BIGLOCK(d);
is_spurious = __spurious_page_fault(addr, regs);
if ( is_spurious )
goto out;
/*
* The only possible reason for a spurious page fault not to be picked
* up already is that a page directory was unhooked by writable page table
* logic and then reattached before the faulting VCPU could detect it.
*/
if ( is_idle_domain(d) || /* no ptwr in idle domain */
IN_HYPERVISOR_RANGE(addr) || /* no ptwr on hypervisor addrs */
shadow_mode_enabled(d) || /* no ptwr logic in shadow mode */
(regs->error_code & PGERR_page_present) ) /* not-present fault? */
goto out;
/*
* The page directory could have been detached again while we weren't
* holding the per-domain lock. Detect that and fix up if it's the case.
*/
if ( unlikely(d->arch.ptwr[PTWR_PT_ACTIVE].l1va) &&
unlikely(l2_linear_offset(addr) ==
d->arch.ptwr[PTWR_PT_ACTIVE].l2_idx) )
{
ptwr_flush(d, PTWR_PT_ACTIVE);
is_spurious = 1;
}
out:
UNLOCK_BIGLOCK(d);
return is_spurious;
}
static int fixup_page_fault(unsigned long addr, struct cpu_user_regs *regs)
{
struct vcpu *v = current;
struct domain *d = v->domain;
int rc;
if ( unlikely(IN_HYPERVISOR_RANGE(addr)) )
{
@@ -590,12 +705,20 @@ static int fixup_page_fault(unsigned long addr, struct cpu_user_regs *regs)
if ( (addr >= GDT_LDT_VIRT_START) && (addr < GDT_LDT_VIRT_END) )
return handle_gdt_ldt_mapping_fault(
addr - GDT_LDT_VIRT_START, regs);
/*
* Do not propagate spurious faults in the hypervisor area to the
* guest. It cannot fix them up.
*/
LOCK_BIGLOCK(d);
rc = __spurious_page_fault(addr, regs);
UNLOCK_BIGLOCK(d);
return rc;
}
else if ( unlikely(shadow_mode_enabled(d)) )
{
if ( unlikely(shadow_mode_enabled(d)) )
return shadow_fault(addr, regs);
}
else if ( likely(VM_ASSIST(d, VMASST_TYPE_writable_pagetables)) )
if ( likely(VM_ASSIST(d, VMASST_TYPE_writable_pagetables)) )
{
LOCK_BIGLOCK(d);
if ( unlikely(d->arch.ptwr[PTWR_PT_ACTIVE].l1va) &&
@@ -609,7 +732,10 @@ static int fixup_page_fault(unsigned long addr, struct cpu_user_regs *regs)
if ( guest_kernel_mode(v, regs) &&
/* Protection violation on write? No reserved-bit violation? */
((regs->error_code & 0xb) == 0x3) &&
((regs->error_code & (PGERR_page_present |
PGERR_write_access |
PGERR_reserved_bit)) ==
(PGERR_page_present | PGERR_write_access)) &&
ptwr_do_page_fault(d, addr, regs) )
{
UNLOCK_BIGLOCK(d);
@@ -621,46 +747,6 @@ static int fixup_page_fault(unsigned long addr, struct cpu_user_regs *regs)
return 0;
}
static int spurious_page_fault(unsigned long addr, struct cpu_user_regs *regs)
{
struct vcpu *v = current;
struct domain *d = v->domain;
int rc;
/*
* The only possible reason for a spurious page fault not to be picked
* up already is that a page directory was unhooked by writable page table
* logic and then reattached before the faulting VCPU could detect it.
*/
if ( is_idle_domain(d) || /* no ptwr in idle domain */
IN_HYPERVISOR_RANGE(addr) || /* no ptwr on hypervisor addrs */
shadow_mode_enabled(d) || /* no ptwr logic in shadow mode */
((regs->error_code & 0x1d) != 0) ) /* simple not-present fault? */
return 0;
LOCK_BIGLOCK(d);
/*
* The page directory could have been detached again while we weren't
* holding the per-domain lock. Detect that and fix up if it's the case.
*/
if ( unlikely(d->arch.ptwr[PTWR_PT_ACTIVE].l1va) &&
unlikely(l2_linear_offset(addr) ==
d->arch.ptwr[PTWR_PT_ACTIVE].l2_idx) )
{
ptwr_flush(d, PTWR_PT_ACTIVE);
rc = 1;
}
else
{
/* Okay, walk the page tables. Only check for not-present faults.*/
rc = __spurious_page_fault(addr);
}
UNLOCK_BIGLOCK(d);
return rc;
}
/*
* #PF error code:
* Bit 0: Protection violation (=1) ; Page not present (=0)
@@ -784,8 +870,8 @@ static inline int admin_io_okay(
(admin_io_okay(_p, 4, _d, _r) ? outl(_v, _p) : ((void)0))
/* Propagate a fault back to the guest kernel. */
#define USER_READ_FAULT 4 /* user mode, read fault */
#define USER_WRITE_FAULT 6 /* user mode, write fault */
#define USER_READ_FAULT (PGERR_user_mode)
#define USER_WRITE_FAULT (PGERR_user_mode | PGERR_write_access)
#define PAGE_FAULT(_faultaddr, _errcode) \
({ propagate_page_fault(_faultaddr, _errcode); \
return EXCRET_fault_fixed; \
-34
View File
@@ -113,40 +113,6 @@ void show_page_walk(unsigned long addr)
unmap_domain_page(l1t);
}
int __spurious_page_fault(unsigned long addr)
{
unsigned long mfn = read_cr3() >> PAGE_SHIFT;
#ifdef CONFIG_X86_PAE
l3_pgentry_t l3e, *l3t;
#endif
l2_pgentry_t l2e, *l2t;
l1_pgentry_t l1e, *l1t;
#ifdef CONFIG_X86_PAE
l3t = map_domain_page(mfn);
l3e = l3t[l3_table_offset(addr)];
mfn = l3e_get_pfn(l3e);
unmap_domain_page(l3t);
if ( !(l3e_get_flags(l3e) & _PAGE_PRESENT) )
return 0;
#endif
l2t = map_domain_page(mfn);
l2e = l2t[l2_table_offset(addr)];
mfn = l2e_get_pfn(l2e);
unmap_domain_page(l2t);
if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) )
return 0;
if ( l2e_get_flags(l2e) & _PAGE_PSE )
return 1;
l1t = map_domain_page(mfn);
l1e = l1t[l1_table_offset(addr)];
mfn = l1e_get_pfn(l1e);
unmap_domain_page(l1t);
return !!(l1e_get_flags(l1e) & _PAGE_PRESENT);
}
#define DOUBLEFAULT_STACK_SIZE 1024
static struct tss_struct doublefault_tss;
static unsigned char doublefault_stack[DOUBLEFAULT_STACK_SIZE];
-34
View File
@@ -115,40 +115,6 @@ void show_page_walk(unsigned long addr)
printk(" L1 = %"PRIpte" %016lx\n", l1e_get_intpte(l1e), pfn);
}
int __spurious_page_fault(unsigned long addr)
{
unsigned long mfn = read_cr3() >> PAGE_SHIFT;
l4_pgentry_t l4e, *l4t;
l3_pgentry_t l3e, *l3t;
l2_pgentry_t l2e, *l2t;
l1_pgentry_t l1e, *l1t;
l4t = mfn_to_virt(mfn);
l4e = l4t[l4_table_offset(addr)];
mfn = l4e_get_pfn(l4e);
if ( !(l4e_get_flags(l4e) & _PAGE_PRESENT) )
return 0;
l3t = mfn_to_virt(mfn);
l3e = l3t[l3_table_offset(addr)];
mfn = l3e_get_pfn(l3e);
if ( !(l3e_get_flags(l3e) & _PAGE_PRESENT) )
return 0;
l2t = mfn_to_virt(mfn);
l2e = l2t[l2_table_offset(addr)];
mfn = l2e_get_pfn(l2e);
if ( !(l2e_get_flags(l2e) & _PAGE_PRESENT) )
return 0;
if ( l2e_get_flags(l2e) & _PAGE_PSE )
return 1;
l1t = mfn_to_virt(mfn);
l1e = l1t[l1_table_offset(addr)];
mfn = l1e_get_pfn(l1e);
return !!(l1e_get_flags(l1e) & _PAGE_PRESENT);
}
asmlinkage void double_fault(void);
asmlinkage void do_double_fault(struct cpu_user_regs *regs)
{
+7 -1
View File
@@ -129,6 +129,13 @@
#define _TF_kernel_mode 0
#define TF_kernel_mode (1<<_TF_kernel_mode)
/* #PF error code values. */
#define PGERR_page_present (1U<<0)
#define PGERR_write_access (1U<<1)
#define PGERR_user_mode (1U<<2)
#define PGERR_reserved_bit (1U<<3)
#define PGERR_instr_fetch (1U<<4)
#ifndef __ASSEMBLY__
struct domain;
@@ -524,7 +531,6 @@ extern always_inline void prefetchw(const void *x)
void show_stack(struct cpu_user_regs *regs);
void show_registers(struct cpu_user_regs *regs);
void show_page_walk(unsigned long addr);
int __spurious_page_fault(unsigned long addr);
asmlinkage void fatal_trap(int trapnr, struct cpu_user_regs *regs);
extern void mtrr_ap_init(void);
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.