x86: Add support for (paging-based) protection domains

This patch implements a simple, lightweight form of protection domains
using a pluggable framework.  Currently, the following plugin is
available:

 - Flat memory model with paging.

The overall goal of a protection domain implementation within this
framework is to define a set of resources that should be accessible to
each protection domain and to prevent that protection domain from
accessing other resources.  The details of each implementation of
protection domains may differ substantially, but they should all be
guided by the principle of least privilege.  However, that idealized
principle is balanced against the practical objectives of limiting the
number of relatively time-consuming context switches and minimizing
changes to existing code.

For additional information, please refer to cpu/x86/mm/README.md.

This patch also causes the C compiler to be used as the default linker
and assembler.
This commit is contained in:
Michael LeMay 2015-08-10 08:34:02 -07:00
parent b0de416682
commit 3908253038
48 changed files with 3558 additions and 295 deletions

View file

@ -28,11 +28,13 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "cpu.h"
#include "gdt.h"
#include "helpers.h"
#include "idt.h"
#include "interrupt.h"
#include "irq.h"
#include "stacks.h"
static void
double_fault_handler(struct interrupt_context context)
@ -40,16 +42,79 @@ double_fault_handler(struct interrupt_context context)
halt();
}
/*---------------------------------------------------------------------------*/
void
cpu_init(void)
/* The OS has switched to its own segment descriptors. However, the protection
* domain support, if enabled, has not yet been fully activated.
*/
static void
boot_stage1(void)
{
gdt_init();
idt_init();
/* Set an interrupt handler for Double Fault exception. This way, we avoid
* the system to triple fault, leaving no trace about what happened.
*/
SET_INTERRUPT_HANDLER(8, 1, double_fault_handler);
SET_EXCEPTION_HANDLER(8, 1, double_fault_handler);
irq_init();
/* Initialize protection domain support, if enabled */
prot_domains_init();
prot_domains_leave_boot_stage1();
}
/*---------------------------------------------------------------------------*/
int main(void);
/* This routine runs with the initial, flat address space, even if protection
* domain support is enabled. The goal behind the design of this routine is to
* keep it as short as possible, since it is unable to directly reference data
* and invoke functions that are intended to be accessible later after the
* system has booted when a multi-segment protection domain model is in use.
*/
void
cpu_boot_stage0(void)
{
/* Reserve three stack slots for return addresses */
uintptr_t top_of_stack = STACKS_INIT_TOP;
#if X86_CONF_PROT_DOMAINS != X86_CONF_PROT_DOMAINS__NONE
uintptr_t *top_of_stack_ptr = (uintptr_t *)top_of_stack;
top_of_stack_ptr[0] = (uintptr_t)prot_domains_launch_kernel;
top_of_stack_ptr[1] = (uintptr_t)prot_domains_launch_app;
#endif
/* Perform common GDT initialization */
gdt_init();
/* Switch all data segment registers to the newly-initialized flat data
* descriptor.
*/
__asm__(
"mov %0, %%ds\n\t"
"mov %0, %%es\n\t"
"mov %0, %%fs\n\t"
"mov %0, %%gs\n\t"
:
: "r" (GDT_SEL_DATA_FLAT)
);
/**
* Perform specific GDT initialization tasks for the protection domain
* implementation that is enabled, if any.
*/
prot_domains_gdt_init();
/* Do not pass memory operands to the asm block below, since it is
* switching from the flat address space to a multi-segment address space
* model if such a model is used by the enabled protection domain
* implementation, if any.
*/
__asm__(
"mov %[_ss_], %%ss\n\t"
"mov %[_esp_], %%esp\n\t"
"ljmp %[_cs_], %[_stage1_]\n\t"
:
: [_ss_] "r" (GDT_SEL_STK_EXC),
[_esp_] "r" (top_of_stack),
[_cs_] "i" ((uint16_t)GDT_SEL_CODE_EXC),
[_stage1_] "i" (boot_stage1)
);
}

View file

