Commit 89d637a8 authored by Ingo Molnar's avatar Ingo Molnar Committed by Linus Torvalds

[PATCH] ldt-fix-2.5.32-A3

this is an updated version of the LDT fixes. It fixes the following kinds
of problems:

 - fix a possible gcc optimization causing a race causing the loading of a
   corrupt LDT descriptor upon context switch. [this fix got simplified
   over previous versions.]

 - remove an unconditional OOM printk, and there's no need to set ->size
   in the OOM path.

 - fix preemption bugs, load_LDT()/clear_LDT() was not preemption-safe,
   when it was used outside of spinlocks.

the context-switch race is the following. 'LDT modification' is the
following operation: the seg->ldt pointer is modified, then seg->size is
modified. In theory gcc is free to reschedule the two modifications, and
first modify ->size, then ->ldt. Thus if this modification is not
synchronized with context-switches, another thread might see a temporary
state of the new ->size [which was increased], but still the old pointer.
Ie.:

	CPU0				CPU1

	pc->size = newsize;
					load_LDT(); // (oldptr, newsize)
	pc->ldt = newptr;

the corrupt LDT is loaded until the SMP cross-call is sent, leaving the
window open for many usecs.

the fix is to put a wmb() after ->ldt modifications. [this is also in
preparation of not-write-ordered SMP x86 designs.]
parent e5d588fe
...@@ -49,17 +49,20 @@ static int alloc_ldt(mm_context_t *pc, int mincount, int reload) ...@@ -49,17 +49,20 @@ static int alloc_ldt(mm_context_t *pc, int mincount, int reload)
memcpy(newldt, pc->ldt, oldsize*LDT_ENTRY_SIZE); memcpy(newldt, pc->ldt, oldsize*LDT_ENTRY_SIZE);
oldldt = pc->ldt; oldldt = pc->ldt;
memset(newldt+oldsize*LDT_ENTRY_SIZE, 0, (mincount-oldsize)*LDT_ENTRY_SIZE); memset(newldt+oldsize*LDT_ENTRY_SIZE, 0, (mincount-oldsize)*LDT_ENTRY_SIZE);
wmb();
pc->ldt = newldt; pc->ldt = newldt;
wmb();
pc->size = mincount; pc->size = mincount;
wmb();
if (reload) { if (reload) {
load_LDT(pc); load_LDT(pc);
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
if (current->mm->cpu_vm_mask != (1<<smp_processor_id())) preempt_disable();
if (current->mm->cpu_vm_mask != (1 << smp_processor_id()))
smp_call_function(flush_ldt, 0, 1, 1); smp_call_function(flush_ldt, 0, 1, 1);
preempt_enable();
#endif #endif
} }
wmb();
if (oldsize) { if (oldsize) {
if (oldsize*LDT_ENTRY_SIZE > PAGE_SIZE) if (oldsize*LDT_ENTRY_SIZE > PAGE_SIZE)
vfree(oldldt); vfree(oldldt);
...@@ -72,11 +75,8 @@ static int alloc_ldt(mm_context_t *pc, int mincount, int reload) ...@@ -72,11 +75,8 @@ static int alloc_ldt(mm_context_t *pc, int mincount, int reload)
static inline int copy_ldt(mm_context_t *new, mm_context_t *old) static inline int copy_ldt(mm_context_t *new, mm_context_t *old)
{ {
int err = alloc_ldt(new, old->size, 0); int err = alloc_ldt(new, old->size, 0);
if (err < 0) { if (err < 0)
printk(KERN_WARNING "ldt allocation failed\n");
new->size = 0;
return err; return err;
}
memcpy(new->ldt, old->ldt, old->size*LDT_ENTRY_SIZE); memcpy(new->ldt, old->ldt, old->size*LDT_ENTRY_SIZE);
return 0; return 0;
} }
......
...@@ -86,14 +86,17 @@ static inline void load_TLS(struct thread_struct *t, unsigned int cpu) ...@@ -86,14 +86,17 @@ static inline void load_TLS(struct thread_struct *t, unsigned int cpu)
static inline void clear_LDT(void) static inline void clear_LDT(void)
{ {
set_ldt_desc(smp_processor_id(), &default_ldt[0], 5); int cpu = get_cpu();
set_ldt_desc(cpu, &default_ldt[0], 5);
load_LDT_desc(); load_LDT_desc();
put_cpu();
} }
/* /*
* load one particular LDT into the current CPU * load one particular LDT into the current CPU
*/ */
static inline void load_LDT (mm_context_t *pc) static inline void load_LDT_nolock(mm_context_t *pc, int cpu)
{ {
void *segments = pc->ldt; void *segments = pc->ldt;
int count = pc->size; int count = pc->size;
...@@ -103,10 +106,17 @@ static inline void load_LDT (mm_context_t *pc) ...@@ -103,10 +106,17 @@ static inline void load_LDT (mm_context_t *pc)
count = 5; count = 5;
} }
set_ldt_desc(smp_processor_id(), segments, count); set_ldt_desc(cpu, segments, count);
load_LDT_desc(); load_LDT_desc();
} }
static inline void load_LDT(mm_context_t *pc)
{
int cpu = get_cpu();
load_LDT_nolock(pc, cpu);
put_cpu();
}
#endif /* !__ASSEMBLY__ */ #endif /* !__ASSEMBLY__ */
#endif #endif
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
typedef struct { typedef struct {
int size; int size;
struct semaphore sem; struct semaphore sem;
void * ldt; void *ldt;
} mm_context_t; } mm_context_t;
#endif #endif
...@@ -44,7 +44,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str ...@@ -44,7 +44,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str
* load the LDT, if the LDT is different: * load the LDT, if the LDT is different:
*/ */
if (unlikely(prev->context.ldt != next->context.ldt)) if (unlikely(prev->context.ldt != next->context.ldt))
load_LDT(&next->context); load_LDT_nolock(&next->context, cpu);
} }
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
else { else {
...@@ -56,7 +56,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str ...@@ -56,7 +56,7 @@ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, str
* tlb flush IPI delivery. We must reload %cr3. * tlb flush IPI delivery. We must reload %cr3.
*/ */
load_cr3(next->pgd); load_cr3(next->pgd);
load_LDT(&next->context); load_LDT_nolock(&next->context, cpu);
} }
} }
#endif #endif
......
Markdown is supported
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