-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
kernel object validation infrastructure #1276
Conversation
kernel/object_validate.c
Outdated
struct _k_object *ko = _k_object_find(object); | ||
|
||
if (!ko) { | ||
printk("granting access to non-existent kernel object %p\n", |
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.
Leftover debugging printk()?
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 intentional, k_object_grant_access() is a public API and should informatively freak out if a bad object is passed in.
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.
Something we need is a more "regular" way to print warning/error messages so they are easier to monitor for (not the subject of this patch though).
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 should be guarded by CONFIG_PRINTK anyway (other places of the code do this)
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.
that's not correct. printk() expands to a no-op inline function if CONFIG_PRINTK=n.
the reason why some other calls are guarded is because they reference the otype_to_str() function. this function is itself guarded because GCC's -ffunction/data-sections doesn't work the way you would expect with strings, and the strings would end up in the binary anyway even if otype_to_str was never used.
|
||
# Take the strings with the binary information for the pointer values, | ||
# and just turn them into pointers | ||
line = re.sub(r'["].*["]', reformat_str, line) |
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.
More things that could be done here:
- Remove the
len
parameter fromhash()
and_k_object_lookup()
len
can be added somewhere in_k_object_lookup()
as a constant value of 4. This should get rid of some branches inside that function
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.
looking at the generated assembly code, the compiler appears to be smart enough to disregard len entirely since both hash and k_object_lookup are now static functions, called only from _k_object_find() which uses a constant parameter (4) for len,
I don't currently see a need for these two steps unless I'm missing something.
For example on x86, all of the code currently reduces to:
Dump of assembler code for function _k_object_find:
0x00002b84 <+0>: push %ebp
0x00002b85 <+1>: mov %esp,%ebp
0x00002b87 <+3>: push %eax
0x00002b88 <+4>: mov 0x8(%ebp),%ecx
0x00002b8b <+7>: movzbl %cl,%eax
0x00002b8e <+10>: movzbl 0x38c0(%eax),%edx
0x00002b95 <+17>: xor %eax,%eax
0x00002b97 <+19>: cmp $0x1,%edx
0x00002b9a <+22>: ja 0x2bac <_k_object_find+40>
0x00002b9c <+24>: cmp 0x40d000(,%edx,8),%ecx
0x00002ba3 <+31>: jne 0x2bac <_k_object_find+40>
0x00002ba5 <+33>: lea 0x40d000(,%edx,8),%eax
0x00002bac <+40>: leave
0x00002bad <+41>: ret
End of assembler dump.
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've made that comment before looking at the assembly output. My bad. Yeah, those changes are unnecessary.
""" | ||
|
||
|
||
def write_gperf_table(fp, objs, static_begin, static_end): |
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.
Played around with this, and it seems we can reduce the table even further if we do some processing on these pointers. The caveat is that the lookup will be slightly slower; but worth checking out, maybe later:
- Subtract the smaller pointer value from the list of all the pointers. In the obj_validation test for qemu_x86, this made the largest value keep well below what a 16-bit integer can hold
- Look at all the values if they're aligned: if the least significant nibble for all the pointers is
0
, these values can be shifted-right by 2. In the obj_validation test for qemu_x86, all the values were well below what an 8-bit integer can hold
The _k_object structure has to be changed based on these findings, and _k_object_find() has to undo the compression, but this reduces the overhead without too much work
kernel/object_validate.c
Outdated
|
||
|
||
/** | ||
* grant a thread access to a kernek object |
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.
s/kernek/kernel/
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.
fixed
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.
The only doc changes here is to add gperf to the development environment. Will there be some documentation (eventually) that talks about the validation mechanism for user-supplied kernel object pointers?
I don't think it's useful to write documentation for features that are not user-facing. All of this is 'behind the scenes' stuff to support userspace. |
I think a (slightly modified) subset of the proposal text you have in the Jira makes a good introduction of the object verification feature in an overall chapter we should have about memory protection, thread isolation and "user-space" features to be introduced later. |
About the documentation: yeah, I agree. We say Zephyr is built with security in mind and having something like this in the documentation is helpful... specially since this isn't done in a common way. |
Guys, it is way too early in the development of this feature to be writing documentation on how it works internally. Too many things can and will change as the design evolves. |
I was hoping for an overview of requirements driving the implementation of various object verification capabilities (memory protection, thread isolation, etc.) rather than the specific implementation details. |
@dbkinder what you are looking for is documented in an RFC in the comments for the JIRA linked in the PR description. |
That I fully agree with you on, I was not hoping for this documentation to be part of this PR or 1.9, should be part of the overall memory protection feature which is not complete yet for 1.9 |
kernel/object_validate.c
Outdated
*/ | ||
} | ||
|
||
memset(ko->perms, 0, CONFIG_MAX_THREAD_BITS); |
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.
In line 181, we just grant the access permission to the caller thread, but clear all permission bits 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.
yeah this isn't right. looks like i fixed it in some later work but I need to backport to this series.
New patch set differences:
|
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.
A few notes & whines. Seems surprisingly small given what it does!
tests/kernel/gen_isr_table/prj.conf
Outdated
@@ -4,4 +4,4 @@ | |||
# A memory barrier does not help, we need an 'instruction barrier' but | |||
# GCC doesn't support this; we need to tell the compiler not to reorder | |||
# memory accesses to trigger_check around calls to trigger_irq. | |||
CONFIG_COMPILER_OPT="-O0" | |||
CONFIG_DEBUG=y |
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.
Pedantry: I'm not sure I like this any better. CONFIG_DEBUG disables optimization, but I don't know that "debug" necessarily requires that to everyone reading this, nor that it will alwyas mean -O0 in perpetuity.
If we want to fix this, maybe writing some simple assembly to call functions in the right order and/or using a attribute((optimize("O0"))) on the relevant function would be better?
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.
attribute((optimize("O0"))) on the relevant function sounds better, I'll do that.
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.
Even then, will O0
guarantee there won't be any instruction reordering?
enum k_objects { | ||
K_OBJ_ALERT, | ||
K_OBJ_DELAYED_WORK, | ||
K_OBJ_MEM_SLAB, |
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.
Are slabs really needed? Seems like (just like pools) the API should work just fine from user code when referring to memory inside userspace, and the utility of managing a kernel-side allocator from user code that can't touch it seems kinda weird.
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.
in its current implementation, the mem_slab APIs will not work from userspace. they contain wait queues, they have other threads pending on them, etc...redesign would be needed to make them work entirely from userspace. have a look at the code for k_mem_slab_free() there's all kinds of stuff in there that userspace can't do.
also its worth noting that detailed analysis of kernel object APIs is for a later stage in development of this feature, currently I am not changing any of them and just trying to get the infrastructure in place.
return "k_pipe"; | ||
case K_OBJ_SEM: | ||
return "k_sem"; | ||
case K_OBJ_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.
Oh right, k_stack. Remind me again why k_stack and k_fifo are different things (beyond the fact that one takes a void* and the other a u32_t)? Not relevant to review, just something we should consider reviewing and unifying.
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.
yeah we have some issues with these APIs, additionally see https://jira.zephyrproject.org/browse/ZEP-2237
kernel/userspace.c
Outdated
struct _k_object *_k_object_find(void *obj) | ||
{ | ||
/* This is the __weak stub implementation, the version created by | ||
* gperf will replace it in build phase 2 |
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.
FWIW: there's no need for this thing to actually be a function if it will never be called. Linker names are typeless. And no need to put it in a specific section if all you want is for the link to not fail. Just a:
__weak int _k_object_find;
...should be enough, no? Or alternativesly a PROVIDES() directive in the linker script that is #if'd appropriately so it shows up only in the early linkage.
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 give that a try!
kernel/userspace.c
Outdated
return NULL; | ||
} | ||
|
||
#ifdef CONFIG_PRINTK |
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.
No need for this #if. The function won't be linked if unreferenced, and it's not static so it won't generate an error.
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.
yes, there is.
gc-sections is broken with C strings. even if gc-sections strips this function, the strings will still be in the binary :(
upstream fixed it, but i believe the fix landed in the GCC 7.x branch.
kernel/userspace.c
Outdated
return; | ||
} | ||
|
||
memset(ko->perms, 0, CONFIG_MAX_THREAD_BITS); |
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.
Naming mistake or size goof? If that config value is in bits, then this is clobbering 8x too much memory. If it's not, it should be renamed.
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.
oops, naming sucks, will fix this
# FIXME: why does k_sys_work_q not resolve an address in the DWARF | ||
# data??? Every single instance it finds is an extern definition | ||
# but not the actual instance in system_work_q.c | ||
# Is there something weird about how lib-y stuff is linked? |
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.
Yes, actually: the kernel directory is always presented to the link as an already-linked static library, to prevent the case where pulling symbols from one file pulls in static data too and bloats the image. It looks like k_sys_work_q is only ever referenced from inside the kernel, so that may be triggering some variant DWARF output. How does this look in e.g. gdb? Can you see the type there?
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 seem to find it in the DWARF output at all except as an extern reference. It's really baffling. I'm going to file a bug for further investigation.
We use gperf to build up a perfect hashtable of pointer values. The way gperf | ||
does this is to create a table 'wordlist' indexed by a string repreesentation | ||
of a pointer address, and then doing memcmp() on a string passed in for | ||
comparison |
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 super clever, but I just have to whine:
How robust is this vs. changes to the code output in upstream gperf? I doubt they're doing much development these days, but if they do it's not unlikely that this script will break badly. It might be a good idea to put a version check somewhere (maybe right here: is there a "generated by gperv v${WHATEVER}" comment in the file?) to catch that circumstance and force us to revalidate the metacompiler.
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.
good idea, I'll add a version string check and error out. so far this works in gperf 3.0.x and 3.1. agree this will fall over if the generated C code changes too much.
return 0; | ||
} | ||
|
||
void k_object_grant_access(void *object, struct k_thread *thread) |
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.
Design note: would be really nice to have a way to do this statically, given that the objects and threads are static objects. Would be even nicer to have it be implicit, along the lines of "a static thread has access to any static kernel object declared in the same C source file"
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 like to punt on this for now, but that could be a future enhancement once all the implications have been analyzed.
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.
https://jira.zephyrproject.org/browse/ZEP-2585 to look into this
"_k_mem_slab_area", "_k_mem_pool_area", | ||
"_k_sem_area", "_k_mutex_area", "_k_alert_area", | ||
"_k_fifo_area", "_k_lifo_area", "_k_stack_area", | ||
"_k_msgq_area", "_k_mbox_area", "_k_pipe_area", | ||
"net_if", "net_if_event", "net_stack", "net_l2_data", |
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.
Unrelated whitespace muckery snuck into the patch.
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.
doesn't the checkpatch script catch that?
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.
@agross-linaro catch what, exactly?
at any rate, the whitespace changes are in their own patch in the series.
updated patch set:
|
* doesn't support this; we need to tell the compiler not to reorder memory | ||
* accesses to trigger_check around calls to trigger_irq. | ||
*/ | ||
__attribute__((optimize("-O0"))) |
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 idea of using the optimize to somehow give an instruction barrier makes me uneasy. Why not just add a __ISB() for ARM architectures. I am sure other arches have similar solutions.
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 don't want to get too sidetracked by this -- I needed to get rid of the global -O0 in the prj,conf because it messes up the padding size for the generated gperf code in the linker script.
I moved it to the function instead. if we want to further iterate on it, please file a bug.
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.
The only questionable thing I saw was the optimization usage. I'd be more comfortable with a std instruction/data barrier being used.
@agross-linaro the optimize thing was already there, its really not in the scope of this patch, i'm just moving it. can you file a bug if you want it looked at further? |
Fair enough. I'll write one up. |
This is unnecessary. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
This test doesn't work on x86, which doesn't use gen_isr_tables. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
Instead, just set -O0 for the particular function which needs it. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
All system calls made from userspace which involve pointers to kernel objects (including device drivers) will need to have those pointers validated; userspace should never be able to crash the kernel by passing it garbage. The actual validation with _k_object_validate() will be in the system call receiver code, which doesn't exist yet. - CONFIG_USERSPACE introduced. We are somewhat far away from having an end-to-end implementation, but at least need a Kconfig symbol to guard the incoming code with. Formal documentation doesn't exist yet either, but will appear later down the road once the implementation is mostly finalized. - In the memory region for RAM, the data section has been moved last, past bss and noinit. This ensures that inserting generated tables with addresses of kernel objects does not change the addresses of those objects (which would make the table invalid) - The DWARF debug information in the generated ELF binary is parsed to fetch the locations of all kernel objects and pass this to gperf to create a perfect hash table of their memory addresses. - The generated gperf code doesn't know that we are exclusively working with memory addresses and uses memory inefficently. A post-processing script process_gperf.py adjusts the generated code before it is compiled to work with pointer values directly and not strings containing them. - _k_object_init() calls inserted into the init functions for the set of kernel object types we are going to support so far Issue: ZEP-2187 Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
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.
Can't see anything else worth whining about.
Note that (now that I'm thinkinga bout the root issue and not just how to do optimization pragmas) it's probably possible to get gcc to do the right thing with a noop in a asm() statement that declares memory as a clobber. But agreed, that's a separate problem.
This implements kernel object validation as discussed in https://jira.zephyrproject.org/browse/ZEP-2187
The actual validation will take place in forthcoming patches to implement system calls, and only done upon privilege elevation.
Not all kernel object types are connected to this mechanism; forthcoming work will enable the rest of them as well as device drivers.