// The TSS I/O permission bitmap. See include/arch/ioperm.h for why. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/mmu.h" #include "core/proc.h" #include "arch/ioperm.h" // The 64-bit TSS, written out rather than indexed. // // The old code reached it as `uint *tss` and set `tss[16]` to put 0x68 in the // I/O map base -- but the I/O map base is at offset 102, which is the high half // of the dword at index 25, not 16. Index 16 is offset 64, in the middle of the // interrupt stack table. // // That was not harmless. The field it meant to write stayed 0, and a base of 0 // with a limit of 0x67 does not mean "no bitmap" -- it means the bitmap starts // at the top of the TSS, so the CPU checked ports 0-831 against the TSS's own // bytes. Those bytes are RSP0: a kernel stack pointer, whose zero bits are // *permission*. Some low ports were reachable from user mode and had been // since the kernel was 64-bit, which nothing had any reason to notice. // // Naming the fields is what makes that visible, so they are named. struct taskstate64 { uint32 res0; uint64 rsp0; uint64 rsp1; uint64 rsp2; uint64 res1; uint64 ist[7]; uint64 res2; uint16 res3; uint16 iomb; // offset from the base of the TSS } __attribute__((packed)); #define IOMAP_BITS 65536 #define IOMAP_BYTES (IOMAP_BITS / 8) // The TSS and its bitmap have to be one object: `iomb` is a 16-bit offset from // the TSS base, and the TSS descriptor's limit is what cuts the bitmap off. // So they are declared together and there is no way for them to drift apart. // // 8 KB per CPU of permanently resident kernel memory is the cost of being able // to say "these ports and no others", and it is charged whether or not any // process ever uses it. A bitmap sized to the highest port actually granted // would be smaller and would have to be resized at runtime, under a descriptor // the CPU is using; this is the version that is obviously correct. struct tssblk { struct taskstate64 ts; uint8 map[IOMAP_BYTES]; // Intel requires a byte of all-ones past the end, so an access that straddles // the final port still reads a deny bit rather than whatever follows. uint8 tail; } __attribute__((packed, aligned(16))); static struct tssblk tssblk[NCPU]; // What each CPU currently has open. Kept per CPU rather than read back from the // process, because switching *away* has to close what was opened and the // process that opened it may be part-way through exit() by then. static struct { struct ioport_range r[NIOPORT]; int n; } installed[NCPU]; static void map_set(uint8 *map, uint16 base, uint16 len, int allow) { uint32 p, end = (uint32)base + len; if (end > IOMAP_BITS) end = IOMAP_BITS; for (p = base; p < end; p++) { if (allow) map[p >> 3] &= ~(1 << (p & 7)); else map[p >> 3] |= (1 << (p & 7)); } } void * ioperm_tss(int cpu, uint64 *limit) { struct tssblk *b = &tssblk[cpu]; // Deny everything to start with. A zero bit is permission, so a zeroed // bitmap is an open machine -- this memset is not initialisation tidiness. memset(b->map, 0xFF, sizeof(b->map)); b->tail = 0xFF; b->ts.iomb = (uint16)__builtin_offsetof(struct tssblk, map); installed[cpu].n = 0; // The limit is the offset of the last valid byte, and it has to include the // trailing 0xFF -- stopping one byte short is the difference between a // straddling access being denied and it reading the next object. *limit = (uint64)__builtin_offsetof(struct tssblk, tail); return b; } // The same block, without the reset. seginit() calls ioperm_tss() once per CPU // to build it; everything after that just wants the pointer. void * ioperm_tss_of(int cpu) { return &tssblk[cpu]; } void ioperm_set_rsp0(void *tss, addr_t stktop) { ((struct taskstate64 *)tss)->rsp0 = (uint64)stktop; } void ioperm_grant(struct proc *p, uint16 base, uint16 len) { int i; if (len == 0) return; for (i = 0; i < p->nioport; i++) if (p->ioport[i].base == base && p->ioport[i].len == len) return; if (p->nioport >= NIOPORT) return; // refused quietly; the driver will fault loudly p->ioport[p->nioport].base = base; p->ioport[p->nioport].len = len; p->nioport++; } void ioperm_revoke_all(struct proc *p) { p->nioport = 0; // The bitmap is not touched here. This can run on a CPU that is not the one // holding the process's ranges open, and the next switchuvm() on each CPU // closes what that CPU opened -- which is the only place the two can be made // to agree without an IPI. } void ioperm_switch(struct proc *p) { int c = cpunum(); struct tssblk *b = &tssblk[c]; int i; // Close whatever was open. Doing this unconditionally rather than diffing // against the incoming process costs a few hundred bit clears on a machine // where nobody uses ports at all -- which is `installed[c].n == 0`, and then // this loop does nothing. for (i = 0; i < installed[c].n; i++) map_set(b->map, installed[c].r[i].base, installed[c].r[i].len, 0); installed[c].n = 0; if (!p || p->nioport == 0) return; for (i = 0; i < p->nioport && i < NIOPORT; i++) { map_set(b->map, p->ioport[i].base, p->ioport[i].len, 1); installed[c].r[i] = p->ioport[i]; } installed[c].n = i; }