@ -31,6 +31,8 @@
#ifndef CPU_H
#define CPU_H
void cpu_init(void);
#include "prot-domains.h"
void cpu_boot_stage0(void) ATTR_CODE_BOOT;
#endif /* CPU_H */

View file

@ -29,45 +29,15 @@
*/
#include <stdint.h>
#include "gdt.h"
#include "gdt-layout.h"
#include "helpers.h"
#include "prot-domains.h"
#include "segmentation.h"
#define NUM_DESC 3
#define GDT_IDX_NULL 0
#define GDT_IDX_CODE 1
#define GDT_IDX_DATA 2
/* All code in the x86 port of Contiki runs at ring (privilege) level 0 */
#define PRIV_LVL 0
/* Compute GDT selector from descriptor index and requested privilege level */
#define GDT_SEL(IDX, RPL) (((IDX) << 3) | (RPL))
#define GDT_SEL_NULL GDT_SEL(GDT_IDX_NULL, 0)
#define GDT_SEL_CODE GDT_SEL(GDT_IDX_CODE, PRIV_LVL)
#define GDT_SEL_DATA GDT_SEL(GDT_IDX_DATA, PRIV_LVL)
/* Each define here is for a specific flag in the descriptor. Refer to Intel
* Combined Manual (Intel 64 and IA-32 Architectures Software Developer's
* Manual), Vol. 3, Section 3.4.5 for a description of each flag.
*/
#define SEG_DESCTYPE(x) ((x) << 0x04) /* Descriptor type (0 for system, 1 for code/data) */
#define SEG_PRES(x) ((x) << 0x07) /* Present */
#define SEG_SAVL(x) ((x) << 0x0C) /* Available for system use */
#define SEG_LONG(x) ((x) << 0x0D) /* Long mode */
#define SEG_SIZE(x) ((x) << 0x0E) /* Size (0 for 16-bit, 1 for 32) */
#define SEG_GRAN(x) ((x) << 0x0F) /* Granularity (0 for 1B - 1MB, 1 for 4KB - 4GB) */
#define SEG_PRIV(x) (((x) & 0x03) << 0x05) /* Set privilege level (0 - 3) */
#define SEG_DATA_RDWR 0x02 /* Read/Write */
#define SEG_CODE_EXRD 0x0A /* Execute/Read */
#define GDT_CODE_PL0 SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | \
SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | \
SEG_PRIV(0) | SEG_CODE_EXRD
#define GDT_DATA_PL0 SEG_DESCTYPE(1) | SEG_PRES(1) | SEG_SAVL(0) | \
SEG_LONG(0) | SEG_SIZE(1) | SEG_GRAN(1) | \
SEG_PRIV(0) | SEG_DATA_RDWR
#define GDT_MEM_PL0 (SEG_DESCTYPE_NSYS | SEG_GRAN_PAGE)
#define GDT_CODE_PL0 (GDT_MEM_PL0 | SEG_TYPE_CODE_EXRD)
#define GDT_DATA_PL0 (GDT_MEM_PL0 | SEG_TYPE_DATA_RDWR)
typedef struct gdtr
{
@ -75,41 +45,53 @@ typedef struct gdtr
uint32_t base;
} __attribute__((packed)) gdtr_t;
typedef uint64_t segment_desc_t;
/* From Intel Combined Manual, Vol. 3 , Section 3.5.1: The base addresses of
* the GDT should be aligned on an eight-byte boundary to yield the best
* processor performance.
*/
static segment_desc_t gdt[NUM_DESC] __attribute__ ((aligned (8)));
segment_desc_t __attribute__ ((aligned(8))) ATTR_BSS_GDT_START
gdt[GDT_NUM_FIXED_DESC];
static void
set_descriptor(unsigned int index, uint32_t base, uint32_t limit, uint16_t flag)
#define GDT_LEN \
((((uintptr_t)&_ebss_gdt_addr) - \
(uintptr_t)gdt)/sizeof(segment_desc_t))
/*---------------------------------------------------------------------------*/
static void ATTR_CODE_BOOT
set_descriptor(unsigned int index,
uint32_t base,
uint32_t len,
uint16_t flag)
{
segment_desc_t descriptor;
if (index >= NUM_DESC)
return;
if(GDT_LEN <= index) {
halt();
}
/* Create the high 32 bit segment */
descriptor = limit & 0x000F0000; /* set limit bits 19:16 */
descriptor |= (flag << 8) & 0x00F0FF00; /* set type, p, dpl, s, g, d/b, l and avl fields */
descriptor |= (base >> 16) & 0x000000FF; /* set base bits 23:16 */
descriptor |= base & 0xFF000000; /* set base bits 31:24 */
/* Shift by 32 to allow for low part of segment */
descriptor <<= 32;
/* Create the low 32 bit segment */
descriptor |= base << 16; /* set base bits 15:0 */
descriptor |= limit & 0x0000FFFF; /* set limit bits 15:0 */
segment_desc_init(&descriptor, base, len, flag);
/* Save descriptor into gdt */
gdt[index] = descriptor;
}
/*---------------------------------------------------------------------------*/
void
gdt_copy_desc_change_dpl(unsigned int dest_idx,
unsigned int src_idx,
unsigned dpl)
{
segment_desc_t desc;
if((GDT_LEN <= dest_idx) || (GDT_LEN <= src_idx)) {
halt();
}
/* This function initializes the Global Offset Table. For simplicity, the
desc = gdt[src_idx];
SEG_SET_FLAG(desc, DPL, dpl);
gdt[dest_idx] = desc;
}
/*---------------------------------------------------------------------------*/
/* This function initializes the Global Descriptor Table. For simplicity, the
* memory is organized following the flat model. Thus, memory appears to
* Contiki as a single continuous address space. Code, data, and stack
* are all contained in this address space (so called linear address space).
@ -120,29 +102,35 @@ gdt_init(void)
gdtr_t gdtr;
/* Initialize gdtr structure */
gdtr.limit = sizeof(segment_desc_t) * NUM_DESC - 1;
gdtr.limit = sizeof(segment_desc_t) * GDT_LEN - 1;
gdtr.base = (uint32_t) &gdt;
/* Initialize descriptors */
set_descriptor(GDT_IDX_NULL, 0, 0, 0);
set_descriptor(GDT_IDX_CODE, 0, 0x0FFFFF, GDT_CODE_PL0);
set_descriptor(GDT_IDX_DATA, 0, 0x0FFFFF, GDT_DATA_PL0);
set_descriptor(GDT_IDX_CODE_FLAT, 0, 0x100000, GDT_CODE_PL0);
set_descriptor(GDT_IDX_DATA_FLAT, 0, 0x100000, GDT_DATA_PL0);
/* Load GDTR register and update segment registers.
*
* CS register cannot be changed directly. For that reason, we do a far jump.
*/
__asm__ ("lgdt %[_gdtr_]\n\t"
"jmp %[_cs_], $1f\n\t"
"1:\n\t"
"mov %[_ds_], %%ds\n\t"
"mov %[_ds_], %%ss\n\t"
"mov %[_ds_], %%es\n\t"
"mov %[_ds_], %%fs\n\t"
"mov %[_ds_], %%gs\n\t"
:
: [_gdtr_] "m" (gdtr),
[_cs_] "i" (GDT_SEL_CODE),
[_ds_] "r" (GDT_SEL_DATA)
);
/* Load GDTR */
__asm__ __volatile__ ("lgdt %0" :: "m" (gdtr));
}
/*---------------------------------------------------------------------------*/
void
gdt_insert(unsigned int idx, segment_desc_t desc)
{
if(GDT_LEN <= idx) {
halt();
}
gdt[idx] = desc;
}
/*---------------------------------------------------------------------------*/
void
gdt_lookup(unsigned int idx, segment_desc_t *desc)
{
if((GDT_LEN <= idx) || (desc == NULL)) {
halt();
}
*desc = gdt[idx];
}
/*---------------------------------------------------------------------------*/

