1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
/*
* linux/kernel/ldt.c
*
* Copyright (C) 1992 Krishna Balasubramanian and Linus Torvalds
*/
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/ldt.h>
#include <asm/ptrace.h>
static int read_ldt(void * ptr, unsigned long bytecount)
{
int error;
void * address = current->ldt;
unsigned long size;
if (!ptr)
return -EINVAL;
size = LDT_ENTRIES*LDT_ENTRY_SIZE;
if (!address) {
address = &default_ldt;
size = sizeof(default_ldt);
}
if (size > bytecount)
size = bytecount;
error = verify_area(VERIFY_WRITE, ptr, size);
if (error)
return error;
memcpy_tofs(ptr, address, size);
return size;
}
static inline int limits_ok(struct modify_ldt_ldt_s *ldt_info)
{
unsigned long base, limit;
/* linear address of first and last accessible byte */
unsigned long first, last;
base = ldt_info->base_addr;
limit = ldt_info->limit;
if (ldt_info->limit_in_pages)
limit = limit * PAGE_SIZE + PAGE_SIZE - 1;
first = base;
last = limit + base;
/* segment grows down? */
if (ldt_info->contents == 1) {
/* data segment grows down */
first = base+limit+1;
last = base+65535;
if (ldt_info->seg_32bit)
last = base-1;
}
return (last >= first && last < TASK_SIZE);
}
static int write_ldt(struct pt_regs * regs, void * ptr, unsigned long bytecount, int oldmode)
{
struct modify_ldt_ldt_s ldt_info;
unsigned long *lp;
int error, i;
if (bytecount != sizeof(ldt_info))
return -EINVAL;
error = verify_area(VERIFY_READ, ptr, sizeof(ldt_info));
if (error)
return error;
memcpy_fromfs(&ldt_info, ptr, sizeof(ldt_info));
if ((ldt_info.contents == 3 && (oldmode || ldt_info.seg_not_present == 0)) || ldt_info.entry_number >= LDT_ENTRIES)
return -EINVAL;
if (!limits_ok(&ldt_info) && (oldmode || ldt_info.seg_not_present == 0))
return -EINVAL;
if (!current->ldt) {
for (i=1 ; i<NR_TASKS ; i++) {
if (task[i] == current) {
if (!(current->ldt = (struct desc_struct*) vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE)))
return -ENOMEM;
memset(current->ldt, 0, LDT_ENTRIES*LDT_ENTRY_SIZE);
set_ldt_desc(gdt+(i<<1)+FIRST_LDT_ENTRY, current->ldt, LDT_ENTRIES);
load_ldt(i);
}
}
}
lp = (unsigned long *) ¤t->ldt[ldt_info.entry_number];
/* Allow LDTs to be cleared by the user. */
if (ldt_info.base_addr == 0 && ldt_info.limit == 0
&& (oldmode ||
( ldt_info.contents == 0
&& ldt_info.read_exec_only == 1
&& ldt_info.seg_32bit == 0
&& ldt_info.limit_in_pages == 0
&& ldt_info.seg_not_present == 1
&& ldt_info.useable == 0 )) ) {
unsigned short sel =(ldt_info.entry_number <<3) | 7;
if (regs->fs == sel || regs->gs == sel)
return -EBUSY;
*lp = 0;
*(lp+1) = 0;
return 0;
}
*lp = ((ldt_info.base_addr & 0x0000ffff) << 16) |
(ldt_info.limit & 0x0ffff);
*(lp+1) = (ldt_info.base_addr & 0xff000000) |
((ldt_info.base_addr & 0x00ff0000)>>16) |
(ldt_info.limit & 0xf0000) |
(ldt_info.contents << 10) |
((ldt_info.read_exec_only ^ 1) << 9) |
(ldt_info.seg_32bit << 22) |
(ldt_info.limit_in_pages << 23) |
((ldt_info.seg_not_present ^1) << 15) |
0x7000;
if (!oldmode) *(lp+1) |= (ldt_info.useable << 20);
return 0;
}
asmlinkage int sys_modify_ldt(int func, void *ptr, unsigned long bytecount)
{
if (func == 0)
return read_ldt(ptr, bytecount);
if (func == 1)
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 1);
if (func == 0x11)
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 0);
return -ENOSYS;
}
|