Novel modular kernel design
Posted: Sat Sep 25, 2021 10:07 pm
So I've been playing with an idea in my mind for a bit for a novel approach to kernel modules.
Background
As we all know, most modern hardware protection systems present system software with a simple binary option for security. The CPU is in either supervisor or user mode. All code which cannot run in user mode must therefore be given full and complete access to every part of the system, indistinguishable from the kernel.
The theoretical result of this is that your keyboard can intercept your network stack, your video driver can overwrite your file system and any device driver can trash the kernel. This may come about in the real world as either a deliberate attack, or merely as poorly written driver software. The results can range from system instability, violation of privacy and theft or loss of data.
The traditional solution to this problem is simply driver signing. Under Windows, kernel modules cannot be loaded unless signed using a public/private key pair issued by a recognized Certificate Authority. This creates some issues, there is a financial barrier of entry as well as a "political" hazard created by centralizing the power to disallow software from running on any PC. Small companies or individuals may find it difficult to publish kernel modules and those who wish to "hack" drivers to allow advanced users to access officially unsupported functionality of their hardware cannot do so.
The only workaround is to disable signature validation completely, opening the user up to any number of exploits. This also does not protect against badly behaved drivers published by legitimate companies.
Linux offers no such protection, only supporting signatures which verify no changes have been made to driver binaries between compilation and execution. This does nothing to tell you if drivers are safe, only if they are as safe as when they were compiled.
Proposal
The novel solution I propose is JIT compiled kernel modules, written in a restricted bytecode language which provides superior static and runtime guarantees by design and which is compiled to machine code within the kernel on module initialization.
This system would guarantee that no arbitrary code will ever run with supervisor privileges, all machine code executed while in supervisor mode would either reside in the kernel binary itself, or be generated at run time by the kernel.
By designing the language and the API with which it communicated with true kernel code carefully it could be possible to expose all of the underlying functionality of the hardware but without the possibility of expressing an unsafe operation in a valid program. All access to memory and potentially dangerous operations could be corralled through a capability based security system, enforced by the JIT complier.
This system is not without precedent, for many years now graphics drivers have contained compilers which generate machine code from shader and parallel computing languages for uploading and execution on the GPU. These use a restricted language and similarly code which cannot be expressed as operations of the GPU is not recognized as a valid program.
Pros (of a well designed implementation):
- Module memory isolation, including stack protection
- Individual modules may have granular ownership of IO ports and memory mapped IO regions
- Core system can remain stable in the face of a misbehaving module
- No longer need to trust CAs or driver publishers, the kernel guarantees security
- ABI abstraction - two kernels with the same language spec. and API can run identical module code, regardless of ABI differences
- Sensitive operations like changes to page structures can be restricted
Cons inherent to the design:
- Complexity of implementation (take the three most complex things in CS; language design, compiler design and kernel design and combine them)
- Hardware operations must be supported by the language, new ways of interacting with hardware cannot simply be implemented in modules, the kernel must have some awareness
- Changes to language specification must be managed to avoid invalidating existing well behaved drivers
- Performance penalty at system boot - compiling of modules must be performed
- An Inter-Module Communication system must be in place as kernel memory is not implicitly shared
- A compiler is a large and potentially unsafe piece of software, it must be inside the kernel
Risks that must be managed by design:
- Well written modules must generate performant code, security cannot have significant runtime performance penalties
- Language must be sufficiently flexible and expressive to allow known and unknown hardware to be controlled efficiently
- Static analysis is inherently unreliable, so static and runtime security must be implement side by side
That's a high level overview of my idea, I welcome any related ideas, comments, criticisms, particularly if you identify factors I may have overlooked. Maybe I've just gone totally mad
Thanks for taking the time to have a look
Background
As we all know, most modern hardware protection systems present system software with a simple binary option for security. The CPU is in either supervisor or user mode. All code which cannot run in user mode must therefore be given full and complete access to every part of the system, indistinguishable from the kernel.
The theoretical result of this is that your keyboard can intercept your network stack, your video driver can overwrite your file system and any device driver can trash the kernel. This may come about in the real world as either a deliberate attack, or merely as poorly written driver software. The results can range from system instability, violation of privacy and theft or loss of data.
The traditional solution to this problem is simply driver signing. Under Windows, kernel modules cannot be loaded unless signed using a public/private key pair issued by a recognized Certificate Authority. This creates some issues, there is a financial barrier of entry as well as a "political" hazard created by centralizing the power to disallow software from running on any PC. Small companies or individuals may find it difficult to publish kernel modules and those who wish to "hack" drivers to allow advanced users to access officially unsupported functionality of their hardware cannot do so.
The only workaround is to disable signature validation completely, opening the user up to any number of exploits. This also does not protect against badly behaved drivers published by legitimate companies.
Linux offers no such protection, only supporting signatures which verify no changes have been made to driver binaries between compilation and execution. This does nothing to tell you if drivers are safe, only if they are as safe as when they were compiled.
Proposal
The novel solution I propose is JIT compiled kernel modules, written in a restricted bytecode language which provides superior static and runtime guarantees by design and which is compiled to machine code within the kernel on module initialization.
This system would guarantee that no arbitrary code will ever run with supervisor privileges, all machine code executed while in supervisor mode would either reside in the kernel binary itself, or be generated at run time by the kernel.
By designing the language and the API with which it communicated with true kernel code carefully it could be possible to expose all of the underlying functionality of the hardware but without the possibility of expressing an unsafe operation in a valid program. All access to memory and potentially dangerous operations could be corralled through a capability based security system, enforced by the JIT complier.
This system is not without precedent, for many years now graphics drivers have contained compilers which generate machine code from shader and parallel computing languages for uploading and execution on the GPU. These use a restricted language and similarly code which cannot be expressed as operations of the GPU is not recognized as a valid program.
Pros (of a well designed implementation):
- Module memory isolation, including stack protection
- Individual modules may have granular ownership of IO ports and memory mapped IO regions
- Core system can remain stable in the face of a misbehaving module
- No longer need to trust CAs or driver publishers, the kernel guarantees security
- ABI abstraction - two kernels with the same language spec. and API can run identical module code, regardless of ABI differences
- Sensitive operations like changes to page structures can be restricted
Cons inherent to the design:
- Complexity of implementation (take the three most complex things in CS; language design, compiler design and kernel design and combine them)
- Hardware operations must be supported by the language, new ways of interacting with hardware cannot simply be implemented in modules, the kernel must have some awareness
- Changes to language specification must be managed to avoid invalidating existing well behaved drivers
- Performance penalty at system boot - compiling of modules must be performed
- An Inter-Module Communication system must be in place as kernel memory is not implicitly shared
- A compiler is a large and potentially unsafe piece of software, it must be inside the kernel
Risks that must be managed by design:
- Well written modules must generate performant code, security cannot have significant runtime performance penalties
- Language must be sufficiently flexible and expressive to allow known and unknown hardware to be controlled efficiently
- Static analysis is inherently unreliable, so static and runtime security must be implement side by side
That's a high level overview of my idea, I welcome any related ideas, comments, criticisms, particularly if you identify factors I may have overlooked. Maybe I've just gone totally mad
Thanks for taking the time to have a look