[go: up one dir, main page]

|
|
Log in / Subscribe / Register

A canary for timer-expiration functions

A canary for timer-expiration functions

Posted Aug 17, 2017 10:04 UTC (Thu) by epa (subscriber, #39769)
Parent article: A canary for timer-expiration functions

An alternative approach would be to eliminate function pointers in kernel code. Surely any function pointer points to one of a small set of possible implementations. It could be replaced with an enum and a trivial jump function which maps enum values to functions. Then the most an attacker can do, if they manage to overwrite the enum value, is to cause control to jump to a different one of the functions in the set.

An uglier approach, but perhaps easier to transition to, is to make a big static array (in a piece of generated C code) containing the address of every function in the kernel. Then you replace function pointers with an index into this array. Now an attacker can jump to an arbitrary kernel function, but not to arbitrary addresses. A small refinement is to only store the needed functions in the array -- kernel functions which aren't currently referenced by function pointers don't need to appear.

Would it be possible to use sparse or another static analyser to automate converting function pointer code to this style?

In user space, could it have benefits too?


to post comments

A canary for timer-expiration functions

Posted Aug 17, 2017 10:30 UTC (Thu) by Sesse (subscriber, #53779) [Link] (6 responses)

> An alternative approach would be to eliminate function pointers in kernel code. Surely any function pointer points to one of a small set of possible implementations. It could be replaced with an enum and a trivial jump function which maps enum values to functions.

So you're going to forbid out-of-tree modules?

A canary for timer-expiration functions

Posted Aug 20, 2017 9:26 UTC (Sun) by jzbiciak (guest, #5246) [Link] (4 responses)

You could, in principle, allow modules to register their entry points as well. Perhaps structure it as a tuple, with <module,entry> pairs, with only the module part being determined dynamically at module-load time.

Indeed, we implemented something like this in a small secure-kernel + secure-module API that I helped develop for an embedded processor. Each module defined its set of entry points, and received a load-time assigned module ID. The module ID did require an extra level of indirection to find the entry point table for the module, so that was one downside.

A canary for timer-expiration functions

Posted Aug 20, 2017 9:35 UTC (Sun) by Sesse (subscriber, #53779) [Link] (3 responses)

At this point, you need a runtime table of function pointers, as opposed to fixed and locked-down switch/case code… what did you gain?

A canary for timer-expiration functions

Posted Aug 20, 2017 9:47 UTC (Sun) by jzbiciak (guest, #5246) [Link] (2 responses)

It's still fixed tables of addresses, converted to small, easily-validated numbers. I can validate that the module ID and index are both in-range with a single comparison each. The tables themselves can live in read-only memory as well.

This limits the attacker to only being able to select among the fixed list of entry points determined at compile time, while still permitting loadable modules.

Now, if someone could modprobe evil.ko, then yeah, they can subvert this. But if they can do that, there are much shorter, more obvious paths to running their code.

A canary for timer-expiration functions

Posted Aug 20, 2017 9:53 UTC (Sun) by Sesse (subscriber, #53779) [Link] (1 responses)

No, my question is; how can you make sure a good out-of-tree module can register a timer in such a scheme? That won't work if the tables are locked and live in read-only memory.

A canary for timer-expiration functions

Posted Aug 20, 2017 10:16 UTC (Sun) by jzbiciak (guest, #5246) [Link]

I'm saying that each module provides its own table in an rodata type of section that can be mapped read-only. The only dynamic run-time bit, then, is a module ID, which indexes a writable table of pointers to read-only tables of function pointers. All of the tables of function pointers at least are read-only.

Something like this very rough sketch:

/* This part lives in the kernel */
void *const kernel_fxn_ptrs[] = { ... };  /* this is read-only */
void *const *module_fxn_table_ptrs[MAX_MODULES];  /* this is read-write */

/* This part lives in each module */
void *const module_fxn_ptrs[] = { ... }; /* this is read-only */

Now, since module_fxn_table_ptrs only needs modification at module load time, you could imagine spending the cost of establishing a writeable mapping to it during module load, and then discarding that writeable mapping once the module's table is registered. That means you'd only have read-only tables for these function pointers under normal circumstances, with a small window during module load where a writeable mapping exists.

Of course, that idea assumes we don't have a full MMU to work with (as was the case on that embedded processor). We do have a full MMU at our disposal.

So here's an even better idea that keeps every table read-only the entire time. Put the function pointer tables in their own dedicated multiple-of-the-page sized section. Let's call it .rodata.fxn_ptr. The kernel maps its own .rodata.fxn_ptr read-only at some virtual address determined at runtime (ASLR). As modules get loaded, map their .rodata.fxn_ptr pages directly after the kernel's, also read-only. The module ID now just becomes "how many pages to skip to get to the start of my module's table." That also removes the indirection. When a module gets unloaded, unmap its table.

Now, you do have a new resource to manage. But, if you force everyone into no more than, say, 8K, that still gives you 1024 entry points on a 64-bit machine.

A canary for timer-expiration functions

Posted Aug 21, 2017 15:45 UTC (Mon) by josh (subscriber, #17465) [Link]

Yes, for a secure kernel.

There are already config options in the kernel that you might have to change if you want to build out-of-tree modules, such as not pruning away unused symbols that no in-tree module uses. This would simply be another such option.

A canary for timer-expiration functions

Posted Aug 17, 2017 11:00 UTC (Thu) by NAR (subscriber, #1313) [Link] (1 responses)

the most an attacker can do, if they manage to overwrite the enum value, is to cause control to jump to a different one of the functions in the set.

I'm afraid it takes only one exploit writer to develop a technique using only the available functions to defeat this mechanism...

A canary for timer-expiration functions

Posted Aug 18, 2017 7:30 UTC (Fri) by epa (subscriber, #39769) [Link]

I don't agree - or at least I don't agree that it implies the mechanism is worthless. Like many security measures it is not an impenetrable barrier but adds to defence in depth. It is a lot more awkward to arrange the stack so that a given kernel function will do what you want, than it is to jump to an unrestricted choice of kernel addresses. Many fewer 'gadgets' (as I understand the jargon) will be available.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds