mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-31 16:38:12 +00:00
c7a9b6471c
kernel test robot <oliver.sang@intel.com> writes[1]:
>
> Greeting,
>
> FYI, we noticed the following commit (built with gcc-9):
>
> commit: 1a4d21a23c
("signal/vm86_32: Replace open coded BUG_ON with an actual BUG_ON")
> https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git master
>
> in testcase: trinity
> version: trinity-static-i386-x86_64-1c734c75-1_2020-01-06
> with following parameters:
>
>
> [ 70.645554][ T3747] kernel BUG at arch/x86/kernel/vm86_32.c:109!
> [ 70.646185][ T3747] invalid opcode: 0000 [#1] SMP
> [ 70.646682][ T3747] CPU: 0 PID: 3747 Comm: trinity-c6 Not tainted 5.15.0-rc1-00009-g1a4d21a23c4c #1
> [ 70.647598][ T3747] EIP: save_v86_state (arch/x86/kernel/vm86_32.c:109 (discriminator 3))
> [ 70.648113][ T3747] Code: 89 c3 64 8b 35 60 b8 25 c2 83 ec 08 89 55 f0 8b 96 10 19 00 00 89 55 ec e8 c6 2d 0c 00 fb 8b 55 ec 85 d2 74 05 83 3a 00 75 02 <0f> 0b 8b 86 10 19 00 00 8b 4b 38 8b 78 48 31 cf 89 f8 8b 7a 4c 81
> [ 70.650136][ T3747] EAX: 00000001 EBX: f5f49fac ECX: 0000000b EDX: f610b600
> [ 70.650852][ T3747] ESI: f5f79cc0 EDI: f5f79cc0 EBP: f5f49f04 ESP: f5f49ef0
> [ 70.651593][ T3747] DS: 007b ES: 007b FS: 00d8 GS: 0000 SS: 0068 EFLAGS: 00010246
> [ 70.652413][ T3747] CR0: 80050033 CR2: 00004000 CR3: 35fc7000 CR4: 000406d0
> [ 70.653169][ T3747] DR0: 00000000 DR1: 00000000 DR2: 00000000 DR3: 00000000
> [ 70.653897][ T3747] DR6: fffe0ff0 DR7: 00000400
> [ 70.654382][ T3747] Call Trace:
> [ 70.654719][ T3747] arch_do_signal_or_restart (arch/x86/kernel/signal.c:792 arch/x86/kernel/signal.c:867)
> [ 70.655288][ T3747] exit_to_user_mode_prepare (kernel/entry/common.c:174 kernel/entry/common.c:209)
> [ 70.655854][ T3747] irqentry_exit_to_user_mode (kernel/entry/common.c:126 kernel/entry/common.c:317)
> [ 70.656450][ T3747] irqentry_exit (kernel/entry/common.c:406)
> [ 70.656897][ T3747] exc_page_fault (arch/x86/mm/fault.c:1535)
> [ 70.657369][ T3747] ? sysvec_kvm_asyncpf_interrupt (arch/x86/mm/fault.c:1488)
> [ 70.657989][ T3747] handle_exception (arch/x86/entry/entry_32.S:1085)
vm86_32.c:109 is: "BUG_ON(!vm86 || !vm86->user_vm86)"
When trying to understand the failure Brian Gerst pointed out[2] that
the code does not need protection against vm86->user_vm86 being NULL.
The copy_from_user code will already handles that case if the address
is going to fault.
Looking futher I realized that if we care about not allowing struct
vm86plus_struct at address 0 it should be do_sys_vm86 (the system
call) that does the filtering. Not way down deep when the emulation
has completed in save_v86_state.
So let's just remove the silly case of attempting to filter a
userspace address with a BUG_ON. Existing userspace can't break and
it won't make the kernel any more attackable as the userspace access
helpers will handle it, if it isn't a good userspace pointer.
I have run the reproducer the fuzzer gave me before I made this change
and it reproduced, and after I made this change and I have not seen
the reported failure. So it does looks like this fixes the reported
issue.
[1] https://lkml.kernel.org/r/20211112074030.GB19820@xsang-OptiPlex-9020
[2] https://lkml.kernel.org/r/CAMzpN2jkK5sAv-Kg_kVnCEyVySiqeTdUORcC=AdG1gV6r8nUew@mail.gmail.com
Suggested-by: Brian Gerst <brgerst@gmail.com>
Reported-by: kernel test robot <oliver.sang@intel.com>
Tested-by: "Eric W. Biederman" <ebiederm@xmission.com>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
832 lines
22 KiB
C
832 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 1994 Linus Torvalds
|
|
*
|
|
* 29 dec 2001 - Fixed oopses caused by unchecked access to the vm86
|
|
* stack - Manfred Spraul <manfred@colorfullife.com>
|
|
*
|
|
* 22 mar 2002 - Manfred detected the stackfaults, but didn't handle
|
|
* them correctly. Now the emulation will be in a
|
|
* consistent state after stackfaults - Kasper Dupont
|
|
* <kasperd@daimi.au.dk>
|
|
*
|
|
* 22 mar 2002 - Added missing clear_IF in set_vflags_* Kasper Dupont
|
|
* <kasperd@daimi.au.dk>
|
|
*
|
|
* ?? ??? 2002 - Fixed premature returns from handle_vm86_fault
|
|
* caused by Kasper Dupont's changes - Stas Sergeev
|
|
*
|
|
* 4 apr 2002 - Fixed CHECK_IF_IN_TRAP broken by Stas' changes.
|
|
* Kasper Dupont <kasperd@daimi.au.dk>
|
|
*
|
|
* 9 apr 2002 - Changed syntax of macros in handle_vm86_fault.
|
|
* Kasper Dupont <kasperd@daimi.au.dk>
|
|
*
|
|
* 9 apr 2002 - Changed stack access macros to jump to a label
|
|
* instead of returning to userspace. This simplifies
|
|
* do_int, and is needed by handle_vm6_fault. Kasper
|
|
* Dupont <kasperd@daimi.au.dk>
|
|
*
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/capability.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/sched/task_stack.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/signal.h>
|
|
#include <linux/string.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/highmem.h>
|
|
#include <linux/ptrace.h>
|
|
#include <linux/audit.h>
|
|
#include <linux/stddef.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/security.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
#include <asm/io.h>
|
|
#include <asm/tlbflush.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/traps.h>
|
|
#include <asm/vm86.h>
|
|
#include <asm/switch_to.h>
|
|
|
|
/*
|
|
* Known problems:
|
|
*
|
|
* Interrupt handling is not guaranteed:
|
|
* - a real x86 will disable all interrupts for one instruction
|
|
* after a "mov ss,xx" to make stack handling atomic even without
|
|
* the 'lss' instruction. We can't guarantee this in v86 mode,
|
|
* as the next instruction might result in a page fault or similar.
|
|
* - a real x86 will have interrupts disabled for one instruction
|
|
* past the 'sti' that enables them. We don't bother with all the
|
|
* details yet.
|
|
*
|
|
* Let's hope these problems do not actually matter for anything.
|
|
*/
|
|
|
|
|
|
/*
|
|
* 8- and 16-bit register defines..
|
|
*/
|
|
#define AL(regs) (((unsigned char *)&((regs)->pt.ax))[0])
|
|
#define AH(regs) (((unsigned char *)&((regs)->pt.ax))[1])
|
|
#define IP(regs) (*(unsigned short *)&((regs)->pt.ip))
|
|
#define SP(regs) (*(unsigned short *)&((regs)->pt.sp))
|
|
|
|
/*
|
|
* virtual flags (16 and 32-bit versions)
|
|
*/
|
|
#define VFLAGS (*(unsigned short *)&(current->thread.vm86->veflags))
|
|
#define VEFLAGS (current->thread.vm86->veflags)
|
|
|
|
#define set_flags(X, new, mask) \
|
|
((X) = ((X) & ~(mask)) | ((new) & (mask)))
|
|
|
|
#define SAFE_MASK (0xDD5)
|
|
#define RETURN_MASK (0xDFF)
|
|
|
|
void save_v86_state(struct kernel_vm86_regs *regs, int retval)
|
|
{
|
|
struct task_struct *tsk = current;
|
|
struct vm86plus_struct __user *user;
|
|
struct vm86 *vm86 = current->thread.vm86;
|
|
|
|
/*
|
|
* This gets called from entry.S with interrupts disabled, but
|
|
* from process context. Enable interrupts here, before trying
|
|
* to access user space.
|
|
*/
|
|
local_irq_enable();
|
|
|
|
BUG_ON(!vm86);
|
|
|
|
set_flags(regs->pt.flags, VEFLAGS, X86_EFLAGS_VIF | vm86->veflags_mask);
|
|
user = vm86->user_vm86;
|
|
|
|
if (!user_access_begin(user, vm86->vm86plus.is_vm86pus ?
|
|
sizeof(struct vm86plus_struct) :
|
|
sizeof(struct vm86_struct)))
|
|
goto Efault;
|
|
|
|
unsafe_put_user(regs->pt.bx, &user->regs.ebx, Efault_end);
|
|
unsafe_put_user(regs->pt.cx, &user->regs.ecx, Efault_end);
|
|
unsafe_put_user(regs->pt.dx, &user->regs.edx, Efault_end);
|
|
unsafe_put_user(regs->pt.si, &user->regs.esi, Efault_end);
|
|
unsafe_put_user(regs->pt.di, &user->regs.edi, Efault_end);
|
|
unsafe_put_user(regs->pt.bp, &user->regs.ebp, Efault_end);
|
|
unsafe_put_user(regs->pt.ax, &user->regs.eax, Efault_end);
|
|
unsafe_put_user(regs->pt.ip, &user->regs.eip, Efault_end);
|
|
unsafe_put_user(regs->pt.cs, &user->regs.cs, Efault_end);
|
|
unsafe_put_user(regs->pt.flags, &user->regs.eflags, Efault_end);
|
|
unsafe_put_user(regs->pt.sp, &user->regs.esp, Efault_end);
|
|
unsafe_put_user(regs->pt.ss, &user->regs.ss, Efault_end);
|
|
unsafe_put_user(regs->es, &user->regs.es, Efault_end);
|
|
unsafe_put_user(regs->ds, &user->regs.ds, Efault_end);
|
|
unsafe_put_user(regs->fs, &user->regs.fs, Efault_end);
|
|
unsafe_put_user(regs->gs, &user->regs.gs, Efault_end);
|
|
|
|
/*
|
|
* Don't write screen_bitmap in case some user had a value there
|
|
* and expected it to remain unchanged.
|
|
*/
|
|
|
|
user_access_end();
|
|
|
|
exit_vm86:
|
|
preempt_disable();
|
|
tsk->thread.sp0 = vm86->saved_sp0;
|
|
tsk->thread.sysenter_cs = __KERNEL_CS;
|
|
update_task_stack(tsk);
|
|
refresh_sysenter_cs(&tsk->thread);
|
|
vm86->saved_sp0 = 0;
|
|
preempt_enable();
|
|
|
|
memcpy(®s->pt, &vm86->regs32, sizeof(struct pt_regs));
|
|
|
|
lazy_load_gs(vm86->regs32.gs);
|
|
|
|
regs->pt.ax = retval;
|
|
return;
|
|
|
|
Efault_end:
|
|
user_access_end();
|
|
Efault:
|
|
pr_alert("could not access userspace vm86 info\n");
|
|
force_fatal_sig(SIGSEGV);
|
|
goto exit_vm86;
|
|
}
|
|
|
|
static int do_vm86_irq_handling(int subfunction, int irqnumber);
|
|
static long do_sys_vm86(struct vm86plus_struct __user *user_vm86, bool plus);
|
|
|
|
SYSCALL_DEFINE1(vm86old, struct vm86_struct __user *, user_vm86)
|
|
{
|
|
return do_sys_vm86((struct vm86plus_struct __user *) user_vm86, false);
|
|
}
|
|
|
|
|
|
SYSCALL_DEFINE2(vm86, unsigned long, cmd, unsigned long, arg)
|
|
{
|
|
switch (cmd) {
|
|
case VM86_REQUEST_IRQ:
|
|
case VM86_FREE_IRQ:
|
|
case VM86_GET_IRQ_BITS:
|
|
case VM86_GET_AND_RESET_IRQ:
|
|
return do_vm86_irq_handling(cmd, (int)arg);
|
|
case VM86_PLUS_INSTALL_CHECK:
|
|
/*
|
|
* NOTE: on old vm86 stuff this will return the error
|
|
* from access_ok(), because the subfunction is
|
|
* interpreted as (invalid) address to vm86_struct.
|
|
* So the installation check works.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
/* we come here only for functions VM86_ENTER, VM86_ENTER_NO_BYPASS */
|
|
return do_sys_vm86((struct vm86plus_struct __user *) arg, true);
|
|
}
|
|
|
|
|
|
static long do_sys_vm86(struct vm86plus_struct __user *user_vm86, bool plus)
|
|
{
|
|
struct task_struct *tsk = current;
|
|
struct vm86 *vm86 = tsk->thread.vm86;
|
|
struct kernel_vm86_regs vm86regs;
|
|
struct pt_regs *regs = current_pt_regs();
|
|
unsigned long err = 0;
|
|
struct vm86_struct v;
|
|
|
|
err = security_mmap_addr(0);
|
|
if (err) {
|
|
/*
|
|
* vm86 cannot virtualize the address space, so vm86 users
|
|
* need to manage the low 1MB themselves using mmap. Given
|
|
* that BIOS places important data in the first page, vm86
|
|
* is essentially useless if mmap_min_addr != 0. DOSEMU,
|
|
* for example, won't even bother trying to use vm86 if it
|
|
* can't map a page at virtual address 0.
|
|
*
|
|
* To reduce the available kernel attack surface, simply
|
|
* disallow vm86(old) for users who cannot mmap at va 0.
|
|
*
|
|
* The implementation of security_mmap_addr will allow
|
|
* suitably privileged users to map va 0 even if
|
|
* vm.mmap_min_addr is set above 0, and we want this
|
|
* behavior for vm86 as well, as it ensures that legacy
|
|
* tools like vbetool will not fail just because of
|
|
* vm.mmap_min_addr.
|
|
*/
|
|
pr_info_once("Denied a call to vm86(old) from %s[%d] (uid: %d). Set the vm.mmap_min_addr sysctl to 0 and/or adjust LSM mmap_min_addr policy to enable vm86 if you are using a vm86-based DOS emulator.\n",
|
|
current->comm, task_pid_nr(current),
|
|
from_kuid_munged(&init_user_ns, current_uid()));
|
|
return -EPERM;
|
|
}
|
|
|
|
if (!vm86) {
|
|
if (!(vm86 = kzalloc(sizeof(*vm86), GFP_KERNEL)))
|
|
return -ENOMEM;
|
|
tsk->thread.vm86 = vm86;
|
|
}
|
|
if (vm86->saved_sp0)
|
|
return -EPERM;
|
|
|
|
if (copy_from_user(&v, user_vm86,
|
|
offsetof(struct vm86_struct, int_revectored)))
|
|
return -EFAULT;
|
|
|
|
|
|
/* VM86_SCREEN_BITMAP had numerous bugs and appears to have no users. */
|
|
if (v.flags & VM86_SCREEN_BITMAP) {
|
|
char comm[TASK_COMM_LEN];
|
|
|
|
pr_info_once("vm86: '%s' uses VM86_SCREEN_BITMAP, which is no longer supported\n", get_task_comm(comm, current));
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&vm86regs, 0, sizeof(vm86regs));
|
|
|
|
vm86regs.pt.bx = v.regs.ebx;
|
|
vm86regs.pt.cx = v.regs.ecx;
|
|
vm86regs.pt.dx = v.regs.edx;
|
|
vm86regs.pt.si = v.regs.esi;
|
|
vm86regs.pt.di = v.regs.edi;
|
|
vm86regs.pt.bp = v.regs.ebp;
|
|
vm86regs.pt.ax = v.regs.eax;
|
|
vm86regs.pt.ip = v.regs.eip;
|
|
vm86regs.pt.cs = v.regs.cs;
|
|
vm86regs.pt.flags = v.regs.eflags;
|
|
vm86regs.pt.sp = v.regs.esp;
|
|
vm86regs.pt.ss = v.regs.ss;
|
|
vm86regs.es = v.regs.es;
|
|
vm86regs.ds = v.regs.ds;
|
|
vm86regs.fs = v.regs.fs;
|
|
vm86regs.gs = v.regs.gs;
|
|
|
|
vm86->flags = v.flags;
|
|
vm86->cpu_type = v.cpu_type;
|
|
|
|
if (copy_from_user(&vm86->int_revectored,
|
|
&user_vm86->int_revectored,
|
|
sizeof(struct revectored_struct)))
|
|
return -EFAULT;
|
|
if (copy_from_user(&vm86->int21_revectored,
|
|
&user_vm86->int21_revectored,
|
|
sizeof(struct revectored_struct)))
|
|
return -EFAULT;
|
|
if (plus) {
|
|
if (copy_from_user(&vm86->vm86plus, &user_vm86->vm86plus,
|
|
sizeof(struct vm86plus_info_struct)))
|
|
return -EFAULT;
|
|
vm86->vm86plus.is_vm86pus = 1;
|
|
} else
|
|
memset(&vm86->vm86plus, 0,
|
|
sizeof(struct vm86plus_info_struct));
|
|
|
|
memcpy(&vm86->regs32, regs, sizeof(struct pt_regs));
|
|
vm86->user_vm86 = user_vm86;
|
|
|
|
/*
|
|
* The flags register is also special: we cannot trust that the user
|
|
* has set it up safely, so this makes sure interrupt etc flags are
|
|
* inherited from protected mode.
|
|
*/
|
|
VEFLAGS = vm86regs.pt.flags;
|
|
vm86regs.pt.flags &= SAFE_MASK;
|
|
vm86regs.pt.flags |= regs->flags & ~SAFE_MASK;
|
|
vm86regs.pt.flags |= X86_VM_MASK;
|
|
|
|
vm86regs.pt.orig_ax = regs->orig_ax;
|
|
|
|
switch (vm86->cpu_type) {
|
|
case CPU_286:
|
|
vm86->veflags_mask = 0;
|
|
break;
|
|
case CPU_386:
|
|
vm86->veflags_mask = X86_EFLAGS_NT | X86_EFLAGS_IOPL;
|
|
break;
|
|
case CPU_486:
|
|
vm86->veflags_mask = X86_EFLAGS_AC | X86_EFLAGS_NT | X86_EFLAGS_IOPL;
|
|
break;
|
|
default:
|
|
vm86->veflags_mask = X86_EFLAGS_ID | X86_EFLAGS_AC | X86_EFLAGS_NT | X86_EFLAGS_IOPL;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Save old state
|
|
*/
|
|
vm86->saved_sp0 = tsk->thread.sp0;
|
|
lazy_save_gs(vm86->regs32.gs);
|
|
|
|
/* make room for real-mode segments */
|
|
preempt_disable();
|
|
tsk->thread.sp0 += 16;
|
|
|
|
if (boot_cpu_has(X86_FEATURE_SEP)) {
|
|
tsk->thread.sysenter_cs = 0;
|
|
refresh_sysenter_cs(&tsk->thread);
|
|
}
|
|
|
|
update_task_stack(tsk);
|
|
preempt_enable();
|
|
|
|
memcpy((struct kernel_vm86_regs *)regs, &vm86regs, sizeof(vm86regs));
|
|
return regs->ax;
|
|
}
|
|
|
|
static inline void set_IF(struct kernel_vm86_regs *regs)
|
|
{
|
|
VEFLAGS |= X86_EFLAGS_VIF;
|
|
}
|
|
|
|
static inline void clear_IF(struct kernel_vm86_regs *regs)
|
|
{
|
|
VEFLAGS &= ~X86_EFLAGS_VIF;
|
|
}
|
|
|
|
static inline void clear_TF(struct kernel_vm86_regs *regs)
|
|
{
|
|
regs->pt.flags &= ~X86_EFLAGS_TF;
|
|
}
|
|
|
|
static inline void clear_AC(struct kernel_vm86_regs *regs)
|
|
{
|
|
regs->pt.flags &= ~X86_EFLAGS_AC;
|
|
}
|
|
|
|
/*
|
|
* It is correct to call set_IF(regs) from the set_vflags_*
|
|
* functions. However someone forgot to call clear_IF(regs)
|
|
* in the opposite case.
|
|
* After the command sequence CLI PUSHF STI POPF you should
|
|
* end up with interrupts disabled, but you ended up with
|
|
* interrupts enabled.
|
|
* ( I was testing my own changes, but the only bug I
|
|
* could find was in a function I had not changed. )
|
|
* [KD]
|
|
*/
|
|
|
|
static inline void set_vflags_long(unsigned long flags, struct kernel_vm86_regs *regs)
|
|
{
|
|
set_flags(VEFLAGS, flags, current->thread.vm86->veflags_mask);
|
|
set_flags(regs->pt.flags, flags, SAFE_MASK);
|
|
if (flags & X86_EFLAGS_IF)
|
|
set_IF(regs);
|
|
else
|
|
clear_IF(regs);
|
|
}
|
|
|
|
static inline void set_vflags_short(unsigned short flags, struct kernel_vm86_regs *regs)
|
|
{
|
|
set_flags(VFLAGS, flags, current->thread.vm86->veflags_mask);
|
|
set_flags(regs->pt.flags, flags, SAFE_MASK);
|
|
if (flags & X86_EFLAGS_IF)
|
|
set_IF(regs);
|
|
else
|
|
clear_IF(regs);
|
|
}
|
|
|
|
static inline unsigned long get_vflags(struct kernel_vm86_regs *regs)
|
|
{
|
|
unsigned long flags = regs->pt.flags & RETURN_MASK;
|
|
|
|
if (VEFLAGS & X86_EFLAGS_VIF)
|
|
flags |= X86_EFLAGS_IF;
|
|
flags |= X86_EFLAGS_IOPL;
|
|
return flags | (VEFLAGS & current->thread.vm86->veflags_mask);
|
|
}
|
|
|
|
static inline int is_revectored(int nr, struct revectored_struct *bitmap)
|
|
{
|
|
return test_bit(nr, bitmap->__map);
|
|
}
|
|
|
|
#define val_byte(val, n) (((__u8 *)&val)[n])
|
|
|
|
#define pushb(base, ptr, val, err_label) \
|
|
do { \
|
|
__u8 __val = val; \
|
|
ptr--; \
|
|
if (put_user(__val, base + ptr) < 0) \
|
|
goto err_label; \
|
|
} while (0)
|
|
|
|
#define pushw(base, ptr, val, err_label) \
|
|
do { \
|
|
__u16 __val = val; \
|
|
ptr--; \
|
|
if (put_user(val_byte(__val, 1), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr--; \
|
|
if (put_user(val_byte(__val, 0), base + ptr) < 0) \
|
|
goto err_label; \
|
|
} while (0)
|
|
|
|
#define pushl(base, ptr, val, err_label) \
|
|
do { \
|
|
__u32 __val = val; \
|
|
ptr--; \
|
|
if (put_user(val_byte(__val, 3), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr--; \
|
|
if (put_user(val_byte(__val, 2), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr--; \
|
|
if (put_user(val_byte(__val, 1), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr--; \
|
|
if (put_user(val_byte(__val, 0), base + ptr) < 0) \
|
|
goto err_label; \
|
|
} while (0)
|
|
|
|
#define popb(base, ptr, err_label) \
|
|
({ \
|
|
__u8 __res; \
|
|
if (get_user(__res, base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr++; \
|
|
__res; \
|
|
})
|
|
|
|
#define popw(base, ptr, err_label) \
|
|
({ \
|
|
__u16 __res; \
|
|
if (get_user(val_byte(__res, 0), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr++; \
|
|
if (get_user(val_byte(__res, 1), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr++; \
|
|
__res; \
|
|
})
|
|
|
|
#define popl(base, ptr, err_label) \
|
|
({ \
|
|
__u32 __res; \
|
|
if (get_user(val_byte(__res, 0), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr++; \
|
|
if (get_user(val_byte(__res, 1), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr++; \
|
|
if (get_user(val_byte(__res, 2), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr++; \
|
|
if (get_user(val_byte(__res, 3), base + ptr) < 0) \
|
|
goto err_label; \
|
|
ptr++; \
|
|
__res; \
|
|
})
|
|
|
|
/* There are so many possible reasons for this function to return
|
|
* VM86_INTx, so adding another doesn't bother me. We can expect
|
|
* userspace programs to be able to handle it. (Getting a problem
|
|
* in userspace is always better than an Oops anyway.) [KD]
|
|
*/
|
|
static void do_int(struct kernel_vm86_regs *regs, int i,
|
|
unsigned char __user *ssp, unsigned short sp)
|
|
{
|
|
unsigned long __user *intr_ptr;
|
|
unsigned long segoffs;
|
|
struct vm86 *vm86 = current->thread.vm86;
|
|
|
|
if (regs->pt.cs == BIOSSEG)
|
|
goto cannot_handle;
|
|
if (is_revectored(i, &vm86->int_revectored))
|
|
goto cannot_handle;
|
|
if (i == 0x21 && is_revectored(AH(regs), &vm86->int21_revectored))
|
|
goto cannot_handle;
|
|
intr_ptr = (unsigned long __user *) (i << 2);
|
|
if (get_user(segoffs, intr_ptr))
|
|
goto cannot_handle;
|
|
if ((segoffs >> 16) == BIOSSEG)
|
|
goto cannot_handle;
|
|
pushw(ssp, sp, get_vflags(regs), cannot_handle);
|
|
pushw(ssp, sp, regs->pt.cs, cannot_handle);
|
|
pushw(ssp, sp, IP(regs), cannot_handle);
|
|
regs->pt.cs = segoffs >> 16;
|
|
SP(regs) -= 6;
|
|
IP(regs) = segoffs & 0xffff;
|
|
clear_TF(regs);
|
|
clear_IF(regs);
|
|
clear_AC(regs);
|
|
return;
|
|
|
|
cannot_handle:
|
|
save_v86_state(regs, VM86_INTx + (i << 8));
|
|
}
|
|
|
|
int handle_vm86_trap(struct kernel_vm86_regs *regs, long error_code, int trapno)
|
|
{
|
|
struct vm86 *vm86 = current->thread.vm86;
|
|
|
|
if (vm86->vm86plus.is_vm86pus) {
|
|
if ((trapno == 3) || (trapno == 1)) {
|
|
save_v86_state(regs, VM86_TRAP + (trapno << 8));
|
|
return 0;
|
|
}
|
|
do_int(regs, trapno, (unsigned char __user *) (regs->pt.ss << 4), SP(regs));
|
|
return 0;
|
|
}
|
|
if (trapno != 1)
|
|
return 1; /* we let this handle by the calling routine */
|
|
current->thread.trap_nr = trapno;
|
|
current->thread.error_code = error_code;
|
|
force_sig(SIGTRAP);
|
|
return 0;
|
|
}
|
|
|
|
void handle_vm86_fault(struct kernel_vm86_regs *regs, long error_code)
|
|
{
|
|
unsigned char opcode;
|
|
unsigned char __user *csp;
|
|
unsigned char __user *ssp;
|
|
unsigned short ip, sp, orig_flags;
|
|
int data32, pref_done;
|
|
struct vm86plus_info_struct *vmpi = ¤t->thread.vm86->vm86plus;
|
|
|
|
#define CHECK_IF_IN_TRAP \
|
|
if (vmpi->vm86dbg_active && vmpi->vm86dbg_TFpendig) \
|
|
newflags |= X86_EFLAGS_TF
|
|
|
|
orig_flags = *(unsigned short *)®s->pt.flags;
|
|
|
|
csp = (unsigned char __user *) (regs->pt.cs << 4);
|
|
ssp = (unsigned char __user *) (regs->pt.ss << 4);
|
|
sp = SP(regs);
|
|
ip = IP(regs);
|
|
|
|
data32 = 0;
|
|
pref_done = 0;
|
|
do {
|
|
switch (opcode = popb(csp, ip, simulate_sigsegv)) {
|
|
case 0x66: /* 32-bit data */ data32 = 1; break;
|
|
case 0x67: /* 32-bit address */ break;
|
|
case 0x2e: /* CS */ break;
|
|
case 0x3e: /* DS */ break;
|
|
case 0x26: /* ES */ break;
|
|
case 0x36: /* SS */ break;
|
|
case 0x65: /* GS */ break;
|
|
case 0x64: /* FS */ break;
|
|
case 0xf2: /* repnz */ break;
|
|
case 0xf3: /* rep */ break;
|
|
default: pref_done = 1;
|
|
}
|
|
} while (!pref_done);
|
|
|
|
switch (opcode) {
|
|
|
|
/* pushf */
|
|
case 0x9c:
|
|
if (data32) {
|
|
pushl(ssp, sp, get_vflags(regs), simulate_sigsegv);
|
|
SP(regs) -= 4;
|
|
} else {
|
|
pushw(ssp, sp, get_vflags(regs), simulate_sigsegv);
|
|
SP(regs) -= 2;
|
|
}
|
|
IP(regs) = ip;
|
|
goto vm86_fault_return;
|
|
|
|
/* popf */
|
|
case 0x9d:
|
|
{
|
|
unsigned long newflags;
|
|
if (data32) {
|
|
newflags = popl(ssp, sp, simulate_sigsegv);
|
|
SP(regs) += 4;
|
|
} else {
|
|
newflags = popw(ssp, sp, simulate_sigsegv);
|
|
SP(regs) += 2;
|
|
}
|
|
IP(regs) = ip;
|
|
CHECK_IF_IN_TRAP;
|
|
if (data32)
|
|
set_vflags_long(newflags, regs);
|
|
else
|
|
set_vflags_short(newflags, regs);
|
|
|
|
goto check_vip;
|
|
}
|
|
|
|
/* int xx */
|
|
case 0xcd: {
|
|
int intno = popb(csp, ip, simulate_sigsegv);
|
|
IP(regs) = ip;
|
|
if (vmpi->vm86dbg_active) {
|
|
if ((1 << (intno & 7)) & vmpi->vm86dbg_intxxtab[intno >> 3]) {
|
|
save_v86_state(regs, VM86_INTx + (intno << 8));
|
|
return;
|
|
}
|
|
}
|
|
do_int(regs, intno, ssp, sp);
|
|
return;
|
|
}
|
|
|
|
/* iret */
|
|
case 0xcf:
|
|
{
|
|
unsigned long newip;
|
|
unsigned long newcs;
|
|
unsigned long newflags;
|
|
if (data32) {
|
|
newip = popl(ssp, sp, simulate_sigsegv);
|
|
newcs = popl(ssp, sp, simulate_sigsegv);
|
|
newflags = popl(ssp, sp, simulate_sigsegv);
|
|
SP(regs) += 12;
|
|
} else {
|
|
newip = popw(ssp, sp, simulate_sigsegv);
|
|
newcs = popw(ssp, sp, simulate_sigsegv);
|
|
newflags = popw(ssp, sp, simulate_sigsegv);
|
|
SP(regs) += 6;
|
|
}
|
|
IP(regs) = newip;
|
|
regs->pt.cs = newcs;
|
|
CHECK_IF_IN_TRAP;
|
|
if (data32) {
|
|
set_vflags_long(newflags, regs);
|
|
} else {
|
|
set_vflags_short(newflags, regs);
|
|
}
|
|
goto check_vip;
|
|
}
|
|
|
|
/* cli */
|
|
case 0xfa:
|
|
IP(regs) = ip;
|
|
clear_IF(regs);
|
|
goto vm86_fault_return;
|
|
|
|
/* sti */
|
|
/*
|
|
* Damn. This is incorrect: the 'sti' instruction should actually
|
|
* enable interrupts after the /next/ instruction. Not good.
|
|
*
|
|
* Probably needs some horsing around with the TF flag. Aiee..
|
|
*/
|
|
case 0xfb:
|
|
IP(regs) = ip;
|
|
set_IF(regs);
|
|
goto check_vip;
|
|
|
|
default:
|
|
save_v86_state(regs, VM86_UNKNOWN);
|
|
}
|
|
|
|
return;
|
|
|
|
check_vip:
|
|
if ((VEFLAGS & (X86_EFLAGS_VIP | X86_EFLAGS_VIF)) ==
|
|
(X86_EFLAGS_VIP | X86_EFLAGS_VIF)) {
|
|
save_v86_state(regs, VM86_STI);
|
|
return;
|
|
}
|
|
|
|
vm86_fault_return:
|
|
if (vmpi->force_return_for_pic && (VEFLAGS & (X86_EFLAGS_IF | X86_EFLAGS_VIF))) {
|
|
save_v86_state(regs, VM86_PICRETURN);
|
|
return;
|
|
}
|
|
if (orig_flags & X86_EFLAGS_TF)
|
|
handle_vm86_trap(regs, 0, X86_TRAP_DB);
|
|
return;
|
|
|
|
simulate_sigsegv:
|
|
/* FIXME: After a long discussion with Stas we finally
|
|
* agreed, that this is wrong. Here we should
|
|
* really send a SIGSEGV to the user program.
|
|
* But how do we create the correct context? We
|
|
* are inside a general protection fault handler
|
|
* and has just returned from a page fault handler.
|
|
* The correct context for the signal handler
|
|
* should be a mixture of the two, but how do we
|
|
* get the information? [KD]
|
|
*/
|
|
save_v86_state(regs, VM86_UNKNOWN);
|
|
}
|
|
|
|
/* ---------------- vm86 special IRQ passing stuff ----------------- */
|
|
|
|
#define VM86_IRQNAME "vm86irq"
|
|
|
|
static struct vm86_irqs {
|
|
struct task_struct *tsk;
|
|
int sig;
|
|
} vm86_irqs[16];
|
|
|
|
static DEFINE_SPINLOCK(irqbits_lock);
|
|
static int irqbits;
|
|
|
|
#define ALLOWED_SIGS (1 /* 0 = don't send a signal */ \
|
|
| (1 << SIGUSR1) | (1 << SIGUSR2) | (1 << SIGIO) | (1 << SIGURG) \
|
|
| (1 << SIGUNUSED))
|
|
|
|
static irqreturn_t irq_handler(int intno, void *dev_id)
|
|
{
|
|
int irq_bit;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&irqbits_lock, flags);
|
|
irq_bit = 1 << intno;
|
|
if ((irqbits & irq_bit) || !vm86_irqs[intno].tsk)
|
|
goto out;
|
|
irqbits |= irq_bit;
|
|
if (vm86_irqs[intno].sig)
|
|
send_sig(vm86_irqs[intno].sig, vm86_irqs[intno].tsk, 1);
|
|
/*
|
|
* IRQ will be re-enabled when user asks for the irq (whether
|
|
* polling or as a result of the signal)
|
|
*/
|
|
disable_irq_nosync(intno);
|
|
spin_unlock_irqrestore(&irqbits_lock, flags);
|
|
return IRQ_HANDLED;
|
|
|
|
out:
|
|
spin_unlock_irqrestore(&irqbits_lock, flags);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
static inline void free_vm86_irq(int irqnumber)
|
|
{
|
|
unsigned long flags;
|
|
|
|
free_irq(irqnumber, NULL);
|
|
vm86_irqs[irqnumber].tsk = NULL;
|
|
|
|
spin_lock_irqsave(&irqbits_lock, flags);
|
|
irqbits &= ~(1 << irqnumber);
|
|
spin_unlock_irqrestore(&irqbits_lock, flags);
|
|
}
|
|
|
|
void release_vm86_irqs(struct task_struct *task)
|
|
{
|
|
int i;
|
|
for (i = FIRST_VM86_IRQ ; i <= LAST_VM86_IRQ; i++)
|
|
if (vm86_irqs[i].tsk == task)
|
|
free_vm86_irq(i);
|
|
}
|
|
|
|
static inline int get_and_reset_irq(int irqnumber)
|
|
{
|
|
int bit;
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
if (invalid_vm86_irq(irqnumber)) return 0;
|
|
if (vm86_irqs[irqnumber].tsk != current) return 0;
|
|
spin_lock_irqsave(&irqbits_lock, flags);
|
|
bit = irqbits & (1 << irqnumber);
|
|
irqbits &= ~bit;
|
|
if (bit) {
|
|
enable_irq(irqnumber);
|
|
ret = 1;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&irqbits_lock, flags);
|
|
return ret;
|
|
}
|
|
|
|
|
|
static int do_vm86_irq_handling(int subfunction, int irqnumber)
|
|
{
|
|
int ret;
|
|
switch (subfunction) {
|
|
case VM86_GET_AND_RESET_IRQ: {
|
|
return get_and_reset_irq(irqnumber);
|
|
}
|
|
case VM86_GET_IRQ_BITS: {
|
|
return irqbits;
|
|
}
|
|
case VM86_REQUEST_IRQ: {
|
|
int sig = irqnumber >> 8;
|
|
int irq = irqnumber & 255;
|
|
if (!capable(CAP_SYS_ADMIN)) return -EPERM;
|
|
if (!((1 << sig) & ALLOWED_SIGS)) return -EPERM;
|
|
if (invalid_vm86_irq(irq)) return -EPERM;
|
|
if (vm86_irqs[irq].tsk) return -EPERM;
|
|
ret = request_irq(irq, &irq_handler, 0, VM86_IRQNAME, NULL);
|
|
if (ret) return ret;
|
|
vm86_irqs[irq].sig = sig;
|
|
vm86_irqs[irq].tsk = current;
|
|
return irq;
|
|
}
|
|
case VM86_FREE_IRQ: {
|
|
if (invalid_vm86_irq(irqnumber)) return -EPERM;
|
|
if (!vm86_irqs[irqnumber].tsk) return 0;
|
|
if (vm86_irqs[irqnumber].tsk != current) return -EPERM;
|
|
free_vm86_irq(irqnumber);
|
|
return 0;
|
|
}
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|