-
Notifications
You must be signed in to change notification settings - Fork 7.3k
x86: MMU-based stack overflow protection #810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
a454f7e
to
6202ce5
Compare
Need to fix a few checkpatch nits. |
6202ce5
to
2bb20a5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't comment on the x86 intricacies, but there are a few nits.
scripts/gen_idt.py
Outdated
sys.stderr.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") | ||
sys.exit(1) | ||
|
||
irq_gate_desc_format = "<HHBBH" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but it would be nice to have a C-struct-as-comment here like it's done elsewhere in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a horrendously complex data structure but I can put a pointer to where it is described in the Intel CPU manuals, like I did in segmentation.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: Similar comment, and a repeat of earlier one: this string isn't a "constant", it's code to be interpreted at runtime. It should go where it's used as a literal, not here.
scripts/gen_idt.py
Outdated
|
||
symdict = {} | ||
for sym in symbols.iter_symbols(): | ||
symdict[sym.name] = sym.entry.st_value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor, but you can use a dict comprehension: return {sym.name: sym.entry.st_value for sym in symbols.iter_symbols()}
include/arch/x86/arch.h
Outdated
* @param vec Interrupt vector | ||
* @param dpl Descriptor privilege level | ||
*/ | ||
#define NANO_CPU_TSS_REGISTER(tss, irq, priority, vec, dpl) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For how long we need to keep NANO_*
identifiers around?
scripts/gen_gdt.py
Outdated
gdt_ent_fmt = "<HHBBBB" | ||
|
||
def create_code_data_entry(base, limit, gran, ex, dpl, rw, dc): | ||
present = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stray semicolon
scripts/gen_gdt.py
Outdated
limit) | ||
|
||
# Cast all these to 1 or 0 | ||
if rw: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of many boolean values, consider a bitmask. It's more readable at the call site (reducing possibility of swapping values by mistake):
create_code_data_entry(base, limit, FLAGS_EX | FLAGS_RW)
Otherwise, these statements are a no-op, as only 0
or 1
are passed from main()
.
2bb20a5
to
e1ee4b1
Compare
Fixed checkpatch issues. |
e1ee4b1
to
c34da58
Compare
Rebased to fix some conflicts introduced by Adithya's MMU patch that went in. |
2a92ced
to
8ce687f
Compare
8ce687f
to
ec6982a
Compare
Rebased to fix a merge conflict. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpickery
arch/x86/core/thread.c
Outdated
@@ -204,6 +204,9 @@ void _new_thread(struct k_thread *thread, char *pStackMem, size_t stackSize, | |||
|
|||
unsigned long *pInitialThread; | |||
|
|||
#if _STACK_GUARD_SIZE | |||
pStackMem += _STACK_GUARD_SIZE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the #if. If _STACK_GUARD_SIZE is zero, then the += will optimize out.
@@ -162,7 +162,8 @@ void main(void) | |||
} | |||
|
|||
TC_PRINT("test alt thread 3: initiate kernel panic\n"); | |||
k_thread_create(&alt_thread, alt_stack, sizeof(alt_stack), | |||
k_thread_create(&alt_thread, alt_stack, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Digression: Seems like this is going to be a persistent error with the new API. If the stack_size argument to k_thread_create() must always be the result of a K_THREAD_STACK_SIZEOF() macro, it probably shouldn't be a size_t named "stack_size" and documented as "stack size in bytes".
A little work could allow the compiler to typecheck this by having a "k_static_stack" type or somesuch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is outside the scope of this patch but I'll keep it in mind
.ds = DATA_SEG, | ||
.es = DATA_SEG, | ||
.fs = DATA_SEG, | ||
.gs = DATA_SEG, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use E, F and G segments, and in fact other OSes tend to use these for things like thread-local storage (or process IDs, etc...). May not be a great idea to set a precedent that these can be used for legitimate memory accesses. Though I guess this is only ever touched for fatal error handlers here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we set these values the same way when we initially boot up in crt0.S and I would rather have these as fixed values matching the initial boot state than uninitialized values which might not be a valid segment selector at all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since designated initializers are being used here, omitted fields will be initialized to 0, so there's no risk of having uninitialized values in this struct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm just not seeing a problem here, and this is the same configuration as is done in crt0.S, really don't want to change this unless we change both, which is outside the scope of this patch
return (base_lo, base_mid, base_hi, limit_lo, limit_hi) | ||
|
||
|
||
gdt_ent_fmt = "<HHBBBB" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: seems like putting this here is actually less readable than writing the literal into the call to struct.pack() below. Sure, it's a string literal, but semantically this is "code", not a constant you want to separate for maintainability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd rather not, for one thing I use this formatter in two different functions
scripts/gen_idt.py
Outdated
sys.stderr.write(os.path.basename(sys.argv[0]) + ": " + text + "\n") | ||
sys.exit(1) | ||
|
||
irq_gate_desc_format = "<HHBBH" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style: Similar comment, and a repeat of earlier one: this string isn't a "constant", it's code to be interpreted at runtime. It should go where it's used as a literal, not here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for the most part. No need to go around fixing the style issues, but the routine to find the symbol table might not work everywhere (given issues to gen_offset.py
).
include/arch/x86/arch.h
Outdated
@@ -106,8 +113,25 @@ typedef struct s_isrList { | |||
#define NANO_CPU_INT_REGISTER(r, n, p, v, d) \ | |||
static ISR_LIST __attribute__((section(".intList"))) \ | |||
__attribute__((used)) MK_ISR_NAME(r) = \ | |||
{&r, n, p, v, d} | |||
{&r, n, p, v, d, 0} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: With more than 3 items in a struct, I prefer to use designated initializers. Also, while you're at it, please add parenthesis around the macro arguments.
scripts/gen_gdt.py
Outdated
|
||
|
||
def get_symbols(elf): | ||
symbols = elf.get_section_by_name(".symtab") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this work with all combinations of Python/pyelftools? Maybe iterating over the section table looking for some SymbolTableSection instance is going to be more robust. The gen_offsets.py script has a function you can copy. Since it raises an exception if the symbol table is absent from the ELF image, the next if can be junked as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'll make this change, but there is no guarantee that other uses of elftools in this or other scripts I am working on will work properlty if people are not using the 0.24 or later version of the library. all of our scripts are run under Python 3, there is no need for worrying about Python 2 suppport
scripts/gen_gdt.py
Outdated
for sym in symbols.iter_symbols(): | ||
symdict[sym.name] = sym.entry.st_value | ||
|
||
return symdict |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick, but creating symdict
and returning it looks nicer with a dict comprehension: return {sym.name: sym.entry.st_value for sym in symbols.iter_symbols()}
.
scripts/gen_idt.py
Outdated
if not handler and not tss: | ||
error("entry does not specify either handler or tss") | ||
|
||
if (handler): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style nitpick: unneeded parenthesis. There are other instances of this in this file.
This enables the MMU-based stack protection feature, which will cause a fatal error if a thread overflows its stack in kernel mode, at a nontrivial cost in memory (4K per thread). Signed-off-by: Andrew Boie <[email protected]>
scripts/gen_idt.py
Outdated
error("Vector %d specified, but size of IDT is only %d vectors" % | ||
(vec, max_vec)) | ||
|
||
if (vectors[vec] != None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stray parenthesis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is what happens when you code switch between C and Python many times in a single day
This is one less host tool we have to compile for every build, and makes the build tools more portable across host OSes. The code is also much simpler to maintain. Issue: ZEP-2063 Signed-off-by: Andrew Boie <[email protected]>
ec6982a
to
ddc18e1
Compare
This has one use-case: configuring the double-fault #DF exception handler to do an IA task switch to a special IA task with a known good stack, such that we can dump diagnostic information and then panic. Will be used for stack overflow detection in kernel mode, as otherwise the CPU will triple-fault and reset. Signed-off-by: Andrew Boie <[email protected]>
We will need this for stack memory protection scenarios where a writable GDT with Task State Segment descriptors will be used. The addresses of the TSS segments cannot be put in the GDT via preprocessor magic due to architecture requirments that the address be split up into different fields in the segment descriptor. Signed-off-by: Andrew Boie <[email protected]>
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 <[email protected]>
Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Andrew Boie <[email protected]>
Each member of the array may need to have a padding size added such that the base address of each array element corresponds to the desired stack alignment. This would mean that sizeof(some array element) would return a larger size than what was originally provided. This won't cause problems at runtime since the space is really there, but for users who are only enabling this padding for debug features, they may be surprised when their stacks are effectively smaller than when this was enabled. Signed-off-by: Andrew Boie <[email protected]>
Subsequent patches will set this guard page as unmapped, triggering a page fault on access. If this is due to stack overflow, a double fault will be triggered, which we are now capable of handling with a switch to a know good stack. Signed-off-by: Andrew Boie <[email protected]>
This will trigger a page fault if the guard area is written to. Since the exception itself will try to write to the memory, a double fault will be triggered and we will do an IA task switch to the df_tss and panic. Signed-off-by: Andrew Boie <[email protected]>
Signed-off-by: Andrew Boie <[email protected]>
Show that this mechanism can detect stack overflows with the guard page. We only do it once since are are in an alternate IA HW task after it happens. Signed-off-by: Andrew Boie <[email protected]>
ddc18e1
to
a61b27d
Compare
Client update was expecting the properties to be part of the resource object, not resource.properties as it should be. Fixes zephyrproject-rtos#810. Signed-off-by: James Prestwood <[email protected]>
We configure a guard page immediately before the thread stack area which is non-present, triggering a CPU exception.
x86 does not do an automatic stack switch when handling exceptions at the same privilege level, and the initial page fault will cause a double fault since there is nowhere for the exception stack data to be pushed, so we introduce an alternate IA hardware task with a known good stack. the double fault exception now will do task gate instead of an interrupt gate.
we also convert gen_idt to Python, and introduce a new script for creating GDT since the GDT data structure is too complex to be specified in C code at build time (certain pointer fields need to be split up, and this can't be done with symbols, only literal values)