/*
|
/*
|
* linux/kernel/ldt.c
|
* linux/kernel/ldt.c
|
*
|
*
|
* Copyright (C) 1992 Krishna Balasubramanian and Linus Torvalds
|
* Copyright (C) 1992 Krishna Balasubramanian and Linus Torvalds
|
*/
|
*/
|
|
|
#include <linux/errno.h>
|
#include <linux/errno.h>
|
#include <linux/sched.h>
|
#include <linux/sched.h>
|
#include <linux/string.h>
|
#include <linux/string.h>
|
#include <linux/mm.h>
|
#include <linux/mm.h>
|
#include <asm/segment.h>
|
#include <asm/segment.h>
|
#include <asm/system.h>
|
#include <asm/system.h>
|
#include <linux/ldt.h>
|
#include <linux/ldt.h>
|
#include <asm/ptrace.h>
|
#include <asm/ptrace.h>
|
|
|
static int read_ldt(void * ptr, unsigned long bytecount)
|
static int read_ldt(void * ptr, unsigned long bytecount)
|
{
|
{
|
int error;
|
int error;
|
void * address = current->ldt;
|
void * address = current->ldt;
|
unsigned long size;
|
unsigned long size;
|
|
|
if (!ptr)
|
if (!ptr)
|
return -EINVAL;
|
return -EINVAL;
|
size = LDT_ENTRIES*LDT_ENTRY_SIZE;
|
size = LDT_ENTRIES*LDT_ENTRY_SIZE;
|
if (!address) {
|
if (!address) {
|
address = &default_ldt;
|
address = &default_ldt;
|
size = sizeof(default_ldt);
|
size = sizeof(default_ldt);
|
}
|
}
|
if (size > bytecount)
|
if (size > bytecount)
|
size = bytecount;
|
size = bytecount;
|
error = verify_area(VERIFY_WRITE, ptr, size);
|
error = verify_area(VERIFY_WRITE, ptr, size);
|
if (error)
|
if (error)
|
return error;
|
return error;
|
memcpy_tofs(ptr, address, size);
|
memcpy_tofs(ptr, address, size);
|
return size;
|
return size;
|
}
|
}
|
|
|
static inline int limits_ok(struct modify_ldt_ldt_s *ldt_info)
|
static inline int limits_ok(struct modify_ldt_ldt_s *ldt_info)
|
{
|
{
|
unsigned long base, limit;
|
unsigned long base, limit;
|
/* linear address of first and last accessible byte */
|
/* linear address of first and last accessible byte */
|
unsigned long first, last;
|
unsigned long first, last;
|
|
|
base = ldt_info->base_addr;
|
base = ldt_info->base_addr;
|
limit = ldt_info->limit;
|
limit = ldt_info->limit;
|
if (ldt_info->limit_in_pages)
|
if (ldt_info->limit_in_pages)
|
limit = limit * PAGE_SIZE + PAGE_SIZE - 1;
|
limit = limit * PAGE_SIZE + PAGE_SIZE - 1;
|
|
|
first = base;
|
first = base;
|
last = limit + base;
|
last = limit + base;
|
|
|
/* segment grows down? */
|
/* segment grows down? */
|
if (ldt_info->contents == 1) {
|
if (ldt_info->contents == 1) {
|
/* data segment grows down */
|
/* data segment grows down */
|
first = base+limit+1;
|
first = base+limit+1;
|
last = base+65535;
|
last = base+65535;
|
if (ldt_info->seg_32bit)
|
if (ldt_info->seg_32bit)
|
last = base-1;
|
last = base-1;
|
}
|
}
|
return (last >= first && last < TASK_SIZE);
|
return (last >= first && last < TASK_SIZE);
|
}
|
}
|
|
|
static int write_ldt(struct pt_regs * regs, void * ptr, unsigned long bytecount, int oldmode)
|
static int write_ldt(struct pt_regs * regs, void * ptr, unsigned long bytecount, int oldmode)
|
{
|
{
|
struct modify_ldt_ldt_s ldt_info;
|
struct modify_ldt_ldt_s ldt_info;
|
unsigned long *lp;
|
unsigned long *lp;
|
int error, i;
|
int error, i;
|
|
|
if (bytecount != sizeof(ldt_info))
|
if (bytecount != sizeof(ldt_info))
|
return -EINVAL;
|
return -EINVAL;
|
error = verify_area(VERIFY_READ, ptr, sizeof(ldt_info));
|
error = verify_area(VERIFY_READ, ptr, sizeof(ldt_info));
|
if (error)
|
if (error)
|
return error;
|
return error;
|
|
|
memcpy_fromfs(&ldt_info, ptr, sizeof(ldt_info));
|
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)
|
if ((ldt_info.contents == 3 && (oldmode || ldt_info.seg_not_present == 0)) || ldt_info.entry_number >= LDT_ENTRIES)
|
return -EINVAL;
|
return -EINVAL;
|
|
|
if (!limits_ok(&ldt_info) && (oldmode || ldt_info.seg_not_present == 0))
|
if (!limits_ok(&ldt_info) && (oldmode || ldt_info.seg_not_present == 0))
|
return -EINVAL;
|
return -EINVAL;
|
|
|
if (!current->ldt) {
|
if (!current->ldt) {
|
for (i=1 ; i<NR_TASKS ; i++) {
|
for (i=1 ; i<NR_TASKS ; i++) {
|
if (task[i] == current) {
|
if (task[i] == current) {
|
if (!(current->ldt = (struct desc_struct*) vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE)))
|
if (!(current->ldt = (struct desc_struct*) vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE)))
|
return -ENOMEM;
|
return -ENOMEM;
|
memset(current->ldt, 0, LDT_ENTRIES*LDT_ENTRY_SIZE);
|
memset(current->ldt, 0, LDT_ENTRIES*LDT_ENTRY_SIZE);
|
set_ldt_desc(gdt+(i<<1)+FIRST_LDT_ENTRY, current->ldt, LDT_ENTRIES);
|
set_ldt_desc(gdt+(i<<1)+FIRST_LDT_ENTRY, current->ldt, LDT_ENTRIES);
|
load_ldt(i);
|
load_ldt(i);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
lp = (unsigned long *) ¤t->ldt[ldt_info.entry_number];
|
lp = (unsigned long *) ¤t->ldt[ldt_info.entry_number];
|
/* Allow LDTs to be cleared by the user. */
|
/* Allow LDTs to be cleared by the user. */
|
if (ldt_info.base_addr == 0 && ldt_info.limit == 0
|
if (ldt_info.base_addr == 0 && ldt_info.limit == 0
|
&& (oldmode ||
|
&& (oldmode ||
|
( ldt_info.contents == 0
|
( ldt_info.contents == 0
|
&& ldt_info.read_exec_only == 1
|
&& ldt_info.read_exec_only == 1
|
&& ldt_info.seg_32bit == 0
|
&& ldt_info.seg_32bit == 0
|
&& ldt_info.limit_in_pages == 0
|
&& ldt_info.limit_in_pages == 0
|
&& ldt_info.seg_not_present == 1
|
&& ldt_info.seg_not_present == 1
|
&& ldt_info.useable == 0 )) ) {
|
&& ldt_info.useable == 0 )) ) {
|
unsigned short sel =(ldt_info.entry_number <<3) | 7;
|
unsigned short sel =(ldt_info.entry_number <<3) | 7;
|
if (regs->fs == sel || regs->gs == sel)
|
if (regs->fs == sel || regs->gs == sel)
|
return -EBUSY;
|
return -EBUSY;
|
*lp = 0;
|
*lp = 0;
|
*(lp+1) = 0;
|
*(lp+1) = 0;
|
return 0;
|
return 0;
|
}
|
}
|
*lp = ((ldt_info.base_addr & 0x0000ffff) << 16) |
|
*lp = ((ldt_info.base_addr & 0x0000ffff) << 16) |
|
(ldt_info.limit & 0x0ffff);
|
(ldt_info.limit & 0x0ffff);
|
*(lp+1) = (ldt_info.base_addr & 0xff000000) |
|
*(lp+1) = (ldt_info.base_addr & 0xff000000) |
|
((ldt_info.base_addr & 0x00ff0000)>>16) |
|
((ldt_info.base_addr & 0x00ff0000)>>16) |
|
(ldt_info.limit & 0xf0000) |
|
(ldt_info.limit & 0xf0000) |
|
(ldt_info.contents << 10) |
|
(ldt_info.contents << 10) |
|
((ldt_info.read_exec_only ^ 1) << 9) |
|
((ldt_info.read_exec_only ^ 1) << 9) |
|
(ldt_info.seg_32bit << 22) |
|
(ldt_info.seg_32bit << 22) |
|
(ldt_info.limit_in_pages << 23) |
|
(ldt_info.limit_in_pages << 23) |
|
((ldt_info.seg_not_present ^1) << 15) |
|
((ldt_info.seg_not_present ^1) << 15) |
|
0x7000;
|
0x7000;
|
if (!oldmode) *(lp+1) |= (ldt_info.useable << 20);
|
if (!oldmode) *(lp+1) |= (ldt_info.useable << 20);
|
return 0;
|
return 0;
|
}
|
}
|
|
|
asmlinkage int sys_modify_ldt(int func, void *ptr, unsigned long bytecount)
|
asmlinkage int sys_modify_ldt(int func, void *ptr, unsigned long bytecount)
|
{
|
{
|
if (func == 0)
|
if (func == 0)
|
return read_ldt(ptr, bytecount);
|
return read_ldt(ptr, bytecount);
|
if (func == 1)
|
if (func == 1)
|
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 1);
|
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 1);
|
if (func == 0x11)
|
if (func == 0x11)
|
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 0);
|
return write_ldt((struct pt_regs *) &func, ptr, bytecount, 0);
|
return -ENOSYS;
|
return -ENOSYS;
|
}
|
}
|
|
|