How to forbid user space and kernel executable pages from becoming writable?

valdis.kletnieks at vt.edu valdis.kletnieks at vt.edu
Sat Jan 5 15:35:39 EST 2019


On Sat, 05 Jan 2019 18:30:01 +0300, Lev Olshvang said:

> Some articles, ex https://shanetully.com/2013/12/writing-a-self-mutating-x86_64-c-program/
> state that mprotect() can change protection of executable section.

Note that appears to be a 5 year old article, and one that tries to be operating system
agnostic.

> As I understanf pte entry has page protection bits set to RO so  mprotect
> should change pte which is loaded to MMU/TLB. Why kernel can not refuse do
> perform this mprotect call(). Whu we do norhave kernel config options to forbid
> user-space mutable code as security feature?

Hint: Read up on how shared libraries get loaded when a process starts up, and
also read up on dlopen() 

Think about how you get those to work if you can't have user-space mutable code
at all.

Are you ready to accept the performance hit of statically linked binaries?  Or the
maintenance nightmare of replacing every relevant binary whenever a bug is
found in a library (use glibc as an example)?

> From the other side,  when  run-time linker or elf_loader loads the
> executable it uses MAP_DENYWRITE which protect executable file from being
> overwritten. 

Now, That's a reasonably cheap protection that closes a fairly large attack surface.
It's a lot harder to exploit a buffer overrun or similar if it's difficult to find or create
a page that's both writable and executable.  Of course, there's always ways to change
a page mapping to allow it - the other half of the protection is that the vast majority
of exploits are severely limited in how many bytes can be injected.

> To add to the confusion, the following quote from the LWN articlle 
> https://lwn.net/Articles/422487/ about CONFIG_DEBUG_SET_MODULE_RONX 
> "Marking the kernel module pages as RO and/or NX is important not only because

> But I am not sure that some variant of pte_clear(), pte_mkexec() can not disable it.

So how would modprobe and (possibly more importantly) rmmod work if it couldn't
be disabled?

> So let me cut to final qiestion:
>
> Suppose I want to cut off dynamic code instrumentation, like ftrace and friends.
> Is it achievable at least at ARM architecture to enforce RO+X at hardware or kernel? 

And here is where we get into a discussion of computer security, and this thing
called a "treat model".  Basically, it boils down to several questions:

1) What sensitive data are you trying to protect?
2) Who/what are you trying to protect it from?
3) Why do you think "I should prevent XYZ" is a reasonable protection?

If you're trying to harden a given feature, it's important to ask whether it's worth the
effort (and include "amount of inconvenience caused" in there).  

And an often overlooked part is "how easy is it to bypass?".  Say you manage to fix
dlopen() and the dynamic loader to totally prevent code outside dlopen() from finding
a writable executable page.  What stops an attacker from simply handing an existing
program a booby-trapped shared library via LD_LIBRARY_PATH?

Similarly, disabling ftrace in the kernel doesn't buy you much if an attacker can
modprobe code that re-enables it, or write a new kernel down into /boot and
waiting for (or causing) a reboot.

So now your security measure needs to include modifying the shared library loader
to ignore environment variables when loading into a setuid binary, and add support
for secure boot and crypto signing of kernel modules and other similar things....



More information about the Kernelnewbies mailing list