| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (C) 1992 Krishna Balasubramanian and Linus Torvalds | 
					
						
							|  |  |  |  * Copyright (C) 1999 Ingo Molnar <mingo@redhat.com> | 
					
						
							|  |  |  |  * Copyright (C) 2002 Andi Kleen | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |  * This handles calls from both 32bit and 64bit mode. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/errno.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/string.h>
 | 
					
						
							|  |  |  | #include <linux/mm.h>
 | 
					
						
							|  |  |  | #include <linux/smp.h>
 | 
					
						
							|  |  |  | #include <linux/vmalloc.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <asm/uaccess.h>
 | 
					
						
							|  |  |  | #include <asm/system.h>
 | 
					
						
							|  |  |  | #include <asm/ldt.h>
 | 
					
						
							|  |  |  | #include <asm/desc.h>
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | #include <asm/mmu_context.h>
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | #ifdef CONFIG_SMP
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static void flush_ldt(void *null) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (current->active_mm) | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 		load_LDT(¤t->active_mm->context); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | static int alloc_ldt(mm_context_t *pc, int mincount, int reload) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	void *oldldt, *newldt; | 
					
						
							|  |  |  | 	int oldsize; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	if (mincount <= pc->size) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return 0; | 
					
						
							|  |  |  | 	oldsize = pc->size; | 
					
						
							| 
									
										
										
										
											2008-02-04 16:48:03 +01:00
										 |  |  | 	mincount = (mincount + (PAGE_SIZE / LDT_ENTRY_SIZE - 1)) & | 
					
						
							|  |  |  | 			(~(PAGE_SIZE / LDT_ENTRY_SIZE - 1)); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	if (mincount * LDT_ENTRY_SIZE > PAGE_SIZE) | 
					
						
							|  |  |  | 		newldt = vmalloc(mincount * LDT_ENTRY_SIZE); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	else | 
					
						
							| 
									
										
										
										
											2008-01-30 13:33:14 +01:00
										 |  |  | 		newldt = (void *)__get_free_page(GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (!newldt) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (oldsize) | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 		memcpy(newldt, pc->ldt, oldsize * LDT_ENTRY_SIZE); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	oldldt = pc->ldt; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	memset(newldt + oldsize * LDT_ENTRY_SIZE, 0, | 
					
						
							|  |  |  | 	       (mincount - oldsize) * LDT_ENTRY_SIZE); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:14 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_X86_64
 | 
					
						
							|  |  |  | 	/* CHECKME: Do we really need this ? */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	wmb(); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:14 +01:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	pc->ldt = newldt; | 
					
						
							|  |  |  | 	wmb(); | 
					
						
							|  |  |  | 	pc->size = mincount; | 
					
						
							|  |  |  | 	wmb(); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (reload) { | 
					
						
							|  |  |  | #ifdef CONFIG_SMP
 | 
					
						
							|  |  |  | 		cpumask_t mask; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		preempt_disable(); | 
					
						
							|  |  |  | 		load_LDT(pc); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 		mask = cpumask_of_cpu(smp_processor_id()); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (!cpus_equal(current->mm->cpu_vm_mask, mask)) | 
					
						
							|  |  |  | 			smp_call_function(flush_ldt, NULL, 1, 1); | 
					
						
							|  |  |  | 		preempt_enable(); | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | 		load_LDT(pc); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if (oldsize) { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 		if (oldsize * LDT_ENTRY_SIZE > PAGE_SIZE) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			vfree(oldldt); | 
					
						
							|  |  |  | 		else | 
					
						
							| 
									
										
										
										
											2008-01-30 13:33:14 +01:00
										 |  |  | 			put_page(virt_to_page(oldldt)); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline int copy_ldt(mm_context_t *new, mm_context_t *old) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int err = alloc_ldt(new, old->size, 0); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (err < 0) | 
					
						
							|  |  |  | 		return err; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	memcpy(new->ldt, old->ldt, old->size * LDT_ENTRY_SIZE); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * we do not have to muck with descriptors here, that is | 
					
						
							|  |  |  |  * done in switch_mm() as needed. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | int init_new_context(struct task_struct *tsk, struct mm_struct *mm) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	struct mm_struct *old_mm; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int retval = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-17 18:04:41 +02:00
										 |  |  | 	mutex_init(&mm->context.lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	mm->context.size = 0; | 
					
						
							|  |  |  | 	old_mm = current->mm; | 
					
						
							|  |  |  | 	if (old_mm && old_mm->context.size > 0) { | 
					
						
							| 
									
										
										
										
											2007-10-17 18:04:41 +02:00
										 |  |  | 		mutex_lock(&old_mm->context.lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		retval = copy_ldt(&mm->context, &old_mm->context); | 
					
						
							| 
									
										
										
										
											2007-10-17 18:04:41 +02:00
										 |  |  | 		mutex_unlock(&old_mm->context.lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return retval; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:14 +01:00
										 |  |  |  * No need to lock the MM as we are the last user | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * 64bit: Don't touch the LDT register - we're already in the next thread. | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |  */ | 
					
						
							|  |  |  | void destroy_context(struct mm_struct *mm) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (mm->context.size) { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:14 +01:00
										 |  |  | #ifdef CONFIG_X86_32
 | 
					
						
							|  |  |  | 		/* CHECKME: Can this ever happen ? */ | 
					
						
							|  |  |  | 		if (mm == current->active_mm) | 
					
						
							|  |  |  | 			clear_LDT(); | 
					
						
							|  |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 		if (mm->context.size * LDT_ENTRY_SIZE > PAGE_SIZE) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			vfree(mm->context.ldt); | 
					
						
							|  |  |  | 		else | 
					
						
							| 
									
										
										
										
											2008-01-30 13:33:14 +01:00
										 |  |  | 			put_page(virt_to_page(mm->context.ldt)); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		mm->context.size = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | static int read_ldt(void __user *ptr, unsigned long bytecount) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	int err; | 
					
						
							|  |  |  | 	unsigned long size; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	struct mm_struct *mm = current->mm; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (!mm->context.size) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	if (bytecount > LDT_ENTRY_SIZE * LDT_ENTRIES) | 
					
						
							|  |  |  | 		bytecount = LDT_ENTRY_SIZE * LDT_ENTRIES; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-17 18:04:41 +02:00
										 |  |  | 	mutex_lock(&mm->context.lock); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	size = mm->context.size * LDT_ENTRY_SIZE; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (size > bytecount) | 
					
						
							|  |  |  | 		size = bytecount; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = 0; | 
					
						
							|  |  |  | 	if (copy_to_user(ptr, mm->context.ldt, size)) | 
					
						
							|  |  |  | 		err = -EFAULT; | 
					
						
							| 
									
										
										
										
											2007-10-17 18:04:41 +02:00
										 |  |  | 	mutex_unlock(&mm->context.lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (err < 0) | 
					
						
							|  |  |  | 		goto error_return; | 
					
						
							|  |  |  | 	if (size != bytecount) { | 
					
						
							|  |  |  | 		/* zero-fill the rest */ | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 		if (clear_user(ptr + size, bytecount - size) != 0) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			err = -EFAULT; | 
					
						
							|  |  |  | 			goto error_return; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return bytecount; | 
					
						
							|  |  |  | error_return: | 
					
						
							|  |  |  | 	return err; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | static int read_default_ldt(void __user *ptr, unsigned long bytecount) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:14 +01:00
										 |  |  | 	/* CHECKME: Can we use _one_ random number ? */ | 
					
						
							|  |  |  | #ifdef CONFIG_X86_32
 | 
					
						
							|  |  |  | 	unsigned long size = 5 * sizeof(struct desc_struct); | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | 	unsigned long size = 128; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 	if (bytecount > size) | 
					
						
							|  |  |  | 		bytecount = size; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (clear_user(ptr, bytecount)) | 
					
						
							|  |  |  | 		return -EFAULT; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	return bytecount; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	struct mm_struct *mm = current->mm; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:31:13 +01:00
										 |  |  | 	struct desc_struct ldt; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int error; | 
					
						
							|  |  |  | 	struct user_desc ldt_info; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	error = -EINVAL; | 
					
						
							|  |  |  | 	if (bytecount != sizeof(ldt_info)) | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	error = -EFAULT; | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	if (copy_from_user(&ldt_info, ptr, sizeof(ldt_info))) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		goto out; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	error = -EINVAL; | 
					
						
							|  |  |  | 	if (ldt_info.entry_number >= LDT_ENTRIES) | 
					
						
							|  |  |  | 		goto out; | 
					
						
							|  |  |  | 	if (ldt_info.contents == 3) { | 
					
						
							|  |  |  | 		if (oldmode) | 
					
						
							|  |  |  | 			goto out; | 
					
						
							|  |  |  | 		if (ldt_info.seg_not_present == 0) | 
					
						
							|  |  |  | 			goto out; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-17 18:04:41 +02:00
										 |  |  | 	mutex_lock(&mm->context.lock); | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	if (ldt_info.entry_number >= mm->context.size) { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 		error = alloc_ldt(¤t->mm->context, | 
					
						
							|  |  |  | 				  ldt_info.entry_number + 1, 1); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (error < 0) | 
					
						
							|  |  |  | 			goto out_unlock; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | 	/* Allow LDTs to be cleared by the user. */ | 
					
						
							|  |  |  | 	if (ldt_info.base_addr == 0 && ldt_info.limit == 0) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (oldmode || LDT_empty(&ldt_info)) { | 
					
						
							| 
									
										
										
										
											2008-01-30 13:31:13 +01:00
										 |  |  | 			memset(&ldt, 0, sizeof(ldt)); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			goto install; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:31:13 +01:00
										 |  |  | 	fill_ldt(&ldt, &ldt_info); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (oldmode) | 
					
						
							| 
									
										
										
										
											2008-01-30 13:31:13 +01:00
										 |  |  | 		ldt.avl = 0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* Install the new entry ...  */ | 
					
						
							|  |  |  | install: | 
					
						
							| 
									
										
										
										
											2008-01-30 13:31:13 +01:00
										 |  |  | 	write_ldt_entry(mm->context.ldt, ldt_info.entry_number, &ldt); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	error = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | out_unlock: | 
					
						
							| 
									
										
										
										
											2007-10-17 18:04:41 +02:00
										 |  |  | 	mutex_unlock(&mm->context.lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | out: | 
					
						
							|  |  |  | 	return error; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 13:30:13 +01:00
										 |  |  | asmlinkage int sys_modify_ldt(int func, void __user *ptr, | 
					
						
							|  |  |  | 			      unsigned long bytecount) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	int ret = -ENOSYS; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch (func) { | 
					
						
							|  |  |  | 	case 0: | 
					
						
							|  |  |  | 		ret = read_ldt(ptr, bytecount); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 1: | 
					
						
							|  |  |  | 		ret = write_ldt(ptr, bytecount, 1); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 2: | 
					
						
							|  |  |  | 		ret = read_default_ldt(ptr, bytecount); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	case 0x11: | 
					
						
							|  |  |  | 		ret = write_ldt(ptr, bytecount, 0); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } |