Re: comments on PMP enhancements


Nick Kossifidis
 

Hello John and thanks for your feedback,


On 2/12/20 6:19 AM, John Hauser wrote:
1.
Currently, when a memory access is prevented by physical memory
attributes (PMAs) or by PMP, an access fault trap is taken. The
proposal defines a new "security exception" and requires that some
blocked memory accesses take a security exception trap instead of the
usual access fault. The document says

A new exception will help distinguish the exceptions we get with
the current PMP spec when the access type doesn’t match R/W/X flags
on the matching rule, from the exception we get when violating the
access controls of the new mechanisms in place.

I request that some explanation be provided for how this distinction
is expected be helpful; i.e., why "denied" accesses need a different
exception code than "enforced" accesses. If the reason is supposed
to be obvious, it was not so to me. Just saying it "will help
distinguish" isn't sufficient. Why distinguish? What good does it do?

For me, it seems obvious that these should all be access faults.
The new mechanism (when MML is set) introduces a barrier between S/U
mode and M mode, We want to be able to distinguish between an access
fault due to crossing that barrier, from other access faults. In other
words if M mode gets an access fault on its own memory we'll get an
access fault as in the current spec, if it gets an access fault on
memory that's marked for S/U use (see truth table) we'll get a security
exception. The reason is that we may want to handle this differently in
sw and it also helps in debugging.

2.
As Jonathan Behrens has already noted, some systems depend on being
able to set mstatus.MXR = 1 temporarily to read S/U-executable
instructions, for emulation purposes. The proposal should be modified
to say that any S/U-mode-only PMP region that grants execute permission
to S/U modes (bit X is set), implicitly grants read permission to
M mode when MXR = 1.
mstatus.MXR is not related to PMP, it's related to virtual memory
permissions and is outside PMP's scope, the scenario you mention
involves using mstatus.MPRV to access the region with S/U privileges
(and virtual memory in place). That's still possible because the
access in this case happens as S/U mode (not as M mode) and so the
S/U mode PMP rules apply.

3.
I'm concerned that the use of the reserved combination W = 1, R = 0
for shared memory regions may be incompatible with a future use for
this encoding in page tables. For example, one possible allocation of
the reserved W/R encoding in page tables could be:

X W R

0 1 0 uncached read-only page
1 1 0 uncached read-write page

If so defined, the same _uncached_ property might also be sensible
for PMP entries, yet we would no longer be able to encode it the same
way, because we have allocated the reserved W/R combination for shared
memory regions instead.

To be clear, I know of no current plans to use the reserved W/R
encoding for an _uncached_ property this way, or for any other purpose.
I am merely giving an example of the sort of inconsistency that could
arise because of our choices today.

I understand that the reserved W/R encoding ended up in use because
there is opposition to touching the two reserved bits that still exist
in a PMP configuration byte, and there are few options for encoding
everything in just the four bits we already have: L, X, W, R. My own
choice would be to go ahead and consume a reserved bit to avoid the
risk of creating a mess of the encoding down the road.
I see your point (that was also our initial approach as you mentioned)
but I don't see why we should be compatible with PTEs. The write-only
combination may be used for all sorts of different reasons on PTEs, it
doesn't have to be compatible with PMP rules. Also we are dealing with a
different resource (physical memory vs virtual), with different address
mapping schemes (we don't have pages here) and different needs (a shared
page for example is a different thing than a shared PMP region, it can
be executable e.g. across processes on U mode).

4.
The biggest concern I have with the proposal is that the effort to
fully lock down the executable regions for M mode, while correct for
maximizing security in principle, doesn't leave enough flexibility
for some systems. Tariq Kurd has given an example of a system that,
during booting, progressively expands the regions accessible to M mode,
which the current proposal prohibits. I'd like to give a couple other
examples that are more specifically about execute permission, but
still revolve around the need to edit M-mode-only PMP entries even when
enhanced security is enabled.

Consider a complex operating system, running in M mode, that supports
loadable "kernel modules", which are components that can be brought
into memory or evicted in response to the varying needs of user-level
tasks. With the current PMP proposal, when MML = 1, this M-mode OS
cannot dynamically adjust the regions of memory that are executable for
loadable modules. Instead, the OS authors must make a choice: either
pre-allocate the maximal amount of memory that could ever be needed for
loaded kernel modules, possibly wasting memory, or entirely forgo using
the security enhancement. If they choose the latter because memory
really is scarce, how has security been improved?

