Missing mitigations
Missing security features
This is a small arbitrary list of cools and/or interesting mitigations that OpenBSD doesn’t have.
Compiler-powered hardening
Before August 2020, OpenBSD was using (and maintaining) GCC 4.2.1, the latest version released under GPLv2, released the 13th of July 2008.
In 2017, Robert Nagy switched the default compiler to clang, for i386 and amd64. Since OpenBSD 6.6, released in October 2019, it’s also the default one for octeon and powerpc.
Unfortunately, starting with LLVM 9.0, released the [19 September 2019]](https://releases.llvm.org/), LLVM is now released under Apache License v2.0 with LLVM Exceptions, a license that the OpenBSD people consider incompatible with their philosophy (while it looks like it actually is compatible).
In August 2020, they apparently decided that having a modern compiler was more important than licensing details and upgraded to LLVM 10.
Constification of function points in kernel-land
PAX_CONSTIFY_PLUGIN
is a gcc plugin by Emese (ephox)
Revfy and pipacs,
marking all structures containing only function pointers as
read-only. It has been part of grsecurity since August 2011.
This prevents trivial control-flow highjacking by function pointer-overwriting.
Integer overflows in critical functions' parameters
size_overflow
is an other plugin by ephox, written in 2011.
In ephox’s own
words:
This plugin recomputes expressions of function arguments marked by a
size_overflow
attribute with double integer precision (DImode
/TImode
for 32/64 bit integer types). The recomputed argument is checked againstTYPE_MAX
and an event is logged on overflow and the triggering process is killed.
It was added to grsecurity in March 2013, and was thoroughly documented for the occasion.
Automatic initialization of variables
In August 2011, pipacs published his PAX_MEMORY_STRUCTLEAK
gcc plugin,
which zero initialize some local variables that are going to be copied to userland.
In June 2014, Florian Weimer from Red Hat posted a patch on gcc’s mailing list to initialize all local variables to zero.
In August 2016, Daniel Micay, developer of GrapheneOS (previously CopperheadOS) implemented a patch for clang to provide zero-initialization.
JF Bastien, compiler engineer at Apple, suggested in November 2018 a patch for clang, to add a flag to automatically initialize variables, either with 0, or with a specific pattern, with only a couple of percent performance impact.
Also in November 2018, Microsoft
added the InitAll
flag to its compiler, providing a similar feature, with only noise-level
performance impact. Joseph Bialek, security engineer in the Microsoft Security
Response Center’s Vulnerability & Mitigations team
added:
Between 2017 and mid 2018, this feature would have killed 49 MSRC cases that involved uninitialized struct data leaking across a trust boundary. It would have also mitigated a number of bugs involving uninitialized struct data being used directly. cc @j00ru @TinySecEx
Control Flow Integrity for forward edges
Control-flow integrity usually has two components, one to enforce forward-edges, and the other for backward-edges. In OpenBSD, backward edges are kinda covered by RETGUARD, by forward ones aren’t.
- PaX has RAP, privately since at least 2012, publically since 2015.
- Android uses clang’s CFI for some userland applications since 2017, cross-DSO since 2018, and in kernel-land since 2019.
- HardenedBSD has non-cross-DSO clang CFI since 2017.
- Windows has CFG in Windows 8.1 and Windows 10 since 2015, and even if they removed it from the scope of its bugbounty in 2018, it’s coming back as XFG, taking inspiration for PaX’s RAP.
While most of them have been bypassed in several ways, they’re getting better and better over time, and might force attackers to use data-only attacks instead of ROP and its friends, even in the case of arbitrary reads, arbitrary writes, at arbitrary times.
Kernel stack protections
In December 2002, pipacs published RANDKSTACK, randomizing each task’s kernel stack pointer before returning from a system call to userland.
In 2011, Jon Oberheide and Dan Rosenberg presented STACKJACKING - your way to
grsec/pax
bypass
at Infiltrate, along with a
blogpost,
prompting pipacs and spender to move thread_info
out of the kernel stack,
port RANDKSTACK to amd64, improve PAX_USERCOPY
, …
But RANDKSTACK
could make things worse, as highlighted in the followup
presentation at SummerCon 11, Stackjacking and Other Kernel
Nonsense,
as well as in the accompanying blogpost.
In response, based on Jon Oberheide and Dan Rosenberg’s recommendations, pipacs
implemented PAX_MEMORY_STACKLEAK
, to erase completely the kernel stack after
each syscall.
In August 2017, Theo de Raadt
added
a small random offset to each process' kernel stack. This can be bypassed
with the kstack self-discovery
technique, as explained in the STACKJACKING
slides, if you have a kernel stack memory disclosure, which aren’t
uncommon. By the way, this isn’t the only trick from this slide deck that can
be applied to OpenBSD ;)
In October 2017, Alexander Popov tried to
upstream
a custom version of PAX_MEMORY_STACKLEAK
in Linux. It was
merged
in September 2018, after a lot of
back-and-forth.
SEGVGUARD/GRKERNSEC_KERN_LOCKOUT/GRKERNSEC_BRUTE
segvguard was created by Rafal (Nergal) Wojtczuk around 2001, and discussed in his The advanced return-into-lib(c) exploits: PaX case study Phrack article. It’s a daemon notified by the kernel every time a process crashes with a SIGSEGV, able to temporarily disable the execution of the crashing programs, thwarting bruteforce-based ASLR bypass.
In 2002, spender implemented GRKERNSEC_BRUTE
:
When a child of a forking daemon is killed by PaX or crashes due to an illegal instruction or other suspicious signal, the parent process will be delayed 30 seconds upon every subsequent fork until the administrator is able to assess the situation and restart the daemon.
In November 2006, Elad Efrat added PaX' Segvguard in NetBSD, along with a nice public explanation of its threat model. Unfortunately, as of 2019, it’s still not enabled by default.
In 2011, it was enhanced to be more severe for suid/sgid binaries:
In the suid/sgid case, the attempt is logged, the user has all their existing instances of the suid/sgid binary terminated and will be unable to execute any suid/sgid binaries for 15 minutes.
Also in 2011, GRKERNSEC_KERN_LOCKOUT
was implemented: if an
UDEREF
/USERCOPY
/KERNEXEC
violation is triggered, or an OOPS due to bad
memory access, if the user is root the system will panic, otherwise
the attempt are logged, all the processes from the users are killed,
and the user won’t be allowed to create new process until the system is
restarted.
HardenedBSD had its own port of PaX' Segvguard since at least 2014, and NetBSD since 2006!!!
This is a bit similar to the idea of trustlevels in OpenBSD, except that it’s: way more granular, proactive and completely automatic. It can also be compared to password bruteforce prevention: increasing delays between tentative, followed by a lockout.
It’s also the only way to make sure that ASLR can’t be bruteforced, even partially. It also acts as a deterrent for unreliable exploits: if the exploit doesn’t work reliably in a single shot, there is no way to launch it again, escalade privileges, and clean the traces.
GRKERNSEC_TPE
This grsecurity’s option provide a way to create a group whose members won’t be able to execute any files that are not in root-owned directories writeable only by root. Of course, with interpreters, it’s still possible for a user to execute arbitrary code, but porting a privilege escalation exploit into Python isn’t fun™.
This mitigation can be emulated by chrooting users, and bind mounting everything
except /bin
and /usr/bin
with noexec
, and while it’s not something as
fundamental as ASLR or NX, I still wanted to mention it.
GUI isolation
Modern linux distributions are now using wayland instead of Xorg, making it possible to isolate GUI programs. An other approach would be to do something similar to Qubes: use an hypervisor.
OpenBSD is currently using an old fork of Xorg called Xenocara.