Show Notes

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 file
  • logrotate can run arbitrary commands through the firstaction script in the configuration
  1. Sets the XAUTHORITY environment variable to a configuration including the code you want to execute as the firstaction.
  2. Changed the current working directory to /etc/logrotate.d
    • Dumps are written to the current directory unless specified otherwise in system config
  3. Sets RLIMIT_CPU to 0
  4. Sets RLIMIT_CORE to infinity
    • This is so a core dump is written at all
  5. 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 pairs and tries to check for references. It does this in three stages:

  1. Processes the current_ephemerons list and pushes entries assumed to be unreachable into the next_ephemerons list
  2. Tries to discover new ephemerons and pushes them in the next_ephemerons list
  3. 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.