On Thu, May 03, 2001 at 11:53:41AM +0300, Kalle Olavi Niemitalo wrote: > Letting processes write to EGA ports isn't that awful (with > today's sync-protected monitors), but if Mach also lets them > write to ports used by IDE or SCSI, then it "introduces a > security hole on systems where you install the package."
I have found the bugs that allow users to write to I/O ports without requesting permission. In i386/i386/ktss.c, only the last byte of the io map of the KERNEL_TSS is set to 0xff (access forbidden), all other are left as they are (all zeros? random? I don't know, but suspect all zero). Threads that don't have their own TSS use the KERNEL_TSS, and this means all threads by default (see pcb.c). BTW, I am pretty sure there is an off by one error, which means that the code writes into the byte past the bitmap, rather than the last byte of it (I verified that the bitmap indeed starts at ktss, and that means that ktss + 0x10000/8 is off by one). This is the first bug. The second bug is that the i386_io_port_add function that creates the user tss initializes it with access_all being true, which means that it is initialized to all zero. So a thread requesting any access will always get all (I suspect). The two places marked with /* XXX */ in the iopb.c file seem to indicate that this is first a correct interpretation of the code and that the author knew that this had to be fixed. Replacing the TRUE at the XXX with FALSE, and setting all bytes in the map to 0xff in the KERNEL_TSS should fix this, but I did not test that part. The reason I could not continue testing the user tss is that the i386_io_port_add call can't find the device -> io_port mapping. I tried both the kd and iopl device, and neither worked. This requires more debugging (maybe tomorrow). We decided by now that this is the wrong approach anyway, and that all this code is doomed to be replaced by a somewhat different approach. However, I think it is not a bad idea to fix this, especially as it doesn't seem to hard, before going on and messing with it (well, at least for me as I have to learn more details about the tss stuff first before I can have a go at the new interface). I am including my preliminary patch and my two test programs (incomplete, just for reference) that show the i386_io_port_add problem. The test program you submitted fails with Illegal Instruction if you apply my patch. BTW, I noticed that there are reminiscences of io map support in thread_setstatus and thread_getstatus, with a reference to a i386_io_port_map call that seems to be intended to set/get a complete bitmask. This cruft can be cleaned up when we have a go at the new interface. Thanks, Marcus --- /mnt/marcus/gnu/cvs/gnumach/i386/i386/ktss.c Tue Feb 25 22:27:10 1997 +++ i386/i386/ktss.c Sun Oct 7 05:15:18 2001 @@ -39,6 +39,7 @@ void ktss_init() { + int i; /* XXX temporary exception stack */ static exception_stack[1024]; @@ -52,8 +53,9 @@ ktss.esp0 = (unsigned)(exception_stack+1024); ktss.io_bit_map_offset = sizeof(ktss); - /* Set the last byte in the I/O bitmap to all 1's. */ - ((unsigned char*)&ktss)[sizeof(ktss)+65536/8] = 0xff; + /* Set all bytes in the I/O bitmap to all 1's. */ + for (i = sizeof(ktss); i < sizeof(ktss)+65536/8; i++) + ((unsigned char*)&ktss)[i] = 0xff; /* Load the TSS. */ ltr(KERNEL_TSS); --- /mnt/marcus/gnu/cvs/gnumach/i386/i386/iopb.c Tue Feb 25 22:27:09 1997 +++ i386/i386/iopb.c Sun Oct 7 05:00:09 2001 @@ -270,7 +270,7 @@ register iopb_tss_t ts; ts = (iopb_tss_t) kalloc(sizeof (struct iopb_tss)); - io_tss_init(ts, TRUE); /* XXX */ + io_tss_init(ts, FALSE); return ts; } @@ -357,7 +360,7 @@ simple_unlock(&iopb_lock); new_io_tss = (iopb_tss_t) kalloc(sizeof(struct iopb_tss)); - io_tss_init(new_io_tss, TRUE); /* XXX */ + io_tss_init(new_io_tss, FALSE); goto Retry; } -- `Rhubarb is no Egyptian god.' Debian http://www.debian.org [EMAIL PROTECTED] Marcus Brinkmann GNU http://www.gnu.org [EMAIL PROTECTED] [EMAIL PROTECTED] http://www.marcus-brinkmann.de
#define _GNU_SOURCE #include <mach.h> #include <device/device.h> #include "mach_i386.h" #include <argp.h> /* struct argp */ #include <unistd.h> /* sleep */ #include "util.h" /* _ */ static __inline void outb (unsigned char value, unsigned short int port) { __asm__ __volatile__ ("outb %b0,%w1": :"a" (value), "Nd" (port)); } const char *argp_program_version = "ioport (" PACKAGE ") " VERSION; const char *argp_program_bug_address = "<[EMAIL PROTECTED]>"; #define VIDMMAP_BEGIN 0xB8000 #define VIDMMAP_SIZE (0xC0000 - 0xB8000) #define VIDMMAP_KDOFS 0xA0000 /* == kd_bitmap_start in mach/i386/i386at/kd.c */ int main (int argc, char **argv) { kern_return_t err; mach_port_t device_master, kd_device; unsigned short *videomem = NULL; vm_address_t mapped; memory_object_t kd_mem = MACH_PORT_NULL; static const struct argp argp = { 0, 0, 0, N_("Move the cursor to the top left corner for a second " "by outputting values to EGA I/O ports.") }; argp_parse (&argp, argc, argv, 0, 0, 0); err = get_privileged_ports (0, &device_master); if (err) error (1, err, "get_privileged_ports"); err = device_open (device_master, D_WRITE, "kd", &kd_device); if (err) error (1, err, "device_open"); err = device_map (kd_device, VM_PROT_READ | VM_PROT_WRITE, VIDMMAP_BEGIN - VIDMMAP_KDOFS, VIDMMAP_SIZE, &kd_mem, 0); if (err) error (1, err, "device_map"); err = vm_map (mach_task_self (), &mapped, VIDMMAP_SIZE, 0, 1, kd_mem, VIDMMAP_BEGIN - VIDMMAP_KDOFS, 0, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_NONE); if (err) error (1, err, "vm_map"); videomem = (unsigned short *) mapped; if (! videomem) error (1, err, "XXX"); err = i386_io_port_add (mach_thread_self (), kd_device); if (err) error (1, err, "i386_io_port_add"); outb (0x0E, 0x3D4); /* cursor position high byte register */ outb (0, 0x3D5); outb (0x0F, 0x3D4); /* cursor position low byte register */ outb (0, 0x3D5); sleep (1); return 0; }
#define _GNU_SOURCE #include <mach.h> #include <device/device.h> #include "mach_i386.h" #include <argp.h> /* struct argp */ #include <unistd.h> /* sleep */ #include "util.h" /* _ */ static __inline void outb (unsigned char value, unsigned short int port) { __asm__ __volatile__ ("outb %b0,%w1": :"a" (value), "Nd" (port)); } const char *argp_program_version = "ioport (" PACKAGE ") " VERSION; const char *argp_program_bug_address = "<[EMAIL PROTECTED]>"; #define VIDMMAP_BEGIN 0xB8000 #define VIDMMAP_SIZE (0xC0000 - 0xB8000) #define VIDMMAP_KDOFS 0xA0000 /* == kd_bitmap_start in mach/i386/i386at/kd.c */ int main (int argc, char **argv) { kern_return_t err; mach_port_t device_master, kd_device; unsigned short *videomem = NULL; vm_address_t mapped; memory_object_t kd_mem = MACH_PORT_NULL; static const struct argp argp = { 0, 0, 0, N_("Move the cursor to the top left corner for a second " "by outputting values to EGA I/O ports.") }; argp_parse (&argp, argc, argv, 0, 0, 0); err = get_privileged_ports (0, &device_master); if (err) error (1, err, "get_privileged_ports"); err = device_open (device_master, D_WRITE, "iopl", &kd_device); if (err) error (1, err, "device_open"); err = i386_io_port_add (mach_thread_self (), kd_device); if (err) error (1, err, "i386_io_port_add"); outb (0x0E, 0x3D4); /* cursor position high byte register */ outb (0, 0x3D5); outb (0x0F, 0x3D4); /* cursor position low byte register */ outb (0, 0x3D5); sleep (1); return 0; }