Skip to content

Commit

Permalink
x86: Remap GDT tables in the fixmap section
Browse files Browse the repository at this point in the history
Each processor holds a GDT in its per-cpu structure. The sgdt
instruction gives the base address of the current GDT. This address can
be used to bypass KASLR memory randomization. With another bug, an
attacker could target other per-cpu structures or deduce the base of
the main memory section (PAGE_OFFSET).

This patch relocates the GDT table for each processor inside the
fixmap section. The space is reserved based on number of supported
processors.

For consistency, the remapping is done by default on 32 and 64-bit.

Each processor switches to its remapped GDT at the end of
initialization. For hibernation, the main processor returns with the
original GDT and switches back to the remapping at completion.

This patch was tested on both architectures. Hibernation and KVM were
both tested specially for their usage of the GDT.

Thanks to Boris Ostrovsky <[email protected]> for testing and
recommending changes for Xen support.

Signed-off-by: Thomas Garnier <[email protected]>
Cc: Alexander Potapenko <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Andrey Ryabinin <[email protected]>
Cc: Andy Lutomirski <[email protected]>
Cc: Ard Biesheuvel <[email protected]>
Cc: Boris Ostrovsky <[email protected]>
Cc: Borislav Petkov <[email protected]>
Cc: Chris Wilson <[email protected]>
Cc: Christian Borntraeger <[email protected]>
Cc: Dmitry Vyukov <[email protected]>
Cc: Frederic Weisbecker <[email protected]>
Cc: Jiri Kosina <[email protected]>
Cc: Joerg Roedel <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: Josh Poimboeuf <[email protected]>
Cc: Juergen Gross <[email protected]>
Cc: Kees Cook <[email protected]>
Cc: Len Brown <[email protected]>
Cc: Linus Torvalds <[email protected]>
Cc: Lorenzo Stoakes <[email protected]>
Cc: Luis R . Rodriguez <[email protected]>
Cc: Matt Fleming <[email protected]>
Cc: Michal Hocko <[email protected]>
Cc: Paolo Bonzini <[email protected]>
Cc: Paul Gortmaker <[email protected]>
Cc: Pavel Machek <[email protected]>
Cc: Peter Zijlstra <[email protected]>
Cc: Radim Krčmář <[email protected]>
Cc: Rafael J . Wysocki <[email protected]>
Cc: Rusty Russell <[email protected]>
Cc: Stanislaw Gruszka <[email protected]>
Cc: Thomas Gleixner <[email protected]>
Cc: Tim Chen <[email protected]>
Cc: Vitaly Kuznetsov <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: zijun_hu <[email protected]>
Link: http://lkml.kernel.org/r/[email protected]
Signed-off-by: Ingo Molnar <[email protected]>
  • Loading branch information
thgarnie authored and Ingo Molnar committed Mar 16, 2017
1 parent f06bdd4 commit 69218e4
Show file tree
Hide file tree
Showing 17 changed files with 114 additions and 29 deletions.
2 changes: 1 addition & 1 deletion arch/x86/entry/vdso/vma.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ static void vgetcpu_cpu_init(void *arg)
d.p = 1; /* Present */
d.d = 1; /* 32-bit */

write_gdt_entry(get_cpu_gdt_table(cpu), GDT_ENTRY_PER_CPU, &d, DESCTYPE_S);
write_gdt_entry(get_cpu_gdt_rw(cpu), GDT_ENTRY_PER_CPU, &d, DESCTYPE_S);
}

static int vgetcpu_online(unsigned int cpu)
Expand Down
58 changes: 53 additions & 5 deletions arch/x86/include/asm/desc.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <asm/desc_defs.h>
#include <asm/ldt.h>
#include <asm/mmu.h>
#include <asm/fixmap.h>

#include <linux/smp.h>
#include <linux/percpu.h>
Expand Down Expand Up @@ -38,18 +39,65 @@ extern struct desc_ptr idt_descr;
extern gate_desc idt_table[];
extern const struct desc_ptr debug_idt_descr;
extern gate_desc debug_idt_table[];
extern pgprot_t pg_fixmap_gdt_flags;

