x86: implement improved double-fault handler

We now create a special IA hardware task for handling
double faults. This has a known good stack so that if
the kernel tries to push stack data onto an unmapped page,
we don't triple-fault and reset the system.

Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
This commit is contained in:
Andrew Boie 2017-07-14 16:35:17 -07:00 committed by Anas Nashif
commit bc666ae7f7
8 changed files with 124 additions and 15 deletions

View file

@ -82,6 +82,8 @@ config X86_STACK_PROTECTION
bool
default n
depends on X86_MMU
select SET_GDT
select GDT_DYNAMIC
prompt "MMU-based stack overflow protection"
help
This option leverages the MMU to cause a system fatal error if the

View file

@ -88,14 +88,14 @@ SECTION_FUNC(TEXT_START, __start)
#ifdef CONFIG_SET_GDT
/* If we set our own GDT, update the segment registers as well.
*/
movw $0x10, %ax /* data segment selector (entry = 3) */
movw $DATA_SEG, %ax /* data segment selector (entry = 3) */
movw %ax, %ds /* set DS */
movw %ax, %es /* set ES */
movw %ax, %fs /* set FS */
movw %ax, %gs /* set GS */
movw %ax, %ss /* set SS */
ljmp $0x08, $__csSet /* set CS = 0x08 */
ljmp $CODE_SEG, $__csSet /* set CS = 0x08 */
__csSet:
#endif /* CONFIG_SET_GDT */
@ -262,7 +262,10 @@ __csSet:
#endif /* CONFIG_X86_MMU */
#ifdef CONFIG_X86_STACK_PROTECTION
mov $MAIN_TSS, %ax
ltr %ax
#endif
/* Jump to C portion of kernel initialization and never return */
jmp _Cstart

View file

@ -130,7 +130,8 @@ extern void (*_kernel_oops_handler)(void);
NANO_CPU_INT_REGISTER(_kernel_oops_handler, NANO_SOFT_IRQ,
CONFIG_X86_KERNEL_OOPS_VECTOR / 16,
CONFIG_X86_KERNEL_OOPS_VECTOR, 0);
#else
#endif
/*
* Define a default ESF for use with _NanoFatalErrorHandler() in the event
* the caller does not have a NANO_ESF to pass
@ -149,7 +150,6 @@ const NANO_ESF _default_esf = {
0xdeaddead, /* CS */
0xdeaddead, /* EFLAGS */
};
#endif /* CONFIG_X86_KERNEL_OOPS */
#if CONFIG_EXCEPTION_DEBUG
@ -192,7 +192,9 @@ EXC_FUNC_NOCODE(IV_OVERFLOW);
EXC_FUNC_NOCODE(IV_BOUND_RANGE);
EXC_FUNC_NOCODE(IV_INVALID_OPCODE);
EXC_FUNC_NOCODE(IV_DEVICE_NOT_AVAILABLE);
EXC_FUNC_CODE(IV_DOUBLE_FAULT);
#ifndef CONFIG_X86_STACK_PROTECTION
EXC_FUNC_NOCODE(IV_DOUBLE_FAULT);
#endif
EXC_FUNC_CODE(IV_INVALID_TSS);
EXC_FUNC_CODE(IV_SEGMENT_NOT_PRESENT);
EXC_FUNC_CODE(IV_STACK_FAULT);
@ -230,3 +232,43 @@ FUNC_NORETURN void page_fault_handler(const NANO_ESF *pEsf)
_EXCEPTION_CONNECT_CODE(page_fault_handler, IV_PAGE_FAULT);
#endif /* CONFIG_EXCEPTION_DEBUG */
#ifdef CONFIG_X86_STACK_PROTECTION
void df_handler(void)
{
printk("DOUBLE FAULT! Likely kernel stack overflow\n");
/* TODO: do forensic analysis to figure out the faulting context
* since this exception type pushes undefined CS:EIP information
*/
/* Double faults are unrecoverable, time to freak out */
_NanoFatalErrorHandler(_NANO_ERR_KERNEL_PANIC, &_default_esf);
}
_GENERIC_SECTION(.tss)
struct task_state_segment _main_tss = {
.ss0 = DATA_SEG
};
char __noinit df_stack[512];
/* Special TSS for handling double-faults with a known good stack */
_GENERIC_SECTION(.tss)
struct task_state_segment _df_tss = {
.esp = (u32_t)(df_stack + sizeof(df_stack)),
.cs = CODE_SEG,
.ds = DATA_SEG,
.es = DATA_SEG,
.fs = DATA_SEG,
.gs = DATA_SEG,
.ss = DATA_SEG,
.eip = (u32_t)df_handler,
.cr3 = (u32_t)&__mmu_tables_start
};
/* Configure a task gate descriptor in the IDT for the double fault
* exception
*/
_X86_IDT_TSS_REGISTER(DF_TSS, -1, -1, IV_DOUBLE_FAULT, 0);
#endif /* CONFIG_X86_STACK_PROTECTION */

