Late last night, I finished up a nifty new feature for our ASLR implementation on FreeBSD. This feature allows you to administer on a per-user, per-group, per-jail, and per-binary basis how ASLR is applied. I don't know of any other ASLR implementation that provides this sort of flexibility. This post will show you how to use FreeBSD's filesystem firewall in conjunction with ASLR.
Why a firewall?
Over the past few months, I've been working with another developer named Oliver Pinter. He started work a few months back in porting PaX's ASLR implementation to FreeBSD and I've been enhancing his implementation in behalf of SoldierX. One of Oliver's goals was to port the paxctl
program. When I joined him in this process, he hadn't yet started on the paxctl
port. paxctl
adds an ELF section header to executable files that tells the kernel ELF loader if ASLR should be turned on or off for this file. While he was fixing other bugs and implementing other features, I had started work on porting paxctl
over. I quickly realized using ELF section headers for FreeBSD simply wasn't possible. Since they are optional, section headers are generally placed at the very end of the file. Program headers, the structures required for telling the kernel how to load the binary, are placed near the beginning of the file.
When you execute execve
, FreeBSD's kernel will first check file existence, permissions, etc. If those checks succeed, the kernel will load only the first page of the file into memory. It uses this first page to check for a shebang line (#!/path/to/interpreter) or a binary file format. In our case, we're dealing with ELF files. The execution is passed to the ELF loader at this point.
The ELF loader loops through the program headers and figures out how to load the binary into memory. Oliver and I have implemented ASLR into this stage of the loading process. Remember, section headers are at the end of the file. Since only the first page of the file is mapped into memory, we don't have access to the PaX section headers. We're now stuck in a catch 22. If we want to get at the section headers, we have to load the rest of the file into memory. Loading the file into memory applies ASLR settings. The PaX section header might tell us to turn off ASLR for this binary. We would have to unload the binary and reload it with the ASLR settings from the PaX section applied. Loading a binary twice for every single execution is a waste.
FreeBSD has a really powerful policy-based security framework they call the MAC Framework. The MAC framework rules get applied even before the first page of the binary is loaded. I figured that I could apply ASLR settings to binaries by tying into the MAC framework. One MAC module is the mac_bsdextended module, which provides a firewall-like interface for setting privilege accesses on resources.
The mac_bsdextended module, and its userland interface, ugidfw, fits our end-goal perfectly.
ugidfw
rule changes
NOTE: I'll let you read the manual page for ugidfw that I linked to above for learning how to create generic rules. I don't want to waste time recreating the wheel.
I've modified the filesys object target to store the inode of a file if a file is specified, rather than the mountpoint of the filesystem. So if you run ugidfw add subject uid lattera object filesys /bin/sh mode n
, that only disables access for /bin/sh, not for all executables for the filesystem /bin/sh is in. This behavior is different from before.
I've added an optional paxflags argument to the firewall ruleset. If set, this will enable or disable ASLR for a given binary. If you run ugidfw add subject uid lattera object filesys /bin/sh mode rx paxflags a
, that will disable ASLR for /bin/sh, but still allow me to execute it.
Demo
ugidfw
rule to disable ASLR for that application.