A strong implementation prevents dynamic code generation. On Linux, this is provided by SELinux. If a process doesn't have the execmem permission, it can't create W|X mappings or transition mappings that were writable to executable, including not being able to do rw -> r -> rx.
Conversation
Replying to
Can you speak more to the cases where preventing the use of execmem is an improvement to security?
Thinking maybe a situation where a vuln doesn't enable full RCE through some *OP variant, but allows enough memory corruption to change the flag to mprotect, and jmp to WX mem?
2
Replying to
It's useful in more than one situation. It remains useful even once the attacker has gained 'arbitrary' code execution. The next step is to escape from the sandbox, and it hurts if they can only use the existing native code by modifying function pointers / return addresses.
1
They can't set up system calls however they want or use arbitrary architectural functionality in ways not implemented by the existing code. Immutable mappings are a standard security feature adding to this too, especially combined with type-based CFI. It reduces attack surface.
1
CFI makes it substantially harder to directly hijack control flow but an attacker doesn't need anything close to arbitrary code execution to inject their own code. Your example is what most exploits really do. They don't program anything complex with ROP/JOP especially with CFI.
1
1
Enforcing it for files prevents classes of vulnerabilities where the attacker tricks the code into executing attacker data. Very useful to enforce that any potentially writable files on storage cannot be executed. Similar to the benefits of a good Content-Security-Policy for JS.
1
1
Consider software with support for loading plugins dynamically. This is often done via native libraries. Tricking these systems into running the attacker's data is one of the most common forms of RCE not involving memory corruption. May or may not involve file write bugs.
1
It's also a good idea to implement the same features for interpreters. It shouldn't be possible to interpret writable/dynamic data by default. Most applications don't need that functionality. Doesn't mean forbidding it outright but at least requiring declaratively opting into it.
1
Part of why these features are useful is coercing application developers into shipping code as part of the signed application package and loading it directly from there. Android 10 forbid running executables from writable app data. Can still ship them in an apk and run them.
1
Can't install an apk without user authorizing it. We close all the remaining holes for native code execution in GrapheneOS for the base system (other than the web browser JIT in isolatedProcess) but we can't be super aggressive with third party apps by default for compatibility.
1
I think the right approach to execmem for Android is forbidding it outside of isolatedProcess. This forces any application using a JIT to use it within the OS provided implementation of the semantic layer of the Chromium sandbox on mobile. It'd also ideally require a property.
So, part of this is an exploit mitigation, but there's also the policy enforcement side in terms of enforcing security best practices. We're not in a position to force security best practices on developers or change how they write their code ourselves... but we influence AOSP.
1
Replying to
Yeah it sounds like forcing all JIT implementations to execute the dynamic code within sandbox is the best trade-off for performance vs security.
Thanks for explaining all of this! I've been practicing lately on pwn challenges, it's good to see what happens w/ real exploits tho.

