diff -r 9038a369268d xen/arch/x86/mm.c --- a/xen/arch/x86/mm.c Thu Jul 21 15:50:11 2005 +++ b/xen/arch/x86/mm.c Fri Jul 22 14:45:09 2005 @@ -52,6 +52,8 @@ * This is useful to prevent a page's type count falling to zero, at which * point safety checks would need to be carried out next time the count * is increased again. + * A page pinned as PGT_none cannot be unpinned. This enables a guest to + * surrender write access to a frame as a security measure. * * A further note on writable page mappings: * ----------------------------------------- @@ -1306,6 +1308,7 @@ page->tlbflush_timestamp = tlbflush_current_time(); if ( unlikely((nx & PGT_type_mask) <= PGT_l4_page_table) && + likely( (nx & PGT_type_mask) != PGT_none) && likely(nx & PGT_validated) ) { /* @@ -1376,7 +1379,7 @@ /* No special validation needed for writable pages. */ /* Page tables and GDT/LDT need to be scanned for validity. */ - if ( type == PGT_writable_page ) + if ( (type == PGT_writable_page) || (type == PGT_none) ) nx |= PGT_validated; } } @@ -1686,10 +1689,22 @@ type = PGT_l4_page_table; goto pin_page; + case MMUEXT_PIN_READONLY: + type = PGT_none; + goto pin_page; + case MMUEXT_UNPIN_TABLE: if ( unlikely(!(okay = get_page_from_pagenr(op.mfn, FOREIGNDOM))) ) { MEM_LOG("Mfn %lx bad domain (dom=%p)", + op.mfn, page_get_owner(page)); + } + else if ( unlikely((PGT_type_mask & (page->u.inuse.type_info)) + == PGT_none) ) + { + okay = 0; + MEM_LOG("Attempt to unpin readonly mfn %lx owned by dom %p", + op.mfn, page_get_owner(page)); } else if ( likely(test_and_clear_bit(_PGT_pinned, diff -r 9038a369268d xen/include/public/xen.h --- a/xen/include/public/xen.h Thu Jul 21 15:50:11 2005 +++ b/xen/include/public/xen.h Fri Jul 22 14:45:09 2005 @@ -148,6 +148,10 @@ * cmd: MMUEXT_REASSIGN_PAGE * mfn: Machine frame number to be reassigned to the FD. * (NB. page must currently belong to the calling domain). + * + * cmd: MMUEXT_PIN_READONLY + * mfn: Machine frame number to be pinned as readonly (PGT_none). + * The frame must belong to the FD, if one is specified. */ #define MMUEXT_PIN_L1_TABLE 0 #define MMUEXT_PIN_L2_TABLE 1 @@ -165,6 +169,7 @@ #define MMUEXT_SET_LDT 13 #define MMUEXT_REASSIGN_PAGE 14 #define MMUEXT_NEW_USER_BASEPTR 15 +#define MMUEXT_PIN_READONLY 16 #ifndef __ASSEMBLY__ struct mmuext_op { diff -r 9038a369268d linux-2.6-xen-sparse/arch/xen/i386/mm/init.c --- a/linux-2.6-xen-sparse/arch/xen/i386/mm/init.c Thu Jul 21 15:50:11 2005 +++ b/linux-2.6-xen-sparse/arch/xen/i386/mm/init.c Fri Jul 22 14:45:09 2005 @@ -41,6 +41,10 @@ #include #include +extern void *sys_call_table; + +int readonly_pin_frames = 0; + unsigned int __VMALLOC_RESERVE = 128 << 20; DEFINE_PER_CPU(struct mmu_gather, mmu_gathers); @@ -183,10 +187,16 @@ continue; /* Map with big pages if possible, otherwise create normal page tables. */ - if (cpu_has_pse) { + /* XXX: avoid using big pages if readonly pinning: need finer grain + * selection of pinned frames (4K vs 4M). + */ + if (cpu_has_pse && !readonly_pin_frames) + { unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1; - if (is_kernel_text(address) || is_kernel_text(address2)) + if (is_kernel_text(address) && is_kernel_text(address2)) + set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC_RO)); + else if (is_kernel_text(address) || is_kernel_text(address2)) set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC)); else set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE)); @@ -200,7 +210,7 @@ if ((pfn >= max_ram_pfn) || pte_present(*pte)) continue; if (is_kernel_text(address)) - set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC)); + set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC_RO)); else set_pte(pte, pfn_pte(pfn, PAGE_KERNEL)); } @@ -327,6 +337,7 @@ unsigned long long __PAGE_KERNEL = _PAGE_KERNEL; unsigned long long __PAGE_KERNEL_EXEC = _PAGE_KERNEL_EXEC; +unsigned long long __PAGE_KERNEL_EXEC_RO = _PAGE_KERNEL_EXEC_RO; #ifndef CONFIG_DISCONTIGMEM #define remap_numa_kva() do {} while (0) @@ -357,6 +368,7 @@ set_in_cr4(X86_CR4_PGE); __PAGE_KERNEL |= _PAGE_GLOBAL; __PAGE_KERNEL_EXEC |= _PAGE_GLOBAL; + __PAGE_KERNEL_EXEC_RO |= _PAGE_GLOBAL; } /* @@ -712,6 +724,22 @@ #ifndef CONFIG_SMP zap_low_mappings(); #endif + + if (readonly_pin_frames) + { + /* + * irrevocable surrender of write access to the system call table + * and zero page. + * XXX: may also protect any other readonly frames here too. + */ + make_lowmem_page_readonly( empty_zero_page ); + make_lowmem_page_readonly( sys_call_table ); + + xen_readonly_pin(__pa(empty_zero_page)); + xen_readonly_pin(__pa(sys_call_table)); + + printk("Read-only frame pinning protection: active\n"); + } } kmem_cache_t *pgd_cache; diff -r 9038a369268d linux-2.6-xen-sparse/arch/xen/i386/mm/hypervisor.c --- a/linux-2.6-xen-sparse/arch/xen/i386/mm/hypervisor.c Thu Jul 21 15:50:11 2005 +++ b/linux-2.6-xen-sparse/arch/xen/i386/mm/hypervisor.c Fri Jul 22 14:45:09 2005 @@ -179,6 +179,64 @@ } #endif /* CONFIG_SMP */ + +/* + * A guest may irrevocably pin the type of a frame it owns to be read-only, + * surrendering write-access in order to protect itself from attack from within. + * Such frames are only unpinned when the domain is destroyed. + * This call will fail if the specified frame currently has writable mappings. + */ +void xen_readonly_pin(unsigned long ptr) +{ + struct mmuext_op op; + op.cmd = MMUEXT_PIN_READONLY; + op.mfn = pfn_to_mfn(ptr >> PAGE_SHIFT); + BUG_ON(HYPERVISOR_mmuext_op(&op, 1, NULL, DOMID_SELF) < 0); +} + +#define RO_PIN_BATCH 16 +void xen_readonly_pin_pfn_range(memory_t start_pfn, unsigned long nr) +{ + unsigned int i, batch; + memory_t pfn, upto; + multicall_entry_t _mcl[RO_PIN_BATCH], *mcl = _mcl; + struct mmuext_op mmuext[RO_PIN_BATCH]; + + for ( i = 0; i < RO_PIN_BATCH; i++ ) + { + mcl[i].op = __HYPERVISOR_mmuext_op; + mcl[i].args[0] = (memory_t)(&mmuext[i]); + mcl[i].args[1] = 1; + mcl[i].args[2] = 0; + mcl[i].args[3] = DOMID_SELF; + + mmuext[i].cmd = MMUEXT_PIN_READONLY; + } + + batch = nr; + upto = 0; + pfn = start_pfn; + + do { + if ( batch > RO_PIN_BATCH ) + batch = RO_PIN_BATCH; + + for ( i = 0; i < batch; i++ ) + { + mmuext[i].mfn = pfn_to_mfn(pfn); + pfn++; + } + + BUG_ON(HYPERVISOR_multicall(mcl, batch) < 0); + + for ( i = 0; i < batch; i++ ) + BUG_ON(mcl[i].result != 0); + + upto += RO_PIN_BATCH; + batch = nr - upto; + + } while ( upto < nr ); +} #ifndef CONFIG_XEN_SHADOW_MODE void xen_pgd_pin(unsigned long ptr) diff -r 9038a369268d linux-2.6-xen-sparse/arch/xen/i386/kernel/setup.c --- a/linux-2.6-xen-sparse/arch/xen/i386/kernel/setup.c Thu Jul 21 15:50:11 2005 +++ b/linux-2.6-xen-sparse/arch/xen/i386/kernel/setup.c Fri Jul 22 14:45:09 2005 @@ -95,6 +95,9 @@ int __initdata acpi_force = 0; extern acpi_interrupt_flags acpi_sci_flags; #endif + +/* flag indicating whether to pin certain frames readonly as security measure*/ +extern int readonly_pin_frames; /* for MCA, but anyone else can use it if they want */ unsigned int machine_id; @@ -852,6 +855,13 @@ disable_ioapic_setup(); #endif /* CONFIG_X86_LOCAL_APIC */ #endif /* CONFIG_ACPI_BOOT */ + + /* + * Irrevocable surrender of write access to certain memory frames + * (eg. system call table) as a security precaution. + */ + else if (!memcmp(from, "roprotect", 9)) + readonly_pin_frames = 1; /* * highmem=size forces highmem to be exactly 'size' bytes. diff -r 9038a369268d linux-2.6-xen-sparse/arch/xen/i386/kernel/entry.S --- a/linux-2.6-xen-sparse/arch/xen/i386/kernel/entry.S Thu Jul 21 15:50:11 2005 +++ b/linux-2.6-xen-sparse/arch/xen/i386/kernel/entry.S Fri Jul 22 14:45:09 2005 @@ -752,6 +752,9 @@ pushl $do_fixup_4gb_segment jmp error_code +# Align the syscall table into its own frame(s) to enable readonly protection +.align 4096 #include "syscall_table.S" syscall_table_size=(.-sys_call_table) +.align 4096 diff -r 9038a369268d linux-2.6-xen-sparse/include/asm-xen/hypervisor.h --- a/linux-2.6-xen-sparse/include/asm-xen/hypervisor.h Thu Jul 21 15:50:11 2005 +++ b/linux-2.6-xen-sparse/include/asm-xen/hypervisor.h Fri Jul 22 14:45:09 2005 @@ -104,6 +104,9 @@ #define xen_pte_pin(_p) ((void)0) #define xen_pte_unpin(_p) ((void)0) #endif +/* Read-only pins are permanent, irrevocable, to protect memory from attacks. */ +void xen_readonly_pin(unsigned long ptr); +void xen_readonly_pin_pfn_range(memory_t start_pfn, unsigned long nr); void xen_set_ldt(unsigned long ptr, unsigned long bytes); void xen_machphys_update(unsigned long mfn, unsigned long pfn); diff -r 9038a369268d linux-2.6-xen-sparse/include/asm-xen/asm-x86_64/pgtable.h --- a/linux-2.6-xen-sparse/include/asm-xen/asm-x86_64/pgtable.h Thu Jul 21 15:50:11 2005 +++ b/linux-2.6-xen-sparse/include/asm-xen/asm-x86_64/pgtable.h Fri Jul 22 14:45:09 2005 @@ -202,6 +202,8 @@ (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_NX | _PAGE_USER ) #define __PAGE_KERNEL_EXEC \ (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_USER ) +#define __PAGE_KERNEL_EXEC_RO \ + (_PAGE_PRESENT | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_USER ) #define __PAGE_KERNEL_NOCACHE \ (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_PCD | _PAGE_ACCESSED | _PAGE_NX | _PAGE_USER ) #define __PAGE_KERNEL_RO \ diff -r 9038a369268d linux-2.6-xen-sparse/include/asm-xen/asm-i386/pgtable.h --- a/linux-2.6-xen-sparse/include/asm-xen/asm-i386/pgtable.h Thu Jul 21 15:50:11 2005 +++ b/linux-2.6-xen-sparse/include/asm-xen/asm-i386/pgtable.h Fri Jul 22 14:45:09 2005 @@ -154,19 +154,25 @@ (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED | _PAGE_NX) #define _PAGE_KERNEL_EXEC \ (_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED) - -extern unsigned long long __PAGE_KERNEL, __PAGE_KERNEL_EXEC; +#define _PAGE_KERNEL_EXEC_RO \ + (_PAGE_PRESENT | _PAGE_DIRTY | _PAGE_ACCESSED) + +extern unsigned long long __PAGE_KERNEL, __PAGE_KERNEL_EXEC, + __PAGE_KERNEL_EXEC_RO; #define __PAGE_KERNEL_RO (__PAGE_KERNEL & ~_PAGE_RW) #define __PAGE_KERNEL_NOCACHE (__PAGE_KERNEL | _PAGE_PCD) #define __PAGE_KERNEL_LARGE (__PAGE_KERNEL | _PAGE_PSE) #define __PAGE_KERNEL_LARGE_EXEC (__PAGE_KERNEL_EXEC | _PAGE_PSE) +#define __PAGE_KERNEL_LARGE_EXEC_RO (__PAGE_KERNEL_EXEC_RO | _PAGE_PSE) #define PAGE_KERNEL __pgprot(__PAGE_KERNEL) #define PAGE_KERNEL_RO __pgprot(__PAGE_KERNEL_RO) #define PAGE_KERNEL_EXEC __pgprot(__PAGE_KERNEL_EXEC) +#define PAGE_KERNEL_EXEC_RO __pgprot(__PAGE_KERNEL_EXEC_RO) #define PAGE_KERNEL_NOCACHE __pgprot(__PAGE_KERNEL_NOCACHE) #define PAGE_KERNEL_LARGE __pgprot(__PAGE_KERNEL_LARGE) #define PAGE_KERNEL_LARGE_EXEC __pgprot(__PAGE_KERNEL_LARGE_EXEC) +#define PAGE_KERNEL_LARGE_EXEC_RO __pgprot(__PAGE_KERNEL_LARGE_EXEC_RO) /* * The i386 can't do page protection for execute, and considers that