Or consider the situation where there is more than one independent
stage of boot-time software that could benefit from enhanced PMP
security. U-Boot, for example, is a complex piece of software in its
own right. If a bootloader like U-Boot is used in an M/U-only system,
it's easy to imagine that enhanced security could help guard against
attacks. But with the current proposal, U-Boot cannot set MML = 1,
because it would have to configure the executable regions not only
for itself but also for the operating system it subsequently loads,
something outside its knowledge or authority. Because all current and
future executable regions must be known and configured before MML can
be enabled, a U-Boot-like loader must run with MML = 0. Again, this
seems like a loss for security in this instance.

I have no argument with anyone who needs all the restrictions the
current proposal provides; we should be able to offer that. But if we
require always that all executable regions be locked down in advance,
we're not providing sufficient flexibility for all systems at all
times, instead sometimes forcing an awkward "maximal security or none"
choice.

(To help his particular system, Mr. Kurd has proposed a DPL bit, Delay
PMP Lock. However, this bit would conflict with one of the intended
purposes of PMP locking as I understand it, which is to permit earlier
initialization software to protect some regions from access by later,
less trusted, M-mode code. By itself, the DPL solution is too simple
because, in general, we need to be able to set some PMP entries that
stay locked, while at the same time other entries remain unlocked for
editing but are nonetheless enforced.)
The issue you mention is there regardless of the MML bit, in the current
spec the only way to restrict M mode is also by using locked rules. I
don't see how we can enforce memory isolation across M-mode processes /
boot stages by allowing the rules that enforce that isolation to be
removed by M-mode.

It's not the same thing as with S mode or U mode where one needs to
tamper with page tables to be able to bypass memory isolation, to do so
one has to completely compromise the core of the OS, in which case there
are bigger security issues to worry about. In this case removing a PMP
rule is one instruction away, the attacker doesn't need to tamper with
any data structures nor take control over the OS. I don't see what kind
of security benefits we get e.g. across different boot stages on M-mode
where the next stage can just flush the PMP ruleset in 16 instructions
or less. I can only see this as a debug feature to be able to catch
invalid accesses, or as in Tariq's threat model, detect a glitch attack.
Also note that in Tariq's case they lock down all memory by default and
gradualy allow regions as the boot process moves on, something also not
compatible with current PMP spec.

Such a discussion however is more relevant to secure boot /
anti-tampering than a proposal for preventing memory access / execution
from M mode like this one. Locking down mtvec for example is a far more
important feature when it comes to secure boot.

As for the examples you brought up, MML is meant to be set after
system's initialization. Initialization may as well include loading
kernel modules or unpacking the kernel etc (especially when that happens
on M mode). It's up to the developers / administrators to decide when to
switch it on. As mentioned on the proposal:

"The idea with this restriction is that after the Firmware or the OS
running on M-mode is initialized, no new code regions are expected to be
added since nothing else is expected to run on M-mode (everything else
will run on S/U mode). Since we want to limit the attack surface of the
system as much as possible, it makes sense to disallow any new code
regions which may include malicious code to be executed on M-mode."

Unless for some reasons people need to load/unload kernel modules all
the time, I don't see how MML prevents them to use them during boot and
set MML afterwards, modules are usually loaded early on init, before
loading daemons and allowing user sessions.

To bridge the gap between "maximal security" and "none", I've developed
a modified proposal with four security levels rather than just the
current two (MML = 0 or 1). Unfortunately, I see no good way to
provide all the needed flexibility without also taking one of the two
reserved PMP configuration bits. While having four security levels may
sound more complex, actually it's not, because the extra configuration
bit allows some encoding complexity to be reduced at the same time.
The only significant cost is the allocation of the reserved bit. I'll
be sending my modified proposal in a follow-up message.
I'll check it out and reply there. Have in mind that this proposal is
meant to solve a specific problem related to a specific threat model,
it's not about changing PMP in general to do all sorts of stuff. Before
we have something else I'd appreciate a threat model and a problem
description.

P.S. U-boot usually knows the executable regions of the kernel, first
because it needs to jump there, second because it's the one that put the
kernel there (and/or unpacked it). Unless we are talking about a kernel
that self-extracts or relocates itself, u-boot can set MML before
jumping to the kernel if needed (and there are no modules to load).

Also loadable modules are not considered a secure approach (unless you
force them to be valid signed etc), nor they save resources (to the
contrary you need more resources to support loading them, even more for
verifying their integrity / authenticity). On embedded systems such as
those without S mode, where Linux will run on M mode, the vendor most
probably knows the hardware in there and will use a static kernel image
instead of supporting loading modules, to save up resources. The only
reason I see for using loadable modules there would be some licensing
stuff that prevents them from being built-in the kernel image.


Regards,
Nick

Join tech-privileged@lists.riscv.org to automatically receive all group messages.