kextaudit_darwintest.m [plain text]
/*
* 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 <darwintest.h>
#include "../../security.h"
#include "../../kextaudit.h"
#include "../../kext_tools_util.h"
#define kKextAuditTestRemoteServiceName "com.apple.internal.xpc.remote.kext_audit"
#define kKextAuditTestKext @"/System/Library/Extensions/Dont Steal Mac OS X.kext"
/* Adding some LASIO definitions directly here */
#define LASIOBridgeStateIsSecure (1 << 0)
#define LASIOBridgeStateNoKernelLoaded (1 << 1)
#define LASIOBridgeStateOffendingKernelLoaded (1 << 2)
#define LASIOBridgeStateOffendingKextLoaded (1 << 3)
#define LASIOBridgeLoadErrorRecorded (1 << 4)
#define LASIOBridgeStateBlacklistMissing (1 << 5)
xpc_remote_connection_t kextaudit_conn;
static void kextaudit_connect_remote_service(void)
{
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) {
T_LOG("No COPROC found, trying external.");
device = remote_device_copy_unique_of_type(REMOTE_DEVICE_TYPE_BRIDGE_COPROC_EXTERNAL);
}
if (!device) {
T_SKIP("Did not find coproc device -- might not be a Gibraltar device");
}
service = remote_device_copy_service(device, kKextAuditTestRemoteServiceName);
T_QUIET; T_ASSERT_NOTNULL(service, "Get kextaudit service on the bridge");
reply_queue = dispatch_queue_create("kextaudit_test", NULL);
T_QUIET; T_ASSERT_NOTNULL(reply_queue, "Create a kextaudit_test dispatch queue");
kextaudit_conn = xpc_remote_connection_create_with_remote_service(service, reply_queue, 0);
T_QUIET; T_ASSERT_NOTNULL(kextaudit_conn, "Create a remote connection for kextaudit service");
/* 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);
T_PASS("Found and connected to the kextaudit multiversed plugin remote service");
}
static void kextaudit_audit_new_kext_load(NSString *kextPath) {
NSURL *kextURL = nil;
OSKextRef theKext = nil;
bool result = false;
AuthOptions_t testOptions = {
.performFilesystemValidation = true,
.performSignatureValidation = true,
.requireSecureLocation = true,
.respectSystemPolicy = false,
.allowNetwork = false,
.isCacheLoad = false,
};
kextURL = [NSURL fileURLWithPath:kextPath];
theKext = OSKextCreate(NULL, (__bridge CFURLRef)kextURL);
T_QUIET; T_ASSERT_NOTNULL(theKext, "Create reference to
result = authenticateKext(theKext, &testOptions);
T_QUIET; T_ASSERT_TRUE(result, "Authenticate kext without network as runtime load");
result = KextAuditLoadCallback(theKext);
T_ASSERT_TRUE(result, "Sending load audit notification. If this returns successfully,"
"the kext at - }
static void get_cdHash_for_kext(NSString *kextPath, char **cdHash) {
NSURL *kextURL;
CFStringRef cdHashCFString;
char *cdHashBuffer = NULL;
kextURL = [NSURL fileURLWithPath:kextPath];
copySigningInfo((__bridge CFURLRef)kextURL, &cdHashCFString, NULL, NULL, NULL);
T_QUIET; T_ASSERT_NOTNULL(cdHashCFString, "Get cdHash of
size_t cdHashBufLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cdHashCFString) + 1, kCFStringEncodingUTF8);
T_QUIET; T_ASSERT_NOTNULL(cdHashBuffer = malloc(cdHashBufLen), "mallocing for cdHash buffer");
const char *cdHash_cpy = NULL;
GET_CSTRING_PTR(cdHashCFString, cdHash_cpy, cdHashBuffer, cdHashBufLen);
T_QUIET; T_ASSERT_NOTNULL(cdHash_cpy, "Get cdHash string pointer");
T_QUIET; T_ASSERT_NOTNULL(strncpy(*cdHash, cdHash_cpy, kKALNCDHashSize), "Copy out cdHash string of kext");
free(cdHashBuffer);
CFRelease(cdHashCFString);
}
static void kextaudit_verify_kext_with_remote_service(NSString *kextPath) {
__block bool gotIt = false;
char *cdHash = malloc(kKALNCDHashSize);
char rawCDHash[kKALNCDHashSize] = {};
char *ptrrawCDHash= rawCDHash;
get_cdHash_for_kext(kextPath, (char **)&cdHash);
size_t cdHashSize = strlen(cdHash);
T_QUIET; T_ASSERT_TRUE(createRawBytesFromHexString(&rawCDHash[0], kKALNKCDHashSize, cdHash, cdHashSize), "create string of raw bytes");
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;
reply = xpc_remote_connection_send_message_with_reply_sync(kextaudit_conn, msg);
T_QUIET; T_ASSERT_NE(xpc_get_type(reply), XPC_TYPE_ERROR, "Get a successful reply from multiversed");
kexts = xpc_dictionary_get_value(reply, "kexts");
T_QUIET; T_ASSERT_TRUE(kexts, "Retrieve kext array from reply");
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[kKALNCDHashSize];
size_t copied = xpc_data_get_bytes(thisHashObj, remote_cdHash, 0, kKALNCDHashSize);
if (copied != cdHashSize / 2) {
T_QUIET; T_EXPECT_EQ_ULONG(copied, cdHashSize / 2, "Expect cdHash of kexts to be half of kKALNCDHashSize");
return true;
}
if (! memcmp((void *)ptrrawCDHash, (void*)remote_cdHash, copied)) {
gotIt = true;
return false;
}
return true;
});
T_ASSERT_TRUE(gotIt, "Verify that multiversed KextAudit plugin knows about the kext at [kextPath UTF8String]);
free((void *)cdHash);
}
static void kextaudit_verify_kext_with_lasio(NSString *kextPath) {
__block bool gotIt = false;
const char *cdHash = malloc(kKALNCDHashSize);
char rawCDHash[kKALNCDHashSize] = {};
char *ptrrawCDHash= rawCDHash;
get_cdHash_for_kext(kextPath, (char **)&cdHash);
size_t cdHashSize = strlen(cdHash);
T_QUIET; T_ASSERT_TRUE(createRawBytesFromHexString(&rawCDHash[0], kKALNKCDHashSize, cdHash, cdHashSize), "create string of raw bytes");
const char *cmdString[] = { "cmd" };
xpc_object_t cmd = xpc_string_create("sendlasioinfo");
xpc_object_t msg = xpc_dictionary_create(cmdString, &cmd, 1);
xpc_object_t reply, loadedKexts;
reply = xpc_remote_connection_send_message_with_reply_sync(kextaudit_conn, msg);
T_QUIET; T_ASSERT_NE(xpc_get_type(reply), XPC_TYPE_ERROR, "Get multiversed reply");
loadedKexts = xpc_dictionary_get_value(reply, "loadedKextHashes");
T_QUIET; T_ASSERT_TRUE(loadedKexts, "Retrieve loaded kext hashes from reply");
xpc_array_apply(loadedKexts, (xpc_array_applier_t)^(size_t index, xpc_object_t loadedKext) {
uint8_t remote_cdHash[kKALNCDHashSize];
size_t copied = xpc_data_get_bytes(loadedKext, remote_cdHash, 0, kKALNCDHashSize);
if (copied != cdHashSize / 2) {
T_QUIET; T_EXPECT_EQ_ULONG(copied, cdHashSize / 2, "Expect cdHash of kexts to be half of kKALNCDHashSize");
return true;
}
if (! memcmp((void *)ptrrawCDHash, (void*)remote_cdHash, copied)) {
gotIt = true;
return false;
}
return true;
});
T_ASSERT_TRUE(gotIt, "Verify that lasio has kext at [kextPath UTF8String]);
free((void *)cdHash);
}
static uint64_t kextaudit_lasio_get_state() {
const char *cmdString[] = { "cmd" };
xpc_object_t cmd = xpc_string_create("sendlasioinfo");
xpc_object_t msg = xpc_dictionary_create(cmdString, &cmd, 1);
xpc_object_t reply;
uint64_t currentState = 0;
reply = xpc_remote_connection_send_message_with_reply_sync(kextaudit_conn, msg);
T_QUIET; T_ASSERT_NE(xpc_get_type(reply), XPC_TYPE_ERROR, "Get multiversed reply");
currentState = xpc_dictionary_get_uint64(reply, "currentState");
return currentState;
}
T_DECL(kextaudit_lasio_verify_kext_registration, "testing if a loaded kext is recorded by the kext_audit plugin and lasio", T_META_ASROOT(true))
{
bool lasio_initialState = false; // initializing to insecure
kextaudit_connect_remote_service();
lasio_initialState = kextaudit_lasio_get_state();
T_LOG("Lasio is at a/an lasio_initialState & LASIOBridgeStateIsSecure ? "secure" : "not secure");
kextaudit_audit_new_kext_load(kKextAuditTestKext);
if (lasio_initialState & LASIOBridgeStateIsSecure) /* initially secure - should remain secure */ {
kextaudit_verify_kext_with_remote_service(kKextAuditTestKext);
kextaudit_verify_kext_with_lasio(kKextAuditTestKext);
T_ASSERT_TRUE((kextaudit_lasio_get_state() & LASIOBridgeStateIsSecure), "Lasio must remain in a secure state");
} else /* initially insecure - should still remain insecure */ {
T_ASSERT_FALSE((kextaudit_lasio_get_state() & LASIOBridgeStateIsSecure), "Lasio must remain in an insecure state");
}
}