Re: debug registers in 2.3.x + PATCH

Petr Vandrovec (vandrove@vc.cvut.cz)
Mon, 6 Sep 1999 22:59:05 +0200


Hi Linus,
in short, search for lines '@ Cut here...' and '@ And here...'. You'll find
patch destined for 2.3.17 between them. Today I found that lazy DR7 setting
can (and will) affect VM86 processes if there is address match between
vm86 task (first megabyte) and debug registers. Because of ptrace() interface
is not restricted, everyone who has rights to trace at least one task can
affect all vm86 tasks in your box :-(
Patch moves check for nulity of DR7 before check for VM86, so spurious
exceptions caused by DR0-DR3 match are ignored. Other debug exceptions are
still processed by VM86 specific code as were before.

Hi Alan,
because of do_debug() in 2.2.12 kernel is same as 2.3.16, I think that
2.2.x series is affected too and maybe that someone can count this problem
as security bug...

Hi others,
to follow up myself, I wrote test sample which confirmed my thinkings
that lazy DR7 is buggy if you have more than zero VM86 tasks and found one
another thing:
On my dual SMP board, (PIII/450), if I remove sleep(1) from catch449.c,
(see source below) I get:
attach: Success
pokeusr dr0: No such process
pokeusr dr7: No such process
cont: No such process
With sleep(1) it works for first catch449. Running 2nd, 3rd.. instance of
catch449 is still a bit tricky. Am I stupid or is there race somewhere?
catch449's child is listed with status "T" in tasklist, which corresponds
to seen behavior - attach was successful, but continue failed :-(
Now back to topic, if I run two catch449.c (for each CPU one), every start
of emu.c ends immediately with:

vm86 returned: 262 (type=6, arg=1)
VM Info: Loops: 1, Except: 0, Var: 0

And 6 = TRAP, 1 = INT1 (trace) :-(
If I run first emu and start catch449 after that, emu immediately stops :-(
So I wrote patch attached here too. This patch moves check for breakpoints
caused by DR_TRAP[0-3] before VM86 check, so it correctly clears DR7 in this
case. I left check for other possible sources (mainly Trace Flag) below check,
so DOS debuggers which sets TF still works (tested with Borland TD).
Diff is against 2.3.16 and machine with this patch is happilly running
on my desk just now. 2.2.12 contains same code, so maybe we should apply it
to 2.2.x kernels too, what do you think Alan?

@ Cut here...
--- linux/arch/i386/kernel/traps.c Wed Aug 18 20:27:34 1999
+++ linux/arch/i386/kernel/traps.c Mon Sep 6 22:45:22 1999
@@ -355,11 +355,17 @@
unsigned int condition;
struct task_struct *tsk = current;

+ __asm__ __volatile__("movl %%db6,%0" : "=r" (condition));
+
+ /* Mask out spurious debug traps due to lazy DR7 setting */
+ if (condition & (DR_TRAP0|DR_TRAP1|DR_TRAP2|DR_TRAP3)) {
+ if (!tsk->thread.debugreg[7])
+ goto clear_dr7;
+ }
+
if (regs->eflags & VM_MASK)
goto debug_vm86;

- __asm__ __volatile__("movl %%db6,%0" : "=r" (condition));
-
/* Mask out spurious TF errors due to lazy TF clearing */
if (condition & DR_STEP) {
/*
@@ -373,12 +379,6 @@
*/
if ((tsk->flags & (PF_DTRACE|PF_PTRACED)) == PF_DTRACE)
goto clear_TF;
- }
-
- /* Mask out spurious debug traps due to lazy DR7 setting */
- if (condition & (DR_TRAP0|DR_TRAP1|DR_TRAP2|DR_TRAP3)) {
- if (!tsk->thread.debugreg[7])
- goto clear_dr7;
}

/* If this is a kernel mode trap, we need to reset db7 to allow us to continue sanely */
@ And here...

/* catch449.c */
#include <sys/vm86.h>
#include <stdlib.h>
#include <stdio.h>
#include <asm/user.h>

#define XFS(x) ((unsigned long)&(((struct user*)NULL)->x))

void main(int argc, char* argv) {
int pid = fork();
if (pid) {
sleep(1);
ptrace(PTRACE_ATTACH, pid, 0, 0);
perror("attach");
ptrace(PTRACE_POKEUSR, pid, XFS(u_debugreg[0]), 0x449);
perror("pokeusr dr0");
ptrace(PTRACE_POKEUSR, pid, XFS(u_debugreg[7]), 0x00030003);
perror("pokeusr dr7");
ptrace(PTRACE_CONT, pid, 0, 0);
perror("cont");
while (1) {
sleep(1000);
}
} else {
printf("Traced..\n");
while (1) ;
printf("End trace..\n");
}
}
/* end of catch449.c */

/* emu.c */
#include <asm/vm86.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/syscall.h>
#include <errno.h>

_syscall2(int,vm86,int,request,struct vm86plus_struct*,vm);

void main(void) {
int i;
unsigned char* m;
struct vm86plus_struct vm;

i = open("/dev/zero", O_RDONLY);
if (i < 0)
perror("open");
m = mmap(0, 1048576, PROT_EXEC|PROT_READ|PROT_WRITE,
MAP_FIXED|MAP_PRIVATE, i, 0);
close(i);
if ((caddr_t)m == (caddr_t)-1)
perror("mmap");
vm.regs.ebx = 0x410; /* loops counter */
vm.regs.ecx = 0;
vm.regs.edx = 0;
vm.regs.esi = 0x420; /* exception counter */
vm.regs.edi = 0x449; /* breakpoint check */
vm.regs.ebp = 0;
vm.regs.eax = 0;
vm.regs.orig_eax = 0;
vm.regs.eip = 0x400; /* code start is 0000:0400 */
vm.regs.cs = 0;
vm.regs.eflags = VM_MASK | IF_MASK;
vm.regs.esp = 0x800;
vm.regs.ss = 0;
vm.regs.es = 0;
vm.regs.ds = 0;
vm.regs.fs = 0;
vm.regs.gs = 0;
vm.flags = VM86_ENTER;
vm.screen_bitmap = 0;
vm.cpu_type = CPU_586;
memset(&vm.int_revectored, 0, sizeof(vm.int_revectored));
memset(&vm.int21_revectored, 0, sizeof(vm.int21_revectored));
memset(&vm.vm86plus, 0, sizeof(vm.vm86plus));
for (i = 0; i < 1024; i+=4) {
/* IDT points to 0000:0406, just in case... */
m[0] = 0x06; m[1] = 0x04; m[2] = 0; m[3] = 0;
}
/* l0400: inc word [bx]; mov al,[di]; jmp l0400;
l0406: inc word [si]; jmp l0400; */
memcpy(m + 0x400, "\xFF\x07\x8A\x05\xEB\xFA\xFF\x04\xEB\xF6",
10);
i = vm86(VM86_ENTER, &vm);
printf("vm86 returned: %d (type=%d, arg=%d)\n", i, VM86_TYPE(i), VM86_ARG(i));
perror("errno");
printf("VM Info: Loops: %d, Except: %d, Var: %d\n",
/* read 0x449 to clear DR7 for this CPU... otherwise you have
to reboot to get access to 0x449 back to work (or run gdb) */
m[0x410], m[0x420], m[0x449]);
};
/* end of emu.c */

Best regards,
Petr Vandrovec
vandrove@vc.cvut.cz

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu
Please read the FAQ at http://www.tux.org/lkml/