94 - 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
AUTHORITY
environment will be inherited and passed into the target binary by sudo. logrotate
has a permissive configuration parser that will ignore any binary data in the filelogrotate
can run arbitrary commands through thefirstaction
script in the configuration
- Sets the
XAUTHORITY
environment variable to a configuration including the code you want to execute as thefirstaction
. - Changed the current working directory to
/etc/logrotate.d
- Dumps are written to the current directory unless specified otherwise in system config
- Sets
RLIMIT_CPU
to 0 - Sets
RLIMIT_CORE
to infinity- This is so a core dump is written at all
- Executes sudo with their target binary as the target
This 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.
Background
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 pair
s and tries to check for references. It does this in three stages:
- Processes the
current_ephemerons
list and pushes entries assumed to be unreachable into thenext_ephemerons
list - Tries to discover new ephemerons and pushes them in the
next_ephemerons
list - Processes newly discovered ephemerons
The Bug
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 (k1
, v1
) and (k2
, v2
) where v2 = k1
. If the (k1
, 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, fakeobj
and leakobj
primitives can be obtained, which then leads to easy arbitrary read/write.