#include <libc.h>
#include <sysexits.h>
#include <asl.h>
#include <syslog.h>
#include <sys/resource.h>
#include <IOKit/kext/OSKext.h>
#include <IOKit/kext/OSKextPrivate.h>
#include "kext_tools_util.h"
#if PRAGMA_MARK
#pragma mark Basic Utility
#endif
char * createUTF8CStringForCFString(CFStringRef aString)
{
char * result = NULL;
CFIndex bufferLength = 0;
if (!aString) {
goto finish;
}
bufferLength = sizeof('\0') +
CFStringGetMaximumSizeForEncoding(CFStringGetLength(aString),
kCFStringEncodingUTF8);
result = (char *)malloc(bufferLength * sizeof(char));
if (!result) {
goto finish;
}
if (!CFStringGetCString(aString, result, bufferLength,
kCFStringEncodingUTF8)) {
SAFE_FREE_NULL(result);
goto finish;
}
finish:
return result;
}
Boolean createCFMutableArray(CFMutableArrayRef * array,
const CFArrayCallBacks * callbacks)
{
Boolean result = true;
*array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
callbacks);
if (!*array) {
result = false;
}
return result;
}
Boolean createCFMutableDictionary(CFMutableDictionaryRef * dict)
{
Boolean result = true;
*dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!*dict) {
result = false;
}
return result;
}
Boolean createCFMutableSet(CFMutableSetRef * setOut,
const CFSetCallBacks * callbacks)
{
Boolean result = true;
*setOut = CFSetCreateMutable(kCFAllocatorDefault, 0,
callbacks);
if (!*setOut) {
result = false;
}
return result;
}
void addToArrayIfAbsent(CFMutableArrayRef array, const void * value)
{
if (kCFNotFound == CFArrayGetFirstIndexOfValue(array, RANGE_ALL(array),
value)) {
CFArrayAppendValue(array, value);
}
return;
}
#if PRAGMA_MARK
#pragma mark Path & File
#endif
ExitStatus checkPath(
const char * path,
const char * suffix, Boolean directoryRequired,
Boolean writableRequired)
{
Boolean result = EX_USAGE;
Boolean nameBad = FALSE;
struct stat statBuffer;
if (!path) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Internal error - %s - NULL path.",
__FUNCTION__);
result = EX_SOFTWARE;
goto finish;
}
result = EX_USAGE;
if (suffix) {
size_t pathLength = strlen(path);
size_t suffixLength = strlen(suffix);
size_t suffixIndex = 0;
size_t periodIndex = 0;
nameBad = TRUE;
if (!pathLength || !suffixLength) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Internal error - %s - empty string.",
__FUNCTION__);
result = EX_SOFTWARE;
goto finish;
}
while (pathLength-- && path[pathLength] == '/') {
if (!pathLength) {
goto finish;
}
}
pathLength++;
if (suffixLength >= pathLength) {
goto finish;
}
suffixIndex = pathLength - suffixLength;
periodIndex = suffixIndex - 1;
if (path[periodIndex] != '.' ||
strncmp(path + suffixIndex, suffix, suffixLength)) {
goto finish;
}
nameBad = FALSE;
}
result = EX_NOINPUT;
if (0 != stat(path, &statBuffer)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Can't stat %s - %s.", path,
strerror(errno));
goto finish;
}
if (directoryRequired && ((statBuffer.st_mode & S_IFMT) != S_IFDIR) ) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"%s is not a directory.",
path);
goto finish;
}
result = EX_NOPERM;
if (writableRequired && access(path, W_OK) == -1) {
OSKextLog( NULL, kOSKextLogErrorLevel,
"%s is not writable.", path);
goto finish;
}
result = EX_OK;
finish:
if (nameBad) {
OSKextLog( NULL, kOSKextLogErrorLevel,
"%s not of type '%s'.", path, suffix);
}
return result;
}
void
saveFile(const void * vKey, const void * vValue, void * vContext)
{
CFStringRef key = (CFStringRef)vKey;
CFDataRef fileData = (CFDataRef)vValue;
SaveFileContext * context = (SaveFileContext *)vContext;
CFURLRef saveURL = NULL; CFBooleanRef fileExists = NULL; char savePath[PATH_MAX];
SInt32 error;
if (context->fatal) {
goto finish;
}
saveURL = CFURLCreateCopyAppendingPathComponent(kCFAllocatorDefault,
context->saveDirURL, key, false);
if (!saveURL) {
context->fatal = true;
goto finish;
}
if (!CFURLGetFileSystemRepresentation(saveURL, false,
(u_char *)savePath, sizeof(savePath))) {
context->fatal = true;
goto finish;
}
if (!context->overwrite) {
fileExists = CFURLCreatePropertyFromResource(kCFAllocatorDefault, saveURL,
kCFURLFileExists, &error);
if (!fileExists || CFBooleanGetTypeID() != CFGetTypeID(fileExists)) {
OSKextLog( NULL, kOSKextLogErrorLevel,
"Error checking file: CFError %d.", (int)error);
}
if (CFBooleanGetValue(fileExists)) {
switch (user_approve( TRUE, REPLY_YES,
"%s exists, overwrite", savePath)) {
case REPLY_YES:
break;
case REPLY_ALL:
fprintf(stderr,
"Overwriting all symbol files for kexts in dependency graph.\n");
context->overwrite = TRUE;
break;
case REPLY_NO:
goto finish;
break;
default:
context->fatal = true;
goto finish;
break;
}
}
}
if (!CFURLWriteDataAndPropertiesToResource(saveURL, fileData,
NULL, &error)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"Failed to save %s.", savePath);
}
finish:
SAFE_RELEASE(saveURL);
SAFE_RELEASE(fileExists);
return;
}
CFStringRef copyKextPath(OSKextRef aKext)
{
CFStringRef result = NULL;
CFURLRef absURL = NULL;
if (!OSKextGetURL(aKext)) {
goto finish;
}
absURL = CFURLCopyAbsoluteURL(OSKextGetURL(aKext));
if (!absURL) {
goto finish;
}
result = CFURLCopyFileSystemPath(absURL, kCFURLPOSIXPathStyle);
finish:
SAFE_RELEASE(absURL);
return result;
}
#if PRAGMA_MARK
#pragma mark Logging
#endif
OSKextLogSpec _sLogSpecsForVerboseLevels[] = {
kOSKextLogErrorLevel | kOSKextLogVerboseFlagsMask, kOSKextLogBasicLevel | kOSKextLogVerboseFlagsMask, kOSKextLogProgressLevel | kOSKextLogVerboseFlagsMask, kOSKextLogStepLevel | kOSKextLogVerboseFlagsMask, kOSKextLogDetailLevel | kOSKextLogVerboseFlagsMask, kOSKextLogDebugLevel | kOSKextLogVerboseFlagsMask, kOSKextLogDebugLevel | kOSKextLogVerboseFlagsMask | kOSKextLogKextOrGlobalMask
};
#define kBadVerboseOptPrefix "-v="
ExitStatus setLogFilterForOpt(
int argc,
char * const * argv,
OSKextLogSpec forceOnFlags)
{
ExitStatus result = EX_USAGE;
OSKextLogSpec logFilter = 0;
const char * localOptarg = NULL;
if (!optarg && optind >= argc) {
logFilter = _sLogSpecsForVerboseLevels[1];
} else {
if (optarg) {
localOptarg = optarg;
} else {
localOptarg = argv[optind];
}
if (!strncmp(localOptarg, kBadVerboseOptPrefix,
sizeof(kBadVerboseOptPrefix) - 1)) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s - syntax error (don't use = with single-letter option args).",
localOptarg);
goto finish;
}
if (localOptarg[0] == '-' && localOptarg[1] == kOptVerbose &&
localOptarg[2] == '0' && (localOptarg[3] == 'x' || localOptarg[3] == 'X')) {
localOptarg += 2;
}
if (localOptarg[0] == '0' && (localOptarg[1] == 'x' || localOptarg[1] == 'X')) {
char * endptr = NULL;
OSKextLogSpec parsedFlags = (unsigned)strtoul(localOptarg, &endptr, 16);
if (endptr[0]) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Can't parse verbose argument %s.", localOptarg);
goto finish;
}
logFilter = parsedFlags;
if (!optarg) {
optind++;
}
} else if (((localOptarg[0] >= '0') || (localOptarg[0] <= '6')) &&
(localOptarg[1] == '\0')) {
logFilter = _sLogSpecsForVerboseLevels[localOptarg[0] - '0'];
if (!optarg) {
optind++;
}
} else {
logFilter = _sLogSpecsForVerboseLevels[1];
}
}
logFilter = logFilter | forceOnFlags;
OSKextSetLogFilter(logFilter, false);
OSKextSetLogFilter(logFilter, true);
result = EX_OK;
finish:
return result;
}
void beQuiet(void)
{
fclose(stdout);
fclose(stderr);
close(1);
close(2);
OSKextSetLogFilter(kOSKextLogSilentFilter, false);
OSKextSetLogFilter(kOSKextLogSilentFilter, true);
return;
}
FILE * g_log_stream = NULL;
aslclient gASLClientHandle = NULL;
aslmsg gASLMessage = NULL;
void tool_openlog(const char * name)
{
gASLClientHandle = asl_open( name, name,
0);
gASLMessage = asl_new(ASL_TYPE_MSG);
return;
}
void tool_log(
OSKextRef aKext __unused,
OSKextLogSpec msgLogSpec,
const char * format, ...)
{
va_list ap;
if (gASLClientHandle) {
int aslLevel = ASL_LEVEL_ERR;
OSKextLogSpec kextLogLevel = msgLogSpec & kOSKextLogLevelMask;
char messageLogSpec[16];
if (kextLogLevel == kOSKextLogErrorLevel) {
aslLevel = ASL_LEVEL_ERR;
} else if (kextLogLevel == kOSKextLogWarningLevel) {
aslLevel = ASL_LEVEL_WARNING;
} else if (kextLogLevel == kOSKextLogBasicLevel) {
aslLevel = ASL_LEVEL_NOTICE;
} else if (kextLogLevel < kOSKextLogDebugLevel) {
aslLevel = ASL_LEVEL_INFO;
} else {
aslLevel = ASL_LEVEL_DEBUG;
}
snprintf(messageLogSpec, sizeof(messageLogSpec), "0x%x", msgLogSpec);
asl_set(gASLMessage, "OSKextLogSpec", messageLogSpec);
va_start(ap, format);
asl_vlog(gASLClientHandle, gASLMessage, aslLevel, format, ap);
va_end(ap);
} else {
if (!g_log_stream) {
g_log_stream = stderr;
}
va_start(ap, format);
vfprintf(g_log_stream, format, ap);
va_end(ap);
fprintf(g_log_stream, "\n");
fflush(g_log_stream);
}
return;
}
void log_CFError(
OSKextRef aKext __unused,
OSKextLogSpec msgLogSpec,
CFErrorRef error)
{
CFStringRef errorString = NULL; char * cstring = NULL;
if (!error) {
return;
}
errorString = CFErrorCopyDescription(error);
if (errorString) {
cstring = createUTF8CStringForCFString(errorString);
OSKextLog( NULL, msgLogSpec,
"CFError descripton: %s.", cstring);
SAFE_RELEASE_NULL(errorString);
SAFE_FREE_NULL(cstring);
}
errorString = CFErrorCopyFailureReason(error);
if (errorString) {
cstring = createUTF8CStringForCFString(errorString);
OSKextLog( NULL, msgLogSpec,
"CFError reason: %s.", cstring);
SAFE_RELEASE_NULL(errorString);
SAFE_FREE_NULL(cstring);
}
return;
}
const char * safe_mach_error_string(mach_error_t error_code)
{
const char * result = mach_error_string(error_code);
if (!result) {
result = "(unknown)";
}
return result;
}
#if PRAGMA_MARK
#pragma mark User Input
#endif
int user_approve(Boolean ask_all, int default_answer, const char * format, ...)
{
int result = REPLY_YES;
va_list ap;
char fake_buffer[2];
int output_length;
char * output_string;
int c, x;
va_start(ap, format);
output_length = vsnprintf(fake_buffer, 1, format, ap);
va_end(ap);
output_string = (char *)malloc(output_length + 1);
if (!output_string) {
result = REPLY_ERROR;
goto finish;
}
va_start(ap, format);
vsnprintf(output_string, output_length + 1, format, ap);
va_end(ap);
while ( 1 ) {
fprintf(stderr, "%s [%s/%s", output_string,
(default_answer == REPLY_YES) ? "Y" : "y",
(default_answer == REPLY_NO) ? "N" : "n");
if (ask_all) {
fprintf(stderr, "/%s",
(default_answer == REPLY_ALL) ? "A" : "a");
}
fprintf(stderr, "]? ");
fflush(stderr);
c = fgetc(stdin);
if (c == EOF) {
result = REPLY_ERROR;
goto finish;
}
if ( c != '\n' ) {
do {
x = fgetc(stdin);
} while (x != '\n' && x != EOF);
if (x == EOF) {
result = REPLY_ERROR;
goto finish;
}
}
if (c == '\n') {
result = default_answer;
goto finish;
} else if (tolower(c) == 'y') {
result = REPLY_YES;
goto finish;
} else if (tolower(c) == 'n') {
result = REPLY_NO;
goto finish;
} else if (ask_all && tolower(c) == 'a') {
result = REPLY_ALL;
goto finish;
} else {
fprintf(stderr, "Please answer 'y' or 'n'%s.\n",
ask_all ? " or 'a'" : "");
}
}
finish:
if (output_string) free(output_string);
return result;
}
const char * user_input(Boolean * eof, const char * format, ...)
{
char * result = NULL; va_list ap;
char fake_buffer[2];
int output_length;
char * output_string = NULL;
unsigned index;
size_t size = 80; int c;
if (eof) {
*eof = false;
}
result = (char *)malloc(size);
if (!result) {
goto finish;
}
index = 0;
va_start(ap, format);
output_length = vsnprintf(fake_buffer, 1, format, ap);
va_end(ap);
output_string = (char *)malloc(output_length + 1);
if (!output_string) {
result = NULL;
goto finish;
}
va_start(ap, format);
vsnprintf(output_string, output_length + 1, format, ap);
va_end(ap);
fprintf(stderr, "%s ", output_string);
fflush(stderr);
c = fgetc(stdin);
while (c != '\n' && c != EOF) {
if (index >= (size - 1)) {
fprintf(stderr, "input line too long\n");
if (result) free(result);
result = NULL;
goto finish;
}
result[index++] = (char)c;
c = fgetc(stdin);
}
result[index] = '\0';
if (c == EOF) {
if (result) free(result);
result = NULL;
if (eof) {
*eof = true;
}
goto finish;
}
finish:
if (output_string) free(output_string);
return result;
}
#if PRAGMA_MARK
#pragma mark Caches
#endif
Boolean readSystemKextPropertyValues(
CFStringRef propertyKey,
const NXArchInfo * arch,
Boolean forceUpdateFlag,
CFArrayRef * valuesOut)
{
Boolean result = false;
CFArrayRef sysExtensionsFolderURLs = OSKextGetSystemExtensionsFolderURLs();
CFMutableArrayRef values = NULL; CFStringRef cacheBasename = NULL; CFArrayRef kexts = NULL; CFMutableDictionaryRef newDict = NULL; CFStringRef kextPath = NULL; CFTypeRef value = NULL; CFStringRef kextVersion = NULL; CFIndex count, i;
cacheBasename = CFStringCreateWithFormat(kCFAllocatorDefault,
NULL, CFSTR("%s%@"),
_kKextPropertyValuesCacheBasename,
propertyKey);
if (!cacheBasename) {
OSKextLogMemError();
goto finish;
}
if (OSKextGetUsesCaches() && !forceUpdateFlag) {
if (_OSKextReadCache(sysExtensionsFolderURLs, cacheBasename,
arch, _kOSKextCacheFormatCFXML, true,
(CFPropertyListRef *)&values)) {
if (values && CFGetTypeID(values) == CFArrayGetTypeID()) {
result = true;
goto finish;
}
}
}
values = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
if (!values) {
OSKextLogMemError();
goto finish;
}
kexts = OSKextCreateKextsFromURLs(kCFAllocatorDefault,
sysExtensionsFolderURLs);
if (!kexts) {
goto finish;
}
count = CFArrayGetCount(kexts);
for (i = 0; i < count; i++) {
OSKextRef aKext = (OSKextRef)CFArrayGetValueAtIndex(kexts, i);
SAFE_RELEASE_NULL(newDict);
SAFE_RELEASE_NULL(kextPath);
kextVersion = NULL;
if ((OSKextGetSimulatedSafeBoot() || OSKextGetActualSafeBoot()) &&
!OSKextIsLoadableInSafeBoot(aKext)) {
continue;
}
value = OSKextGetValueForInfoDictionaryKey(aKext, propertyKey);
if (!value) {
continue;
}
newDict = CFDictionaryCreateMutable(
kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if (!newDict) {
goto finish;
}
CFDictionarySetValue(newDict, CFSTR("Data"), value);
CFDictionarySetValue(newDict, CFSTR("CFBundleIdentifier"),
OSKextGetIdentifier(aKext));
kextPath = copyKextPath(aKext);
if (!kextPath) {
goto finish;
}
CFDictionarySetValue(newDict, CFSTR("OSBundlePath"), kextPath);
kextVersion = OSKextGetValueForInfoDictionaryKey(aKext,
CFSTR("CFBundleVersion"));
if (!kextVersion) {
goto finish;
}
CFDictionarySetValue(newDict, CFSTR("CFBundleVersion"),
kextVersion);
CFArrayAppendValue(values, newDict);
}
if (OSKextGetUsesCaches() || forceUpdateFlag) {
_OSKextWriteCache(sysExtensionsFolderURLs, cacheBasename,
arch, _kOSKextCacheFormatCFXML, values);
}
result = true;
finish:
if (result && valuesOut && values) {
*valuesOut = (CFArrayRef)CFRetain(values);
}
SAFE_RELEASE(values);
SAFE_RELEASE(cacheBasename);
SAFE_RELEASE(kexts);
SAFE_RELEASE(newDict);
SAFE_RELEASE(kextPath);
return result;
}