142 - NimbusPwn, a CLFS Vulnerability, and DatAFLow
Blogpost by Microsoft that details a few vulnerabilities in the networkd-dispatcher
component in systemd
which can be chained for LPE. When looking at the code flow, they noticed it would register a signal receiver on the system bus, and the handler would receive a state
path followed by some data. If this state path was an existing object, and the data was different from the previously stored data, run_hooks_for_state()
would be invoked. This routine will call all root-owned scripts in that state’s directory, which gets built up as /etc/networkd-daemon/<state>/d
.
Multiple issues exist here. First of all, this path construction is vulnerable to directory traversal, as the state
is not sanitized. Furthermore, symbolic links are followed, both when checking the script files to load them into the list, as well as when executing them with subprocess
. This allows a Time-of-Check Time-of-Use (TOCTOU) bug to open up, as an attacker can traverse out of the protected directory and setup symbolic links. First, they set the link to a valid root-owned script to get the symbolic link loaded into the scripts list, then they change it to a script they control when subprocess goes to execute it.
Memory corruption issue in CLFS. The issue comes down to the parsing of log blocks when loading log files from disk.
Background
The main structure that make up a log block is the base block, which consists of a log block header of metadata followed by record entries. These records have a two-byte signature attached to them, which is referenced when decoding the block for consistency guarantees. Records also have a header which contains an array of offsets to associated contexts (or “containers”) for that record. Contexts have some sensitive fields, namely pContainer
, which is a kernel pointer that points to the parent container class for that context. As such, care has to be taken when loading files from disk - this field must be zero’d, otherwise the user could control a kernel pointer which contains function pointers that get called in RemoveContainer()
later on.
The bug
The problem is the lack of validation on the signature offset that gets read from the block header. It’s possible to set this signature offset to intersect with the context object, specifically that pContainer
field. When decoded, this will give a user the ability to corrupt pContainer
after it had been zero’d, allowing them direct control over the remove
and destroy
function pointers that are called in RemoveContainer()
.
An uninitialized pointer is freed by proving a malformed IOCA file with a size_Y
of zero. What happens normally is that there is an initialization routine that iterates from size_Y
to 0. Initializing the table_mys_rgb
table. Then later during the cleanup routine, in delete_table_mys_rgb_ptr
it’ll call the delete operator which leads to a free on an index into the table. When you provide a file with size_Y
as 0
, the initialization loop never assigns a pointer here. Leading to an arbitrary free.