View file

@ -7,12 +7,6 @@
#include<kernel.h>
#include<mmustructs.h>
/* Linker variable. It needed to access the start of the Page directory */
extern u32_t __mmu_tables_start;
#define X86_MMU_PD ((struct x86_mmu_page_directory *)\
(void *)&__mmu_tables_start)
/* Ref to _x86_mmu_buffer_validate documentation for details */
#define USER_PERM_BIT_POS ((u32_t)0x1)
#define GET_RW_PERM(flags) (flags & BUFF_WRITEABLE)

View file

@ -39,6 +39,13 @@
#include <misc/dlist.h>
#endif
/* GDT layout */
#define CODE_SEG 0x08
#define DATA_SEG 0x10
#define MAIN_TSS 0x18
#define DF_TSS 0x20
/* increase to 16 bytes (or more?) to support SSE/SSE2 instructions? */
#define STACK_ALIGN_SIZE 4

View file

@ -576,10 +576,18 @@ extern FUNC_NORETURN void _SysFatalErrorHandler(unsigned int reason,
[reason] "i" (reason_p)); \
CODE_UNREACHABLE; \
} while (0)
#else
#endif
/** Dummy ESF for fatal errors that would otherwise not have an ESF */
extern const NANO_ESF _default_esf;
#endif /* CONFIG_X86_KERNEL_OOPS */
#ifdef CONFIG_X86_MMU
/* Linker variable. It needed to access the start of the Page directory */
extern u32_t __mmu_tables_start;
#define X86_MMU_PD ((struct x86_mmu_page_directory *)\
(void *)&__mmu_tables_start)
#endif /* CONFIG_X86_MMU */
#endif /* !_ASMLANGUAGE */

View file

@ -152,13 +152,18 @@ SECTIONS
#endif
#ifdef CONFIG_GDT_DYNAMIC
KEEP(*(.tss))
. = ALIGN(8);
_gdt = .;
#ifdef LINKER_PASS2
KEEP(*(gdt_ram_data))
#else /* LINKER_PASS2 */
#ifdef CONFIG_X86_STACK_PROTECTION
#define GDT_NUM_ENTRIES 5
#else /* CONFIG_X86_STACK_PROTECTION */
#define GDT_NUM_ENTRIES 3
#endif /* CONFIG_X86_STACK_PROTECTION */
. += GDT_NUM_ENTRIES * 8;
#endif /* LINKER_PASS2 */

View file

@ -11,6 +11,16 @@ import os
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection
def debug(text):
if not args.verbose:
return
sys.stdout.write(os.path.basename(sys.argv[0]) + ": " + text + "\n")
def error(text):
sys.stderr.write(os.path.basename(sys.argv[0]) + ": " + text + "\n")
sys.exit(1)
gdt_pd_fmt = "<HIH"
FLAGS_GRAN = 1 << 7 # page granularity
@ -65,6 +75,23 @@ def create_code_data_entry(base, limit, dpl, flags, access):
access, flags, base_hi)
def create_tss_entry(base, limit, dpl):
present = 1
base_lo, base_mid, base_hi, limit_lo, limit_hi, = chop_base_limit(base,
limit)
type_code = 0x9 # non-busy 32-bit TSS descriptor
gran = 0
flags = (gran << 7) | limit_hi
type_byte = ((present << 7) | (dpl << 5) | type_code)
return struct.pack(gdt_ent_fmt, limit_lo, base_lo, base_mid,
type_byte, flags, base_hi)
def get_symbols(obj):
for section in obj.iter_sections():
if isinstance(section, SymbolTableSection):
@ -95,6 +122,16 @@ def main():
kernel = ELFFile(fp)
syms = get_symbols(kernel)
# NOTE: use-cases are extremely limited; we always have a basic flat
# code/data segments. If we are doing stack protection, we are going to
# have two TSS to manage the main task and the special task for double
# fault exception handling
if "CONFIG_X86_STACK_PROTECTION" in syms:
stackprot = True
num_entries = 5
else:
stackprot = False
num_entries = 3
gdt_base = syms["_gdt"]
@ -112,6 +149,17 @@ def main():
fp.write(create_code_data_entry(0, 0xFFFFF, 0,
FLAGS_GRAN, ACCESS_RW))
if stackprot:
main_tss = syms["_main_tss"]
df_tss = syms["_df_tss"]
# Selector 0x18: main TSS
fp.write(create_tss_entry(main_tss, 0x67, 0))
# Selector 0x20: double-fault TSS
fp.write(create_tss_entry(df_tss, 0x67, 0))
if __name__ == "__main__":
main()