/*
* kextaudit_test.m
* kext_tools
*
* Copyright 2018 Apple Inc. All rights reserved.
*
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <Foundation/Foundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <RemoteXPC/RemoteXPC.h>
#include <RemoteServiceDiscovery/RemoteServiceDiscovery.h>
#include <MultiverseSupport/kext_audit_plugin_common.h>
#include "../kext_tools_util.h"
#include "../kextaudit.h"
#include "../security.h"
#include "unit_test.h"
#define kKextAuditTestRemoteServiceName "com.apple.internal.xpc.remote.kext_audit"
xpc_remote_connection_t kextaudit_conn;
static bool test_kextaudit_find_remote_service(void)
{
bool result = true;
remote_device_t device = NULL;
remote_service_t service = NULL;
dispatch_queue_t reply_queue = NULL;
device = remote_device_copy_unique_of_type(REMOTE_DEVICE_TYPE_BRIDGE_COPROC);
if (device == NULL) {
TEST_LOG("No COPROC found, trying external.");
device = remote_device_copy_unique_of_type(REMOTE_DEVICE_TYPE_BRIDGE_COPROC_EXTERNAL);
}
TEST_REQUIRE("Find bridge", device, result, false, error);
service = remote_device_copy_service(device, kKextAuditTestRemoteServiceName);
TEST_REQUIRE("Get kextaudit service", service, result, false, error);
reply_queue = dispatch_queue_create("kextaudit_test", NULL);
TEST_REQUIRE("Allocate dispatch queue", reply_queue, result, false, error);
kextaudit_conn = xpc_remote_connection_create_with_remote_service(service, reply_queue, 0);
TEST_REQUIRE("Create remote connection", kextaudit_conn, result, false, error);
/* Empty block, since we call send_message_with_reply */
xpc_remote_connection_set_event_handler(kextaudit_conn, ^(xpc_object_t event) {});
xpc_remote_connection_activate(kextaudit_conn);
error:
return result;
}
static bool test_kextaudit_multiversed_roundtrip(void)
{
Boolean result;
__block bool gotIt = false;
NSURL *kextURL;
OSKextRef theKext;
CFStringRef cdHashCFString;
const char *cdHash = NULL;
AuthOptions_t testOptions = {
.performFilesystemValidation = true,
.performSignatureValidation = true,
.requireSecureLocation = true,
.respectSystemPolicy = false,
.allowNetwork = false,
.isCacheLoad = false,
};
const char *cmdString[] = { "cmd" };
xpc_object_t cmd = xpc_string_create("sendloadinfo");
xpc_object_t msg = xpc_dictionary_create(cmdString, &cmd, 1);
xpc_object_t reply, kexts;
char rawCDHash[kKALNKCDHashSize] = { };
char* ptrrawCDHash = rawCDHash;
size_t cdHashSize;
CFIndex cdHashBufLen;
char *cdHashBuffer;
kextURL = [NSURL fileURLWithPath:@"/System/Library/Extensions/Dont Steal Mac OS X.kext"];
theKext = OSKextCreate(NULL, (__bridge CFURLRef)kextURL);
TEST_REQUIRE("Able to create reference to DSMOS kext", theKext != NULL, result, false, error);
result = authenticateKext(theKext, &testOptions);
TEST_REQUIRE("Kext authenticates without network as runtime load", result, result, false, error);
result = KextAuditLoadCallback(theKext);
TEST_REQUIRE("Success sending load notification", result, result, false, error);
copySigningInfo((__bridge CFURLRef)kextURL, &cdHashCFString, NULL, NULL, NULL);
TEST_REQUIRE("Get cdHash from security subsystem", cdHashCFString, result, false, error);
cdHashBufLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cdHashCFString) + 1, kCFStringEncodingUTF8);
cdHashBuffer = malloc(cdHashBufLen);
TEST_REQUIRE("Able to malloc cdHashBuffer", cdHashBuffer != NULL, result, false, error);
GET_CSTRING_PTR(cdHashCFString, cdHash, cdHashBuffer, cdHashBufLen);
TEST_REQUIRE("Get cdHash string pointer", cdHash, result, false, error);
cdHashSize = strlen(cdHash);
result = createRawBytesFromHexString(&rawCDHash[0], kKALNKCDHashSize, cdHash, cdHashSize);
TEST_REQUIRE("Success creading raw bytes", result, result, false, error);
reply = xpc_remote_connection_send_message_with_reply_sync(kextaudit_conn, msg);
TEST_REQUIRE("Successful reply from multiversed", xpc_get_type(reply) != XPC_TYPE_ERROR, result, false, error);
kexts = xpc_dictionary_get_value(reply, "kexts");
TEST_REQUIRE("Retreive kext array from reply", kexts, result, false, error);
xpc_array_apply(kexts, (xpc_array_applier_t)^(size_t index, xpc_object_t kext) {
xpc_object_t thisHashObj = xpc_dictionary_get_value(kext, "cdhash");
char remote_cdHash[kKALNKCDHashSize];
size_t copied = xpc_data_get_bytes(thisHashObj, remote_cdHash, 0, kKALNKCDHashSize);
if (copied != cdHashSize/2) {
return true;
}
int i;
for (i = 0; i < copied; i++) {
if(ptrrawCDHash[i] != remote_cdHash[i]) {
return TRUE;
}
}
gotIt = true;
return false;
});
TEST_REQUIRE("Found cdHash on the other end", gotIt, result, false, error);
error:
return result;
}
#if DEBUG
extern bool KextAuditInitialize(void);
extern bool KextAuditTestBridge(uint8_t *kaln,
struct KextAuditBridgeResponse *kabr);
static bool send_kernel_load_notification(const char *hash_str, const char *csrActiveConfig_str, const char *type_tag_str)
{
if (!KextAuditInitialize()) {
fprintf(stderr, "Error initializing KextAudit connection\n");
return false;
}
struct KextAuditLoadNotificationEFI kaln = {};
struct KextAuditBridgeResponse kabr = {};
if (!createRawBytesFromHexString((char *)&kaln.cdHash[0], sizeof(kaln.cdHash),
hash_str, strlen(hash_str))) {
fprintf(stderr, "Invalid kernel hash: '%s'\n", hash_str);
return false;
}
kaln.loadType = kKALTImmutableKernel;
kabr.status = kKALNStatusTest;
if (strlen(type_tag_str) != 4) {
fprintf(stderr, "Invalid type_tag. Must be 4 characters, e.g. mkrn");
return false;
}
if (strlen(csrActiveConfig_str) != 4) {
fprintf(stderr, "Invalid csrActiveConfig. Must be 4 characters, e.g. mkrn");
return false;
}
uint32_t type_tag = 0;
type_tag = (uint32_t)(type_tag_str[0]) << 24;
type_tag |= (uint32_t)(type_tag_str[1]) << 16;
type_tag |= (uint32_t)(type_tag_str[2]) << 8;
type_tag |= (uint32_t)(type_tag_str[3]);
kaln.typeTag = type_tag;
uint32_t csrActiveConfig = 0;
csrActiveConfig = (uint32_t)(csrActiveConfig_str[0]) << 24;
csrActiveConfig |= (uint32_t)(csrActiveConfig_str[1]) << 16;
csrActiveConfig |= (uint32_t)(csrActiveConfig_str[2]) << 8;
csrActiveConfig |= (uint32_t)(csrActiveConfig_str[3]);
kaln.csrActiveConfig = csrActiveConfig;
kaln.csrActiveConfig_status = csrActiveConfigSet;
fprintf(stdout, "Sending test load, {type: if (!KextAuditTestBridge((uint8_t*)&kaln, &kabr)) {
fprintf(stderr, "KextAuditTestBridge failed. Check logs...\n");
return false;
}
fprintf(stdout, "Sent kernel load notifiction. response: return true;
}
static bool send_bridgeos_ping(void)
{
if (!KextAuditInitialize()) {
fprintf(stderr, "Error initializing KextAudit connection\n");
return false;
}
struct KextAuditLoadNotificationKext kaln = {};
struct KextAuditBridgeResponse kabr = {};
kaln.loadType = kKALTMax;
kabr.status = kKALNStatusTest;
if (!KextAuditTestBridge((uint8_t )&kaln, &kabr)) {
fprintf(stderr, "KextAuditTestBridge failed. Check logs...\n");
return false;
}
fprintf(stdout, "Sent bridge ping. response: return true;
}
#endif /* DEBUG */
int main(int argc, char **argv)
{
bool result = true;
/* check for root privileges */
if (geteuid() != 0){
fprintf(stderr, " return 1;
}
#if DEBUG
if (argc > 3) {
result = send_kernel_load_notification(argv[1], argv[2], argv[3]);
return !result;
} else if (argc == 2 && !strcmp(argv[1], "ping")) {
result = send_bridgeos_ping();
return !result;
} else if (argc > 1) {
fprintf(stderr, "invalid invocation of return -1;
}
#endif /* DEBUG */
result = test_kextaudit_find_remote_service();
TEST_RESULT("Find the kextaudit multiversed plugin remote service", result);
if (!result) {
goto error;
}
result = test_kextaudit_multiversed_roundtrip();
TEST_RESULT("Roundtrip message between KextAudit and multiversed", result);
if (!result) {
goto error;
}
error:
return !result;
}