View file

@ -1,5 +1,5 @@
/*
* Copyright (C) 2015, Intel Corporation. All rights reserved.
* Copyright (C) 2015-2016, Intel Corporation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@ -31,6 +31,32 @@
#ifndef GDT_H
#define GDT_H
void gdt_init(void);
#include "gdt-layout.h"
#include "prot-domains.h"
#include "segmentation.h"
extern segment_desc_t gdt[];
extern int _ebss_gdt_addr;
#define GDT_IDX_OF_DESC(ptr) \
((((uintptr_t)(ptr)) - ((uintptr_t)&gdt))/ \
sizeof(segment_desc_t))
/**
* \brief Compute the selector for a GDT entry allocated somewhere besides gdt.c.
* \param ptr Pointer to GDT descriptor.
* \param rpl Requested Privilege Level.
*/
#define GDT_SEL_OF_DESC(ptr, rpl) GDT_SEL(GDT_IDX_OF_DESC(ptr), rpl)
#define ATTR_BSS_GDT __attribute__((section(".gdt_bss")))
#define ATTR_BSS_GDT_START __attribute__((section(".gdt_bss_start")))
void gdt_copy_desc_change_dpl(unsigned int dest_idx,
unsigned int src_idx,
unsigned dpl);
void gdt_init(void) ATTR_CODE_BOOT;
void gdt_insert(unsigned int idx, segment_desc_t desc);
void gdt_lookup(unsigned int idx, segment_desc_t *desc);
#endif /* GDT_H */