struct gdt_page {
struct desc_struct gdt[GDT_ENTRIES];
} __attribute__((aligned(PAGE_SIZE)));

DECLARE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page);

static inline struct desc_struct *get_cpu_gdt_table(unsigned int cpu)
/* Provide the original GDT */
static inline struct desc_struct *get_cpu_gdt_rw(unsigned int cpu)
{
return per_cpu(gdt_page, cpu).gdt;
}

static inline unsigned long get_cpu_gdt_rw_vaddr(unsigned int cpu)
{
return (unsigned long)get_cpu_gdt_rw(cpu);
}

/* Provide the current original GDT */
static inline struct desc_struct *get_current_gdt_rw(void)
{
return this_cpu_ptr(&gdt_page)->gdt;
}

static inline unsigned long get_current_gdt_rw_vaddr(void)
{
return (unsigned long)get_current_gdt_rw();
}

/* Get the fixmap index for a specific processor */
static inline unsigned int get_cpu_gdt_ro_index(int cpu)
{
return FIX_GDT_REMAP_BEGIN + cpu;
}

/* Provide the fixmap address of the remapped GDT */
static inline struct desc_struct *get_cpu_gdt_ro(int cpu)
{
unsigned int idx = get_cpu_gdt_ro_index(cpu);
return (struct desc_struct *)__fix_to_virt(idx);
}

static inline unsigned long get_cpu_gdt_ro_vaddr(int cpu)
{
return (unsigned long)get_cpu_gdt_ro(cpu);
}

/* Provide the current read-only GDT */
static inline struct desc_struct *get_current_gdt_ro(void)
{
return get_cpu_gdt_ro(smp_processor_id());
}

static inline unsigned long get_current_gdt_ro_vaddr(void)
{
return (unsigned long)get_current_gdt_ro();
}

#ifdef CONFIG_X86_64

