A Kernel Race, SuDump, and a Chrome Garbage Collector Bug
There is an out-of-bounds access that occures by causing Squirel to lookup a method in the array of class fields.
When accessing class fields and methods a lookup is performed on the attributes name to determine its index within one of two internal arrays (one for fields, one for methods). The most-significant byte of this index is a bit-field which is used to indicate which of the arrays the index corresponds to:
0x02000000 for fields,
0x01000000 for methods.
The problem is that the number of classes is not limited to prevent the index from properly expanding into that bit and fooling the
_isfield() macro that checks the field bit. An attacker could craft a class with more than 0x020000XX methods, but only 1 field. The
_isfield() check will pass, and the index of 0x000000XX (the bit-field will be masked out) will be looked up on the field array going out of bounds).
To exploit this the author crafts a string that immitates the expected
SQObjectPtr structure that the field array usually contains, and can prepare the heap to place it next to the
_defaultvalues array and tricks Squirrel into returning an array that points to
0x0 and contains
0xffffffffffffffff elements giving access to the entire address space.
Bit of a logic bug/abuse resulting in the ability to write files with semi-controlled content in any directory regardless of privileges. Under normal circumstances when a suid binary crashes, it will be considered non-dumpable, more generally speaking, when a process has a difference between its real and effective group or user ids it will not be dumped.
This little bit of logic can be abused when you consider how some suid binaries work, such as
sudo which is a
suid binary, and will start off as root and not be dumpable, it will adopt a specified user/group (usually root/uid=0,gid=0) and then execute another program. So what ends up happening is that while sudo will not be dumpable, after it changes its uid/gid then executes a new (non-suid) binary that new binary will be dumpable, and the dump will be written with what ever privileges the process has (root). This attack can work with other
suid binaries also.
Once you’ve gained this write-anywhere primitive to exploit it Aleph Security take advantage of a few things:
- First, they must be able to cause the final program to crash, the post covers a couple methods to do this, neither rely on any special privileges.
- Then they need to gain some control over the data being written by the core dump. This is accomplished through environment variables, specifically they found the
AUTHORITYenvironment will be inherited and passed into the target binary by sudo.
logrotatehas a permissive configuration parser that will ignore any binary data in the file
logrotatecan run arbitrary commands through the
firstactionscript in the configuration
- Sets the
XAUTHORITYenvironment variable to a configuration including the code you want to execute as the
- Changed the current working directory to
- Dumps are written to the current directory unless specified otherwise in system config
- This is so a core dump is written at all
- Executes sudo with their target binary as the target
sudo example does depend on being in
/etc/sudoers and being able to execute atleast one binary as root. This is definitely a bit of an ask for the exploitable scenario but not impossible. It is not entirely unheard of for systems to be configured where certain users have access to only a particular binary with sudo, but it does require some extra configuration beyond the default.
Ignoring plenty of nuance,
tiocspgrp (TTY IOCTL Set Process Group) would grab the wrong lock. Pseudoterminals (pty) have a master and a slave device, both of which are controlled by userland and can have ioctls called on them. This is the
tty pointer for the function, the
real_tty is passed in also, which will point to the master tty if it is part of a pty.
The problem is that while the function modifies the
real_tty it grabs the
tty’s spinlock. So by invoking the IOCTL on both ends you can have two threads in the critical area at one time resulting in skewed reference counters. There are two corruption cases, the original
pgrp will have its reference counter decremented twice and which ever thread wins the race (sets
pgrp first) will have its
pgrp overwritten without a corresponding decrement of the reference count.
A logic bug in the Chrome garbage collector was discovered which could cause use-after-free. The garbage collector (GC) is a monolithic and complex component of the browser, and some background knowledge is needed to appreciate the issue.
First, browser garbage collectors utilize a “mark and sweep” technique, where it will iterate over all global/in-scope objects, look for references to them, and “mark” them if any references are found. At the end, any unmarked objects get garbage collected and freed. Where it gets interesting is, the
WeakMap data-structure can be used to store weak references, where objects can be stored in the map without preventing it from being garbage collected.
These cases have to be handled carefully. These
WeakMap entries are known as
ephemeron pairs. The browser must ensure that any map entries where the value is “reachable” from a strong reference is accounted for and won’t be collected. To do this,
WeakMap’s contain a hash table. When the GC reaches this hash table, any key-value pairs that aren’t already marked are queued in a worklist to be processed again at the end of the GC cycle. At the end of the cycle, it goes back through the
ephemeron pairs and tries to check for references. It does this in three stages:
- Processes the
current_ephemeronslist and pushes entries assumed to be unreachable into the
- Tries to discover new ephemerons and pushes them in the
- Processes newly discovered ephemerons
The problem is, the newly discovered ephemerons in the second stage might have strong references to previously assumed unreachable ephemerons that were already processed. This case is never accounted for and they never get marked, meaning the collector can free them even though there’s a strong reference remaining. One way this can happen is if you have two key-value pairs such as (
v1) and (
v2 = k1. If the (
v1) pair is visited first where both are unmarked, the second pair will get visited, and
k1 will get marked via the value of the second pair, but
v1 from the first pair will remain unmarked and thus will be collected.
This provides a targeted use-after-free primitive where the attacker can free any JS object they choose, as they can store any reference they want to retrieve post-GC. In this case, they targeted a large JSArray, which gets allocated in the “large object space”. By overlapping a large array of objects and a large array of doubles,
leakobj primitives can be obtained, which then leads to easy arbitrary read/write.