Protection Model

The Ring Cycle has 4 levels of protection which map directly to the hardware privilege levels like so:

ringcyclecircles.jpg

  • Ring 0 -- Fully trusted kernel code. Drivers built into the kernel at compile time go here. Has full access to all system resources.
  • Ring 1 -- Partially trusted driver code. Drivers in ring 1 may have control over the processor interrupt flag. Generally more trusted than ring 2 drivers since they can clobber their memory.
  • Ring 2 -- Partially trusted driver code. Drivers in ring 2 may be restricted to non-interrupt based devices, and cannot clobber ring 1 memory.
  • Ring 3 -- Untrusted user code. Cannot touch any memory for the other rings.

When loading a module, you may specify as a load paramter which ring you want the driver loaded into. Once loaded, the only way to switch rings is to unload and then reload the module.

The address space layout in the Ring Cycle looks like this:

ringcycleaddress.gif

The key point is that code at ring X may access the memory of all code running in ring Y where Y>=X, but never when Y<X. If a driver in say, ring 1 tries to dereference a ring 0 pointer like 0xc1234567, it will take a general protection fault since the address is beyond the segment limit. In the GP fault handler the kernel will notice this came from the driver thread, and first kill the offending thread, and then attempt to cleanly unload the module (by scheduling work to be done later). The fault handler would also return the code -ENODEV to the user thread. When the kernel attempts to cleanly unload the module, it will try to call the exit routine. That method could obviously fault as well. If that happens the module is forceably unloaded.

What data structures/memory are drivers trusted with? They may access the following:

  • All memory in rings below them (including user memory for the process that called them)
  • Kernel data structures passed as parameters to a driver call
  • I/O ports that they request access to
  • Their own task_struct
  • Any function in the Driver API (DAPI) made available by the kernel

Note that in the current driver model, drivers may access ANY and ALL memory, ports, data structures, etc.

An example scenario that shows this new protection model in action is as follows: Let's say you have a driver that has a bug where a malicious user could pass in a bad buffer which would cause the driver to write one byte anywhere in memory that the user wanted. In the current model, a malicious user could easily crash the system by say, overwriting parts of the IDT, or worse, clobber their own task_struct and increase their privileges. Needless to say, badness=10000.

In the Ring Cycle, this would not happen since when the driver tried to write to the IDT or the user's task_struct, that address would be outside it's segment and a general protection fault would be triggered. The kernel would then kill the thread, return an error code to the user and nothing bad would happen. The driver may also get unloaded. In any case, the system would stay up.