#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <CommonCrypto/CommonDigest.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <MultiverseSupport/kext_audit_plugin_common.h>
#include <sys/csr.h>
#include "security.h"
#include "kext_tools_util.h"
#include "signposts.h"
#define kSha1HexDigestSize (CC_SHA1_DIGEST_LENGTH * 2 + 1)
#define kSha1ZeroHash "0000000000000000000000000000000000000000"
static bool gKextAuditIsInitialized = false;
static bool gShouldAuditKexts = true;
static io_connect_t gKextAuditIOConn;
static bool KextAuditMakeKALNFromInfo(struct KextAuditLoadNotificationKext *kaln, OSKextVersion version,
CFStringRef cdHashCFString, CFStringRef bundleIDCFString,
CFStringRef teamIDCFString)
{
bool result = true;
enum KextAuditLoadType loadType;
const char *cdHash, *teamID, *bundleID;
size_t cdHashLen, bundleIDLen;
char versionString[kKALNKKextVersionSize] = { };
char rawBundleHash[CC_SHA256_DIGEST_LENGTH] = { };
char rawCDHash[kKALNKCDHashSize] = { };
CFIndex cdHashBufLen, bundleIDBufLen, teamIDBufLen;
char *cdHashBuffer = NULL;
char *bundleIDBuffer = NULL;
char *teamIDBuffer = NULL;
cdHashBufLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cdHashCFString) + 1, kCFStringEncodingUTF8);
cdHashBuffer = malloc(cdHashBufLen);
if (!cdHashBuffer) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Impossible to malloc cdHashBuffer %zu bytes", cdHashBufLen);
result = false;
goto error;
}
GET_CSTRING_PTR(cdHashCFString, cdHash, cdHashBuffer, cdHashBufLen);
if (!cdHash) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not get CDHash string");
result = false;
goto error;
}
cdHashLen = strlen(cdHash);
if (cdHashLen == 40) {
loadType = kKALTKextCDHashSha1;
} else if (cdHashLen == 64) {
loadType = kKALTKextCDHashSha256;
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Kext had a strange hash: %s, length %zu", cdHash, cdHashLen);
result = false;
goto error;
}
result = createRawBytesFromHexString(&rawCDHash[0], kKALNKCDHashSize, cdHash, cdHashLen);
if (!result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not create raw hash from CDHash %s", cdHash);
goto error;
}
bundleIDBufLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(bundleIDCFString) + 1, kCFStringEncodingUTF8);
bundleIDBuffer = malloc(bundleIDBufLen);
if (!bundleIDBuffer) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Impossible to malloc bundleIDBuffer %zu bytes", bundleIDBufLen);
result = false;
goto error;
}
GET_CSTRING_PTR(bundleIDCFString, bundleID, bundleIDBuffer, bundleIDBufLen);
if (!bundleID) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not get bundleID string");
result = false;
goto error;
}
bundleIDLen = strlen(bundleID);
result = CC_SHA256(bundleID, (unsigned int)bundleIDLen, (unsigned char*)&rawBundleHash[0]);
if (!result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not create hash of bundleID");
goto error;
}
teamIDBufLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(teamIDCFString) + 1, kCFStringEncodingUTF8);
teamIDBuffer = malloc(bundleIDBufLen);
if (!teamIDBuffer) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Impossible to maloc teamIDBuffer %zu bytes", teamIDBufLen);
result = false;
goto error;
}
GET_CSTRING_PTR(teamIDCFString, teamID, teamIDBuffer, teamIDBufLen);
result = OSKextVersionGetString(version, &versionString[0], kOSKextVersionMaxLength);
if (!result) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not get version string");
goto error;
}
kaln->loadType = loadType;
memcpy(&kaln->cdHash[0], rawCDHash, kKALNKCDHashSize);
if (teamID) {
strncpy(&kaln->teamID[0], teamID, kKALNKTeamIDSize);
} else {
memset(&kaln->teamID[0], 0, kKALNKTeamIDSize);
}
memcpy(&kaln->bundleHash[0], rawBundleHash, kKALNKBundleHashSize);
strncpy(&kaln->kextVersion[0], &versionString[0], kKALNKKextVersionSize);
kaln->teamID[kKALNKTeamIDSize-1] = '\0';
kaln->kextVersion[kKALNKKextVersionSize-1] = '\0';
error:
if (cdHashBuffer) {
free(cdHashBuffer);
}
if (bundleIDBuffer) {
free(bundleIDBuffer);
}
if (teamIDBuffer) {
free(teamIDBuffer);
}
return result;
}
static bool KextAuditMakeKALNFromKext(struct KextAuditLoadNotificationKext *kaln, OSKextRef theKext)
{
CFURLRef kextURL = NULL;
CFStringRef bundleIDCFString = NULL;
CFStringRef cdHashCFString = NULL;
CFStringRef teamIDCFString = NULL;
char *adHocHash = NULL;
OSKextVersion version = 0;
bool haveAdHocHash = false;
bool result = false;
kextURL = OSKextGetURL(theKext);
if (!kextURL) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not get URL from kext.");
result = false;
goto error;
}
bundleIDCFString = OSKextGetIdentifier(theKext);
if (!bundleIDCFString) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not get bundleID from kext.");
result = false;
goto error;
}
version = OSKextGetVersion(theKext);
if (!version) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not get version from kext.");
result = false;
goto error;
}
copySigningInfo(kextURL, &cdHashCFString, &teamIDCFString, NULL, NULL);
if (!teamIDCFString) {
teamIDCFString = CFSTR("");
}
if (!cdHashCFString) {
getAdhocSignatureHash(kextURL, &adHocHash, NULL);
haveAdHocHash = (adHocHash != NULL);
if (!haveAdHocHash) {
adHocHash = kSha1ZeroHash;
}
cdHashCFString = CFStringCreateWithCString(NULL, adHocHash, kCFStringEncodingUTF8);
}
result = KextAuditMakeKALNFromInfo(kaln, version, cdHashCFString,
bundleIDCFString, teamIDCFString);
if (!result) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not create kext load notification from kext info.");
goto error;
}
error:
SAFE_RELEASE(cdHashCFString);
SAFE_RELEASE(teamIDCFString);
if (haveAdHocHash) {
free(adHocHash);
}
return result;
}
static bool KextAuditNotifyBridgeWithReplySync(struct KextAuditLoadNotificationKext *kaln,
struct KextAuditBridgeResponse *kabr)
{
if (!kaln || !kabr) {
return false;
}
if (!gKextAuditIsInitialized) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"KextAudit IOKit connection not initialized!");
return false;
}
IOByteCount inStructSize = sizeof(*kaln);
size_t outStructSize = sizeof(*kabr);
IOReturn ir;
ir = IOConnectCallStructMethod(gKextAuditIOConn,
kKextAuditMethodNotifyLoad,
kaln,
inStructSize,
kabr,
&outStructSize);
if (ir != kIOReturnSuccess) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Error communicating with KextAudit kext.");
return false;
}
return true;
}
#if DEBUG
bool KextAuditTestBridge(uint8_t* kaln,
struct KextAuditBridgeResponse *kabr)
{
if (!gKextAuditIsInitialized) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"KextAudit IOKit connection not initialized!");
return false;
}
if (!gShouldAuditKexts) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"KextAudit disabled!");
return false;
}
IOByteCount inStructSize = kKALNStructSize;
size_t outStructSize = sizeof(*kabr);
IOReturn ir;
ir = IOConnectCallStructMethod(gKextAuditIOConn,
kKextAuditMethodTest,
kaln,
inStructSize,
kabr,
&outStructSize);
if (ir != kIOReturnSuccess) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Error communicating with KextAudit kext.");
return false;
}
return true;
}
#endif
bool KextAuditInitialize(void)
{
mach_port_t port;
io_service_t service;
IOReturn ir;
bool result = true;
int wait_time_us = 1000000;
if (gKextAuditIsInitialized) {
return true;
}
gShouldAuditKexts = true;
if (csr_check(CSR_ALLOW_UNTRUSTED_KEXTS) == 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Disabling KextAudit: SIP is off");
gShouldAuditKexts = false;
goto out;
}
ir = IOMasterPort(MACH_PORT_NULL, &port);
if (ir != kIOReturnSuccess) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not get master port.");
result = false;
goto error;
}
do {
service = IOServiceGetMatchingService(port, IOServiceMatching("KextAudit"));
if (!service) {
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogLoadFlag,
"waiting for KextAudit to start");
usleep(10000);
wait_time_us -= 10000;
}
} while (!service && wait_time_us > 0);
if (!service) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Disabling KextAudit: Could not find KextAudit kext");
gShouldAuditKexts = false;
goto out;
}
ir = IOServiceOpen(service, mach_task_self(), 0, &gKextAuditIOConn);
if (ir != kIOReturnSuccess) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not open connection with KextAudit kext.");
result = false;
goto error;
}
out:
gKextAuditIsInitialized = true;
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogLoadFlag,
"KextAudit initialized: audit=%c", gShouldAuditKexts ? 'T' : 'F');
error:
return result;
}
Boolean KextAuditLoadCallback(OSKextRef theKext)
{
struct KextAuditLoadNotificationKext kaln = { };
struct KextAuditBridgeResponse kabr = { };
bool result = true;
os_signpost_id_t spid_callback, spid_kaln;
spid_callback = generate_signpost_id();
os_signpost_interval_begin(get_signpost_log(), spid_callback, SIGNPOST_KEXTD_KEXTAUDITLOADCALLBACK);
if (!theKext) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Can't audit kext load if kext is nonexistent");
result = false;
goto error;
}
if (!gKextAuditIsInitialized) {
result = KextAuditInitialize();
if (!result) {
goto error;
}
}
if (!gShouldAuditKexts) {
result = true;
goto error;
}
spid_kaln = os_signpost_id_make_with_pointer(get_signpost_log(), (void *)theKext);
os_signpost_interval_begin(get_signpost_log(), spid_kaln, SIGNPOST_KEXTD_KEXTAUDITMAKEKALN);
result = KextAuditMakeKALNFromKext(&kaln, theKext);
if (!result) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not create kext load notification.");
os_signpost_interval_end(get_signpost_log(), spid_kaln, SIGNPOST_KEXTD_KEXTAUDITMAKEKALN);
goto error;
}
os_signpost_interval_end(get_signpost_log(), spid_kaln, SIGNPOST_KEXTD_KEXTAUDITMAKEKALN);
kabr.status = kKALNStatusLoad;
result = KextAuditNotifyBridgeWithReplySync(&kaln, &kabr);
if (!result) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Could not notify bridge of kext load.");
goto error;
}
if (kabr.status == kKALNStatusNoBridge) {
gShouldAuditKexts = false;
OSKextLog( NULL,
kOSKextLogBasicLevel | kOSKextLogLoadFlag,
"KextAudit didn't find a bridge: audit=%c", gShouldAuditKexts ? 'T' : 'F');
goto error;
}
if (kabr.status != kKALNStatusBridgeAck) {
OSKextLog( theKext,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"KextAuditNotifyBridgeWithReplySync returned %d", kabr.status);
goto error;
}
error:
os_signpost_interval_end(get_signpost_log(), spid_callback,
SIGNPOST_KEXTD_KEXTAUDITLOADCALLBACK, "%s", (gShouldAuditKexts ? (result ? "success" : "failure") : "no-audit"));
return result;
}