static inline void pack_gate(gate_desc *gate, unsigned type, unsigned long func,
Expand Down Expand Up @@ -174,7 +222,7 @@ static inline void set_tssldt_descriptor(void *d, unsigned long addr, unsigned t

static inline void __set_tss_desc(unsigned cpu, unsigned int entry, void *addr)
{
struct desc_struct *d = get_cpu_gdt_table(cpu);
struct desc_struct *d = get_cpu_gdt_rw(cpu);
tss_desc tss;

set_tssldt_descriptor(&tss, (unsigned long)addr, DESC_TSS,
Expand All @@ -194,7 +242,7 @@ static inline void native_set_ldt(const void *addr, unsigned int entries)

set_tssldt_descriptor(&ldt, (unsigned long)addr, DESC_LDT,
entries * LDT_ENTRY_SIZE - 1);
write_gdt_entry(get_cpu_gdt_table(cpu), GDT_ENTRY_LDT,
write_gdt_entry(get_cpu_gdt_rw(cpu), GDT_ENTRY_LDT,
&ldt, DESC_LDT);
asm volatile("lldt %w0"::"q" (GDT_ENTRY_LDT*8));
}
Expand All @@ -209,7 +257,7 @@ DECLARE_PER_CPU(bool, __tss_limit_invalid);

static inline void force_reload_TR(void)
{
struct desc_struct *d = get_cpu_gdt_table(smp_processor_id());
struct desc_struct *d = get_current_gdt_rw();
tss_desc tss;

memcpy(&tss, &d[GDT_ENTRY_TSS], sizeof(tss_desc));
Expand Down Expand Up @@ -288,7 +336,7 @@ static inline unsigned long native_store_tr(void)

static inline void native_load_tls(struct thread_struct *t, unsigned int cpu)
{
struct desc_struct *gdt = get_cpu_gdt_table(cpu);
struct desc_struct *gdt = get_cpu_gdt_rw(cpu);
unsigned int i;

for (i = 0; i < GDT_ENTRY_TLS_ENTRIES; i++)
Expand Down
4 changes: 4 additions & 0 deletions arch/x86/include/asm/fixmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ enum fixed_addresses {
#ifdef CONFIG_X86_INTEL_MID
FIX_LNW_VRTC,
#endif
/* Fixmap entries to remap the GDTs, one per processor. */
FIX_GDT_REMAP_BEGIN,
FIX_GDT_REMAP_END = FIX_GDT_REMAP_BEGIN + NR_CPUS - 1,

__end_of_permanent_fixed_addresses,

/*
Expand Down
1 change: 1 addition & 0 deletions arch/x86/include/asm/processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,7 @@ extern struct desc_ptr early_gdt_descr;

extern void cpu_set_gdt(int);
extern void switch_to_new_gdt(int);
extern void load_fixmap_gdt(int);
extern void load_percpu_segment(int);
extern void cpu_init(void);

Expand Down
2 changes: 1 addition & 1 deletion arch/x86/include/asm/stackprotector.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ static inline void setup_stack_canary_segment(int cpu)
{
#ifdef CONFIG_X86_32
unsigned long canary = (unsigned long)&per_cpu(stack_canary, cpu);
struct desc_struct *gdt_table = get_cpu_gdt_table(cpu);
struct desc_struct *gdt_table = get_cpu_gdt_rw(cpu);
struct desc_struct desc;

desc = gdt_table[GDT_ENTRY_STACK_CANARY];
Expand Down
2 changes: 1 addition & 1 deletion arch/x86/kernel/acpi/sleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ int x86_acpi_suspend_lowlevel(void)
#ifdef CONFIG_SMP
initial_stack = (unsigned long)temp_stack + sizeof(temp_stack);
early_gdt_descr.address =
(unsigned long)get_cpu_gdt_table(smp_processor_id());
(unsigned long)get_cpu_gdt_rw(smp_processor_id());
initial_gs = per_cpu_offset(smp_processor_id());
#endif
initial_code = (unsigned long)wakeup_long64;
Expand Down
6 changes: 3 additions & 3 deletions arch/x86/kernel/apm_32.c
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ static long __apm_bios_call(void *_call)

cpu = get_cpu();
BUG_ON(cpu != 0);
gdt = get_cpu_gdt_table(cpu);
gdt = get_cpu_gdt_rw(cpu);
save_desc_40 = gdt[0x40 / 8];
gdt[0x40 / 8] = bad_bios_desc;

Expand Down Expand Up @@ -685,7 +685,7 @@ static long __apm_bios_call_simple(void *_call)

cpu = get_cpu();
BUG_ON(cpu != 0);
gdt = get_cpu_gdt_table(cpu);
gdt = get_cpu_gdt_rw(cpu);
save_desc_40 = gdt[0x40 / 8];
gdt[0x40 / 8] = bad_bios_desc;

Expand Down Expand Up @@ -2352,7 +2352,7 @@ static int __init apm_init(void)
* Note we only set APM segments on CPU zero, since we pin the APM
* code to that CPU.
*/
gdt = get_cpu_gdt_table(0);
gdt = get_cpu_gdt_rw(0);
set_desc_base(&gdt[APM_CS >> 3],
(unsigned long)__va((unsigned long)apm_info.bios.cseg << 4));
set_desc_base(&gdt[APM_CS_16 >> 3],
Expand Down
29 changes: 27 additions & 2 deletions arch/x86/kernel/cpu/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,26 @@ void load_percpu_segment(int cpu)
load_stack_canary_segment();
}

/* Used by XEN to force the GDT read-only when required */
pgprot_t pg_fixmap_gdt_flags = PAGE_KERNEL;

/* Setup the fixmap mapping only once per-processor */
static inline void setup_fixmap_gdt(int cpu)
{
__set_fixmap(get_cpu_gdt_ro_index(cpu),
__pa(get_cpu_gdt_rw(cpu)), pg_fixmap_gdt_flags);
}

/* Load a fixmap remapping of the per-cpu GDT */
void load_fixmap_gdt(int cpu)
{
struct desc_ptr gdt_descr;

gdt_descr.address = (long)get_cpu_gdt_ro(cpu);
gdt_descr.size = GDT_SIZE - 1;
load_gdt(&gdt_descr);
}

/*
* Current gdt points %fs at the "master" per-cpu area: after this,
* it's on the real one.
Expand All @@ -456,11 +476,10 @@ void switch_to_new_gdt(int cpu)
{
struct desc_ptr gdt_descr;

gdt_descr.address = (long)get_cpu_gdt_table(cpu);
gdt_descr.address = (long)get_cpu_gdt_rw(cpu);
gdt_descr.size = GDT_SIZE - 1;
load_gdt(&gdt_descr);
/* Reload the per-cpu base */

load_percpu_segment(cpu);
}

Expand Down Expand Up @@ -1526,6 +1545,9 @@ void cpu_init(void)

if (is_uv_system())
uv_cpu_init();

setup_fixmap_gdt(cpu);
load_fixmap_gdt(cpu);
}

#else
Expand Down Expand Up @@ -1581,6 +1603,9 @@ void cpu_init(void)
dbg_restore_debug_regs();

fpu__init_cpu();

setup_fixmap_gdt(cpu);
load_fixmap_gdt(cpu);
}
#endif

Expand Down
2 changes: 1 addition & 1 deletion arch/x86/kernel/setup_percpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ static inline void setup_percpu_segment(int cpu)
pack_descriptor(&gdt, per_cpu_offset(cpu), 0xFFFFF,
0x2 | DESCTYPE_S, 0x8);
gdt.s = 1;
write_gdt_entry(get_cpu_gdt_table(cpu),
write_gdt_entry(get_cpu_gdt_rw(cpu),
GDT_ENTRY_PERCPU, &gdt, DESCTYPE_S);
#endif
}
Expand Down
2 changes: 1 addition & 1 deletion arch/x86/kernel/smpboot.c
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,7 @@ static int do_boot_cpu(int apicid, int cpu, struct task_struct *idle)
unsigned long timeout;

idle->thread.sp = (unsigned long)task_pt_regs(idle);
early_gdt_descr.address = (unsigned long)get_cpu_gdt_table(cpu);
early_gdt_descr.address = (unsigned long)get_cpu_gdt_rw(cpu);
initial_code = (unsigned long)start_secondary;
initial_stack = idle->thread.sp;

Expand Down
4 changes: 2 additions & 2 deletions arch/x86/platform/efi/efi_32.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pgd_t * __init efi_call_phys_prolog(void)
load_cr3(initial_page_table);
__flush_tlb_all();

gdt_descr.address = __pa(get_cpu_gdt_table(0));
gdt_descr.address = __pa(get_cpu_gdt_rw(0));
gdt_descr.size = GDT_SIZE - 1;
load_gdt(&gdt_descr);

Expand All @@ -79,7 +79,7 @@ void __init efi_call_phys_epilog(pgd_t *save_pgd)
{
struct desc_ptr gdt_descr;

gdt_descr.address = (unsigned long)get_cpu_gdt_table(0);
gdt_descr.address = (unsigned long)get_cpu_gdt_rw(0);
gdt_descr.size = GDT_SIZE - 1;
load_gdt(&gdt_descr);

Expand Down
7 changes: 5 additions & 2 deletions arch/x86/power/cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ static void __save_processor_state(struct saved_context *ctxt)
* 'pmode_gdt' in wakeup_start.
*/
ctxt->gdt_desc.size = GDT_SIZE - 1;
ctxt->gdt_desc.address = (unsigned long)get_cpu_gdt_table(smp_processor_id());
ctxt->gdt_desc.address = (unsigned long)get_cpu_gdt_rw(smp_processor_id());

store_tr(ctxt->tr);

Expand Down Expand Up @@ -162,7 +162,7 @@ static void fix_processor_context(void)
int cpu = smp_processor_id();
struct tss_struct *t = &per_cpu(cpu_tss, cpu);
#ifdef CONFIG_X86_64
struct desc_struct *desc = get_cpu_gdt_table(cpu);
struct desc_struct *desc = get_cpu_gdt_rw(cpu);
tss_desc tss;
#endif
set_tss_desc(cpu, t); /*
Expand All @@ -183,6 +183,9 @@ static void fix_processor_context(void)
load_mm_ldt(current->active_mm); /* This does lldt */

fpu__resume_cpu();

/* The processor is back on the direct GDT, load back the fixmap */
load_fixmap_gdt(cpu);
}

/**
Expand Down
5 changes: 4 additions & 1 deletion arch/x86/xen/enlighten.c
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,7 @@ static void load_TLS_descriptor(struct thread_struct *t,

*shadow = t->tls_array[i];

gdt = get_cpu_gdt_table(cpu);
gdt = get_cpu_gdt_rw(cpu);
maddr = arbitrary_virt_to_machine(&gdt[GDT_ENTRY_TLS_MIN+i]);
mc = __xen_mc_entry(0);

Expand Down Expand Up @@ -1545,6 +1545,9 @@ asmlinkage __visible void __init xen_start_kernel(void)
*/
xen_initial_gdt = &per_cpu(gdt_page, 0);

/* GDT can only be remapped RO */
pg_fixmap_gdt_flags = PAGE_KERNEL_RO;

xen_smp_init();

#ifdef CONFIG_ACPI_NUMA
Expand Down
1 change: 1 addition & 0 deletions arch/x86/xen/mmu.c
Original file line number Diff line number Diff line change
Expand Up @@ -2326,6 +2326,7 @@ static void xen_set_fixmap(unsigned idx, phys_addr_t phys, pgprot_t prot)
#endif
case FIX_TEXT_POKE0:
case FIX_TEXT_POKE1:
case FIX_GDT_REMAP_BEGIN ... FIX_GDT_REMAP_END:
/* All local page mappings */
pte = pfn_pte(phys, prot);
break;
Expand Down
2 changes: 1 addition & 1 deletion arch/x86/xen/smp.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ cpu_initialize_context(unsigned int cpu, struct task_struct *idle)
if (ctxt == NULL)
return -ENOMEM;

gdt = get_cpu_gdt_table(cpu);
gdt = get_cpu_gdt_rw(cpu);

#ifdef CONFIG_X86_32
ctxt->user_regs.fs = __KERNEL_PERCPU;
Expand Down
6 changes: 3 additions & 3 deletions drivers/lguest/x86/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ void __init lguest_arch_host_init(void)
* byte, not the size, hence the "-1").
*/
state->host_gdt_desc.size = GDT_SIZE-1;
state->host_gdt_desc.address = (long)get_cpu_gdt_table(i);
state->host_gdt_desc.address = (long)get_cpu_gdt_rw(i);

/*
* All CPUs on the Host use the same Interrupt Descriptor
Expand Down Expand Up @@ -554,8 +554,8 @@ void __init lguest_arch_host_init(void)
* The Host needs to be able to use the LGUEST segments on this
* CPU, too, so put them in the Host GDT.
*/
get_cpu_gdt_table(i)[GDT_ENTRY_LGUEST_CS] = FULL_EXEC_SEGMENT;
get_cpu_gdt_table(i)[GDT_ENTRY_LGUEST_DS] = FULL_SEGMENT;
get_cpu_gdt_rw(i)[GDT_ENTRY_LGUEST_CS] = FULL_EXEC_SEGMENT;
get_cpu_gdt_rw(i)[GDT_ENTRY_LGUEST_DS] = FULL_SEGMENT;
}

/*
Expand Down
Loading

0 comments on commit 69218e4

Please sign in to comment.