#include <darwintest.h>
#include <string.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include <IOKit/kext/misc_util.h>
T_GLOBAL_META(T_META_NAMESPACE("IOKitUser.kext"));
#pragma mark --- Prototypes ---
static CFComparisonResult compare_CFString(const void *val1, const void *val2, void *context);
static void test_OSKextCopySymbolReferences(const char * const path, const CFStringRef expectedSymbols[]);
#pragma mark --- Helper Functions ---
static CFComparisonResult compare_CFString(const void *val1, const void *val2, void *context){
return CFStringCompare(val1, val2, 0);
}
static void test_OSKextCopySymbolReferences(const char * const path, const CFStringRef expectedSymbols[]){
size_t pathLength = 0;
CFURLRef url = NULL;
OSKextRef kext = NULL;
CFMutableArrayRef symbols = NULL;
CFMutableArrayRef expectedSymbolsArray = NULL;
long symbolsCount = 0;
long expectedSymbolsCount = 0;
T_SETUPBEGIN;
T_QUIET; T_ASSERT_NOTNULL(path, "invalid test. path == NULL");
pathLength = strlen(path);
if(expectedSymbols){
for(int i=0; expectedSymbols[i] != NULL; ++i){
++expectedSymbolsCount;
}
expectedSymbolsArray = CFArrayCreateMutable(kCFAllocatorDefault, expectedSymbolsCount, &kCFTypeArrayCallBacks);
T_QUIET; T_ASSERT_NOTNULL(expectedSymbolsArray, "failed to create array of expected symbols");
CFArrayReplaceValues(expectedSymbolsArray, CFRangeMake(0, 0), (const void **)expectedSymbols, expectedSymbolsCount);
T_QUIET; T_ASSERT_EQ(expectedSymbolsCount, (long)CFArrayGetCount(expectedSymbolsArray), "failed to populate expected symbols array");
}
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (const UInt8 *)path, pathLength, false);
T_QUIET; T_ASSERT_NOTNULL(url, "failed to create CFURL for path: %s", path);
kext = OSKextCreate(kCFAllocatorDefault, url);
T_QUIET; T_ASSERT_NOTNULL(kext, "failed to create OSKext for path: %s", path);
T_SETUPEND;
symbols = OSKextCopySymbolReferences(kext);
if(expectedSymbols){
T_ASSERT_NOTNULL(symbols, "non-NULL result from OSKextCopySymbolReferences(\"%s\")", path);
} else{
T_ASSERT_NULL(symbols, "NULL result from OSKextCopySymbolReferences(\"%s\")", path);
goto cleanup;
}
symbolsCount = CFArrayGetCount(symbols);
if(expectedSymbolsCount != symbolsCount){
long nonKasanSymbolsCount = 0;
for(int s=0; s<symbolsCount; ++s){
CFStringRef symbol = CFArrayGetValueAtIndex(symbols, s);
CFRange matchRange = CFStringFind(symbol, CFSTR("_asan_"), 0x0 );
if(matchRange.location == kCFNotFound){
++nonKasanSymbolsCount;
}
}
T_ASSERT_EQ(expectedSymbolsCount, nonKasanSymbolsCount, "symbol count (ignoring kasan) matches expected: %ld", expectedSymbolsCount);
} else{
T_ASSERT_EQ(expectedSymbolsCount, symbolsCount, "symbol count matches expected: %ld", expectedSymbolsCount);
}
CFArraySortValues(symbols, CFRangeMake(0, symbolsCount), compare_CFString, NULL);
CFArraySortValues(expectedSymbolsArray, CFRangeMake(0, expectedSymbolsCount), compare_CFString, NULL);
long symbolIndex = 0;
long expectedSymbolIndex = 0;
while(symbolIndex < symbolsCount && expectedSymbolIndex < expectedSymbolsCount){
CFStringRef symbol = CFArrayGetValueAtIndex(symbols, symbolIndex);
CFStringRef expectedSymbol = CFArrayGetValueAtIndex(expectedSymbolsArray, expectedSymbolIndex);
CFComparisonResult compare = CFStringCompare(symbol, expectedSymbol, 0);
if(compare != kCFCompareEqualTo){
CFRange matchRange = CFStringFind(symbol, CFSTR("_asan_"), 0x0 );
if(matchRange.location != kCFNotFound){
++symbolIndex;
continue;
}
char *symbolName = createUTF8CStringForCFString(symbol);
char *expectedSymbolName = createUTF8CStringForCFString(expectedSymbol);
if(symbolName && expectedSymbolName){
T_ASSERT_FAIL("symbol mismatch! expecting: %s, actual: %s", expectedSymbolName, symbolName);
} else{
T_ASSERT_FAIL("symbol mismatch at sorted index: %ld, expected index: %ld. unable to allocate memory for better log message", symbolIndex, expectedSymbolIndex);
}
SAFE_FREE_NULL(symbolName);
SAFE_FREE_NULL(expectedSymbolName);
}
++symbolIndex;
++expectedSymbolIndex;
}
T_QUIET; T_ASSERT_EQ(symbolIndex, symbolsCount, "symbol mismatch! extra symbols encountered");
T_QUIET; T_ASSERT_EQ(expectedSymbolIndex, expectedSymbolsCount, "symbol mismatch! missing expected symbols");
T_PASS("all symbols match!");
cleanup:
SAFE_RELEASE(url);
SAFE_RELEASE(kext);
SAFE_RELEASE(symbols);
SAFE_RELEASE(expectedSymbolsArray);
}
#pragma mark --- OSKextCopySymbolReferences() Tests ---
T_DECL(OSKextCopySymbolReferences_0_undefined,
"check OSKextCopySymbolReferences() returns empty array for kext without any undefined symbols"
)
{
const char *path = "/AppleInternal/Tests/IOKitUser/Kexts/DoNothing.kext";
const CFStringRef expectedSymbols[] = {
NULL
};
test_OSKextCopySymbolReferences(path, expectedSymbols);
}
T_DECL(OSKextCopySymbolReferences_10_undefined,
"check OSKextCopySymbolReferences() returns array of symbols for kext with undefined symbols"
)
{
const char *path = "/AppleInternal/Tests/IOKitUser/Kexts/DoNothing-10undef.kext";
const CFStringRef expectedSymbols[] = {
CFSTR("_do_nothing_1"),
CFSTR("_do_nothing_2"),
CFSTR("_do_nothing_3"),
CFSTR("_do_nothing_4"),
CFSTR("_do_nothing_5"),
CFSTR("_do_nothing_6"),
CFSTR("_do_nothing_7"),
CFSTR("_do_nothing_8"),
CFSTR("_do_nothing_9"),
CFSTR("_do_nothing_10"),
NULL
};
test_OSKextCopySymbolReferences(path, expectedSymbols);
}
T_DECL(OSKextCopySymbolReferences_10_undefined_KASAN,
"KASAN: check OSKextCopySymbolReferences() returns array of symbols for kext with undefined symbols"
)
{
const char *path = "/AppleInternal/Tests/IOKitUser/Kexts/DoNothing-10undef.kext";
const CFStringRef expectedSymbols[] = {
CFSTR("_do_nothing_1"),
CFSTR("_do_nothing_2"),
CFSTR("_do_nothing_3"),
CFSTR("_do_nothing_4"),
CFSTR("_do_nothing_5"),
CFSTR("_do_nothing_6"),
CFSTR("_do_nothing_7"),
CFSTR("_do_nothing_8"),
CFSTR("_do_nothing_9"),
CFSTR("_do_nothing_10"),
NULL
};
OSKextSetExecutableSuffix("_kasan", NULL);
test_OSKextCopySymbolReferences(path, expectedSymbols);
OSKextSetExecutableSuffix(NULL, NULL);
}