interrupt or exception, the handling mechanism remains the same: The CPU jumps to a handler routine by retrieving its address from a table. On Intel x86, Both interrupt and exception handlers are stored in the same table.
From Intel x86 manual
To aid in handling exceptions and interrupts, each architecturally defined exception and each interrupt condition
requiring special handling by the processor is assigned a unique identification number, called a vector number. The
processor uses the vector number assigned to an exception or interrupt as an index into the interrupt descriptor
table (IDT). The table provides the entry point to an exception or interrupt handler (see Section 6.10, “Interrupt
Descriptor Table (IDT)”).
The allowable range for vector numbers is 0 to 255. Vector numbers in the range 0 through 31 are reserved by the
Intel 64 and IA-32 architectures for architecture-defined exceptions and interrupts. Not all of the vector numbers
in this range have a currently defined function. The unassigned vector numbers in this range are reserved. Do not
use the reserved vector numbers.
Vector numbers in the range 32 to 255 are designated as user-defined interrupts and are not reserved by the Intel
64 and IA-32 architecture. These interrupts are generally assigned to external I/O devices to enable those devices
to send interrupts to the processor through one of the external hardware interrupt mechanisms (see Section 6.3,
“Sources of Interrupts”).
The IVT is controlled by the OS, so a malicious kernel can force whatever IVT content it wants.
But it's still not relevant, as the kernel can arbitrarily halt execution of any process at any point, and therefore can read the content of all registers whenever it wants them.
Again, if your attack model is a malicious kernel nothing you're doing in user mode is going to protect you. If you're using kernel APIs to install protections against that, you're still dealing with a malicious kernel that can ignore or wrap whatever you do.
If you're trying to mitigate/protect against kernel bugs that's a different threat model, but the same general problems exist only by accident so are less likely to leak useful information.
> But it's still not relevant, as the kernel can arbitrarily halt execution of any process at any point, and therefore can read the content of all registers whenever it wants them.
Not if the kernel can't handle interruptions? The only way for the kernel to "steal control" from the process is through an interruption (software or hardware). If all interrupts are first handled by the secure monitor, which handles saving the registers securely, then it's OK.
Userspace obviously isn't directly impact the "secure monitor", e.g. hypervisor, so instead what you're saying is that we have two operating systems:
1. the os in charge of the hypervisor
2. the os in charge of the virtualised system.
We're saying that we don't trust 2. so we're going to get the kernel from 1. to intercept interrupts. But that means we already have a trustworthy kernel. The one running the hypervisor, so why aren't we just using that?
that is also complete nonsense. I could have a whole other core in the system (nowadays computers have more than one), which can replace the vectors back with the original ones and not the "secured" ones.
In fact, if I was a malicious kernel, I would do precisely that
This whole idea will not work unless your hypervisor is a real complete hypervisor. This attempt at a halfway-hypervisor-lite is doomed to failure for this and many other reasons.
Ginseng modifies the kernel and protects sensitive parts by using the CPU's trusted execution environment[1] which has a higher privilege level than the kernel. From the paper
We now describe Ginseng’s runtime protection against such
accesses. The runtime protection heavily relies on GService,
a passive, app-independent piece of software in the Secure
world. GService ensures the code integrity, data confidentiality
and control-flow integrity (CFI). It does so only for sensitive
functions to minimize overhead. It also modifies the kernel
at three points, when booting, when modifying the kernel
page table, and when handling an exception. Since we do
not trust the OS, the kernel may overwrite the modifications.
However, when any of these modifications is disabled, the
kernel will infinitely trigger data aborts trying to modify readonly
memory, thus ensuring sensitive data are always safe.
I would have look at the source code to find more but the github repository has been deleted.
No amount of modifications in the kernel will make this safe. What if I load a module that fixes my IRQ handlers? Or do they forbid modules? What if I find an exploit in the kernel? Or did they somehow make a 100% exploit-free kernel?
This sort of thing is exactly why TEE exists. It cannot be done half in userspace half in TEE
From what i have understood, Ginseng sets sensitive memory regions to only be accessed by the TEE then unmap these regions from the kernel memory space. If your kernel module try to read/write/map these regions, a memory violation exception is raised and then handled by the TEE.
That literally means that every time you take an interrupt, you also take an extra fault into the hypervisor (since your CPU cannot read the vector, because it has been protected). In that case, forget any ideas of speed. The whole point of hardware assisted virtualization was to prevent that situation. These guys suggest going decades back in terms of performance. No thanks.
From Intel x86 manual
To aid in handling exceptions and interrupts, each architecturally defined exception and each interrupt condition requiring special handling by the processor is assigned a unique identification number, called a vector number. The processor uses the vector number assigned to an exception or interrupt as an index into the interrupt descriptor table (IDT). The table provides the entry point to an exception or interrupt handler (see Section 6.10, “Interrupt Descriptor Table (IDT)”).
The allowable range for vector numbers is 0 to 255. Vector numbers in the range 0 through 31 are reserved by the Intel 64 and IA-32 architectures for architecture-defined exceptions and interrupts. Not all of the vector numbers in this range have a currently defined function. The unassigned vector numbers in this range are reserved. Do not use the reserved vector numbers.
Vector numbers in the range 32 to 255 are designated as user-defined interrupts and are not reserved by the Intel 64 and IA-32 architecture. These interrupts are generally assigned to external I/O devices to enable those devices to send interrupts to the processor through one of the external hardware interrupt mechanisms (see Section 6.3, “Sources of Interrupts”).
https://en.wikipedia.org/wiki/Interrupt_vector_table
https://en.wikipedia.org/wiki/Interrupt_descriptor_table