View file

@ -28,9 +28,13 @@
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "gdt-layout.h"
#include "prot-domains.h"
#include <stdint.h>
#include "helpers.h"
#include "segmentation.h"
#include "idt.h"
#define NUM_DESC 256
@ -55,22 +59,27 @@ typedef struct intr_gate_desc {
* of the IDT should be aligned on an 8-byte boundary to maximize performance
* of cache line fills.
*/
static intr_gate_desc_t idt[NUM_DESC] __attribute__ ((aligned(8)));
static intr_gate_desc_t __attribute__((aligned(8))) ATTR_BSS_KERN
idt[NUM_DESC];
/*---------------------------------------------------------------------------*/
/* XXX: If you change this function prototype, make sure you fix the assembly
* code in SET_INTERRUPT_HANDLER macro in interrupt.h. Otherwise, you might
* code in SET_INT_EXC_HANDLER macro in interrupt.h. Otherwise, you might
* face a very-hard-to-find bug in the interrupt handling system.
*/
void
idt_set_intr_gate_desc(int intr_num, uint32_t offset)
idt_set_intr_gate_desc(int intr_num,
uint32_t offset,
uint16_t cs,
uint16_t dpl)
{
intr_gate_desc_t *desc = &idt[intr_num];
desc->offset_low = offset & 0xFFFF;
desc->selector = 0x08; /* Offset in GDT for code segment */
desc->selector = cs;
desc->fixed = BIT(9) | BIT(10);
desc->d = 1;
desc->dpl = 0;
desc->dpl = dpl;
desc->p = 1;
desc->offset_high = (offset >> 16) & 0xFFFF;
}

View file

@ -32,8 +32,12 @@
#define IDT_H
#include <stdint.h>
#include "prot-domains.h"
void idt_init(void);
void idt_set_intr_gate_desc(int intr_num, uint32_t offset);
void idt_init(void) ATTR_CODE_BOOT;
void idt_set_intr_gate_desc(int intr_num,
uint32_t offset,
uint16_t cs,
uint16_t dpl);
#endif /* IDT_H */

View file

@ -32,10 +32,17 @@
#define INTERRUPT_H
#include <stdint.h>
#include "gdt-layout.h"
#include "idt.h"
struct interrupt_context {
/* The general-purpose register values are saved by the pushal instruction in
* the interrupt dispatcher. Having access to these saved values may be
* useful in some future interrupt or exception handler, and saving and later
* restoring them also enables the ISR to freely overwrite the EAX, ECX, and
* EDX registers as is permitted by the cdecl calling convention.
*/
uint32_t edi;
uint32_t esi;
uint32_t ebp;
@ -44,16 +51,28 @@ struct interrupt_context {
uint32_t edx;
uint32_t ecx;
uint32_t eax;
/* These two values are pushed on the stack by the CPU when it delivers an
* exception with an associated error code. Currently, only the double fault
* handler accepts this structure as a parameter, and that type of exception
* does have an associated error code.
*/
uint32_t error_code;
uint32_t eip;
/* The CPU pushes additional values beyond these on the stack, specifically
* the code segment descriptor and flags. If a privilege-level change occurs
* during delivery, the CPU additionally pushes the stack pointer and stack
* segment descriptor.
*/
};
#define ISR_STUB(label_str, has_error_code, handler_str) \
#define ISR_STUB(label_str, has_error_code, handler_str, exc) \
"jmp 2f\n\t" \
".align 4\n\t" \
label_str ":\n\t" \
" pushal\n\t" \
PROT_DOMAINS_ENTER_ISR(exc) \
" call " handler_str "\n\t" \
PROT_DOMAINS_LEAVE_ISR(exc) \
" popal\n\t" \
" .if " #has_error_code "\n\t" \
" add $4, %%esp\n\t" \
@ -72,6 +91,14 @@ struct interrupt_context {
* void handler(void)
* Otherwise, it should be:
* void handler(struct interrupt_context context)
* exc: 0 if this is an interrupt, which should be handled
* at the interrupt privilege level. 1 if this is an
* exception, which should be handled at the
* exception privilege level.
* dpl: Privilege level for IDT descriptor, which is the
* numerically-highest privilege level that can
* generate this interrupt with a software interrupt
* instruction.
*
* Since there is no easy way to write an Interrupt Service Routines
* (ISR) in C (for further information on this, see [1]), we provide
@ -81,18 +108,30 @@ struct interrupt_context {
*
* [1] http://wiki.osdev.org/Interrupt_Service_Routines
*/
#define SET_INTERRUPT_HANDLER(num, has_error_code, handler) \
do { \
__asm__ __volatile__ ( \
"push $1f\n\t" \
"push %0\n\t" \
"call %P1\n\t" \
"add $8, %%esp\n\t" \
ISR_STUB("1", has_error_code, "%P2") \
:: "g" (num), "i" (idt_set_intr_gate_desc), "i" (handler) \
: "eax", "ecx", "edx" \
); \
#define SET_INT_EXC_HANDLER(num, has_error_code, handler, exc, dpl) \
do { \
__asm__ __volatile__ ( \
"pushl %[_dpl_]\n\t" \
"pushl %[_cs_]\n\t" \
"pushl $1f\n\t" \
"pushl %[_isr_num_]\n\t" \
"call idt_set_intr_gate_desc\n\t" \
"add $16, %%esp\n\t" \
ISR_STUB("1", has_error_code, "%P[_handler_]", exc) \
: \
: [_isr_num_] "g" (num), \
[_handler_] "i" (handler), \
[_cs_] "i" (exc ? GDT_SEL_CODE_EXC : GDT_SEL_CODE_INT), \
[_dpl_] "i" (dpl) \
/* the invocation of idt_set_intr_gate_desc may clobber */ \
/* the caller-saved registers: */ \
: "eax", "ecx", "edx" \
); \
} while (0)
#define SET_INTERRUPT_HANDLER(num, has_error_code, handler) \
SET_INT_EXC_HANDLER(num, has_error_code, handler, 0, PRIV_LVL_INT)
#define SET_EXCEPTION_HANDLER(num, has_error_code, handler) \
SET_INT_EXC_HANDLER(num, has_error_code, handler, 1, PRIV_LVL_EXC)
/* Disable maskable hardware interrupts */
#define DISABLE_IRQ() \