darling-xnu/libkern/c++/OSKext.cpp
2023-05-16 21:41:14 -07:00

15434 lines
457 KiB
C++

/*
* Copyright (c) 2008-2016 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
#define IOKIT_ENABLE_SHARED_PTR
extern "C" {
#include <string.h>
#include <kern/clock.h>
#include <kern/host.h>
#include <kern/kext_alloc.h>
#include <firehose/tracepoint_private.h>
#include <firehose/chunk_private.h>
#include <os/firehose_buffer_private.h>
#include <vm/vm_kern.h>
#include <vm/vm_map.h>
#include <kextd/kextd_mach.h>
#include <libkern/kernel_mach_header.h>
#include <libkern/kext_panic_report.h>
#include <libkern/kext_request_keys.h>
#include <libkern/mkext.h>
#include <libkern/prelink.h>
#include <libkern/version.h>
#include <libkern/zlib.h>
#include <mach/host_special_ports.h>
#include <mach/mach_vm.h>
#include <mach/mach_time.h>
#include <sys/sysctl.h>
#include <uuid/uuid.h>
#include <sys/random.h>
#include <pexpert/pexpert.h>
#include <sys/pgo.h>
#if CONFIG_MACF
#include <sys/kauth.h>
#include <security/mac_framework.h>
#endif
#if CONFIG_CSR
#include <sys/csr.h>
#include <sys/stat.h>
#include <sys/vnode.h>
#endif /* CONFIG_CSR */
};
#include <os/cpp_util.h>
#include <libkern/OSKextLibPrivate.h>
#include <libkern/c++/OSKext.h>
#include <libkern/c++/OSLib.h>
#include <IOKit/IOLib.h>
#include <IOKit/IOCatalogue.h>
#include <IOKit/IORegistryEntry.h>
#include <IOKit/IOService.h>
#include <IOKit/IOUserServer.h>
#include <IOKit/IOStatisticsPrivate.h>
#include <IOKit/IOBSD.h>
#include <IOKit/IOPlatformExpert.h>
#include <san/kasan.h>
#if PRAGMA_MARK
#pragma mark External & Internal Function Protos
#endif
/*********************************************************************
*********************************************************************/
extern "C" {
extern int IODTGetLoaderInfo(const char * key, void ** infoAddr, int * infoSize);
extern void IODTFreeLoaderInfo(const char * key, void * infoAddr, int infoSize);
extern ppnum_t pmap_find_phys(pmap_t pmap, addr64_t va); /* osfmk/machine/pmap.h */
extern int dtrace_keep_kernel_symbols(void);
#if defined(__x86_64__) || defined(__i386__)
extern kern_return_t i386_slide_individual_kext(kernel_mach_header_t *mh, uintptr_t slide);
extern kern_return_t i386_slide_kext_collection_mh_addrs(kernel_mach_header_t *mh, uintptr_t slide, bool adjust_mach_headers);
extern void *ubc_getobject_from_filename(const char *filename, struct vnode **, off_t *file_size);
static void *allocate_kcfileset_map_entry_list(void);
static void add_kcfileset_map_entry(void *map_entry_list, vm_map_offset_t start, vm_map_offset_t size);
static void deallocate_kcfileset_map_entry_list_and_unmap_entries(void *map_entry_list, boolean_t unmap_entries, bool pageable);
int vnode_put(struct vnode *vp);
kern_return_t vm_map_kcfileset_segment(vm_map_offset_t *start, vm_map_offset_t size,
void *control, vm_object_offset_t fileoffset, vm_prot_t max_prot);
kern_return_t vm_unmap_kcfileset_segment(vm_map_offset_t *start, vm_map_offset_t size);
void * ubc_getobject(struct vnode *vp, __unused int flags);
#endif //(__x86_64__) || defined(__i386__)
}
extern unsigned long gVirtBase;
extern unsigned long gPhysBase;
extern vm_map_t g_kext_map;
bool pageableKCloaded = false;
bool auxKCloaded = false;
bool resetAuxKCSegmentOnUnload = false;
extern boolean_t pageablekc_uuid_valid;
extern uuid_t pageablekc_uuid;
extern uuid_string_t pageablekc_uuid_string;
extern boolean_t auxkc_uuid_valid;
extern uuid_t auxkc_uuid;
extern uuid_string_t auxkc_uuid_string;
static OSReturn _OSKextCreateRequest(
const char * predicate,
OSSharedPtr<OSDictionary> & requestP);
static OSString * _OSKextGetRequestPredicate(OSDictionary * requestDict);
static OSObject * _OSKextGetRequestArgument(
OSDictionary * requestDict,
const char * argName);
static bool _OSKextSetRequestArgument(
OSDictionary * requestDict,
const char * argName,
OSObject * value);
static void * _OSKextExtractPointer(OSData * wrapper);
static OSKextRequestResourceCallback _OSKextExtractCallbackPointer(OSData * wrapper);
static OSReturn _OSDictionarySetCStringValue(
OSDictionary * dict,
const char * key,
const char * value);
static bool _OSKextInUnloadedPrelinkedKexts(const OSSymbol * theBundleID);
#if CONFIG_KXLD
static bool _OSKextInPrelinkRebuildWindow(void);
#endif
// We really should add containsObject() & containsCString to OSCollection & subclasses.
// So few pad slots, though....
static bool _OSArrayContainsCString(OSArray * array, const char * cString);
static void OSKextLogKextInfo(OSKext *aKext, uint64_t address, uint64_t size, firehose_tracepoint_code_t code);
/* Prelinked arm kexts do not have VM entries because the method we use to
* fake an entry (see libsa/bootstrap.cpp:readPrelinkedExtensions()) does
* not work on ARM. To get around that, we must free prelinked kext
* executables with ml_static_mfree() instead of kext_free().
*/
#if __i386__ || __x86_64__
#define VM_MAPPED_KEXTS 1
#define KASLR_KEXT_DEBUG 0
#define KASLR_IOREG_DEBUG 0
#elif __arm__ || __arm64__
#define VM_MAPPED_KEXTS 0
#define KASLR_KEXT_DEBUG 0
#else
#error Unsupported architecture
#endif
#if PRAGMA_MARK
#pragma mark Constants & Macros
#endif
/*********************************************************************
* Constants & Macros
*********************************************************************/
/* Use this number to create containers.
*/
#define kOSKextTypicalLoadCount (150)
/* Any kext will have at least 1 retain for the internal lookup-by-ID dict.
* A loaded kext will no dependents or external retains will have 2 retains.
*/
#define kOSKextMinRetainCount (1)
#define kOSKextMinLoadedRetainCount (2)
/**********
* Strings and substrings used in dependency resolution.
*/
#define APPLE_KEXT_PREFIX "com.apple."
#define KERNEL_LIB "com.apple.kernel"
#define PRIVATE_KPI "com.apple.kpi.private"
/* Version for compatbility pseudokexts (com.apple.kernel.*),
* compatible back to v6.0.
*/
#define KERNEL6_LIB "com.apple.kernel.6.0"
#define KERNEL6_VERSION "7.9.9"
#define KERNEL_LIB_PREFIX "com.apple.kernel."
#define KPI_LIB_PREFIX "com.apple.kpi."
#define STRING_HAS_PREFIX(s, p) (strncmp((s), (p), strlen(p)) == 0)
#define REBUILD_MAX_TIME (60 * 5) // 5 minutes
#define MINIMUM_WAKEUP_SECONDS (30)
/*********************************************************************
* infoDict keys for internally-stored data. Saves on ivar slots for
* objects we don't keep around past boot time or during active load.
*********************************************************************/
/* A usable, uncompressed file is stored under this key.
*/
#define _kOSKextExecutableKey "_OSKextExecutable"
/* An indirect reference to the executable file from an mkext
* is stored under this key.
*/
#define _kOSKextMkextExecutableReferenceKey "_OSKextMkextExecutableReference"
/* If the file is contained in a larger buffer laid down by the booter or
* sent from user space, the OSKext stores that OSData under this key so that
* references are properly tracked. This is always an mkext, right now.
*/
#define _kOSKextExecutableExternalDataKey "_OSKextExecutableExternalData"
#define OS_LOG_HDR_VERSION 1
#define NUM_OS_LOG_SECTIONS 2
#define OS_LOG_SECT_IDX 0
#define CSTRING_SECT_IDX 1
#if PRAGMA_MARK
#pragma mark Typedefs
#endif
/*********************************************************************
* Typedefs
*********************************************************************/
/*********************************************************************
* osLogDataHeaderRef describes the header information of an OSData
* object that is returned when querying for kOSBundleLogStringsKey.
* We currently return information regarding 2 sections - os_log and
* cstring. In the case that the os_log section doesn't exist, we just
* return an offset and length of 0 for that section.
*********************************************************************/
typedef struct osLogDataHeader {
uint32_t version;
uint32_t sect_count;
struct {
uint32_t sect_offset;
uint32_t sect_size;
} sections[0];
} osLogDataHeaderRef;
/*********************************************************************
* MkextEntryRef describes the contents of an OSData object
* referencing a file entry from an mkext so that we can uncompress
* (if necessary) and extract it on demand.
*
* It contains the mkextVersion in case we ever wind up supporting
* multiple mkext formats. Mkext format 1 is officially retired as of
* Snow Leopard.
*********************************************************************/
typedef struct MkextEntryRef {
mkext_basic_header * mkext; // beginning of whole mkext file
void * fileinfo;// mkext2_file_entry or equiv; see mkext.h
} MkextEntryRef;
#if PRAGMA_MARK
#pragma mark Global and static Module Variables
#endif
/*********************************************************************
* Global & static variables, used to keep track of kexts.
*********************************************************************/
static bool sPrelinkBoot = false;
static bool sSafeBoot = false;
static bool sKeepSymbols = false;
static bool sPanicOnKCMismatch = false;
static bool sOSKextWasResetAfterUserspaceReboot = false;
/*********************************************************************
* sKextLock is the principal lock for OSKext, and guards all static
* and global variables not owned by other locks (declared further
* below). It must be taken by any entry-point method or function,
* including internal functions called on scheduled threads.
*
* sKextLock and sKextInnerLock are recursive due to multiple functions
* that are called both externally and internally. The other locks are
* nonrecursive.
*
* Which locks are taken depends on what they protect, but if more than
* one must be taken, they must always be locked in this order
* (and unlocked in reverse order) to prevent deadlocks:
*
* 1. sKextLock
* 2. sKextInnerLock
* 3. sKextSummariesLock
* 4. sKextLoggingLock
*/
static IORecursiveLock * sKextLock = NULL;
static OSSharedPtr<OSDictionary> sKextsByID;
static OSSharedPtr<OSDictionary> sExcludeListByID;
static OSKextVersion sExcludeListVersion = 0;
static OSSharedPtr<OSArray> sLoadedKexts;
static OSSharedPtr<OSDictionary> sNonLoadableKextsByID;
static OSSharedPtr<OSArray> sUnloadedPrelinkedKexts;
static OSSharedPtr<OSArray> sLoadedDriverKitKexts;
// Requests to the IOKit daemon waiting to be picked up.
static OSSharedPtr<OSArray> sKernelRequests;
// Identifier of kext load requests in sKernelRequests
static OSSharedPtr<OSSet> sPostedKextLoadIdentifiers;
static OSSharedPtr<OSArray> sRequestCallbackRecords;
// Identifiers of all kexts ever requested in kernel; used for prelinked kernel
static OSSharedPtr<OSSet> sAllKextLoadIdentifiers;
#if CONFIG_KXLD
static KXLDContext * sKxldContext = NULL;
#endif
static uint32_t sNextLoadTag = 0;
static uint32_t sNextRequestTag = 0;
static bool sUserLoadsActive = false;
static bool sIOKitDaemonActive = false;
static bool sDeferredLoadSucceeded = false;
static bool sConsiderUnloadsExecuted = false;
#if NO_KEXTD
static bool sKernelRequestsEnabled = false;
#else
static bool sKernelRequestsEnabled = true;
#endif
static bool sLoadEnabled = true;
static bool sUnloadEnabled = true;
/*********************************************************************
* Stuff for the OSKext representing the kernel itself.
**********/
static OSKext * sKernelKext = NULL;
/* Set up a fake kmod_info struct for the kernel.
* It's used in OSRuntime.cpp to call OSRuntimeInitializeCPP()
* before OSKext is initialized; that call only needs the name
* and address to be set correctly.
*
* We don't do much else with the kerne's kmod_info; we never
* put it into the kmod list, never adjust the reference count,
* and never have kernel components reference it.
* For that matter, we don't do much with kmod_info structs
* at all anymore! We just keep them filled in for gdb and
* binary compability.
*/
kmod_info_t g_kernel_kmod_info = {
.next = NULL,
.info_version = KMOD_INFO_VERSION,
.id = 0, // loadTag: kernel is always 0
.name = kOSKextKernelIdentifier,// bundle identifier
.version = "0", // filled in in OSKext::initialize()
.reference_count = -1, // never adjusted; kernel never unloads
.reference_list = NULL,
.address = 0,
.size = 0, // filled in in OSKext::initialize()
.hdr_size = 0,
.start = NULL,
.stop = NULL
};
/* Set up a fake kmod_info struct for statically linked kexts that don't have one. */
kmod_info_t invalid_kmod_info = {
.next = NULL,
.info_version = KMOD_INFO_VERSION,
.id = UINT32_MAX,
.name = "invalid",
.version = "0",
.reference_count = -1,
.reference_list = NULL,
.address = 0,
.size = 0,
.hdr_size = 0,
.start = NULL,
.stop = NULL
};
extern "C" {
// symbol 'kmod' referenced in: model_dep.c, db_trace.c, symbols.c, db_low_trace.c,
// dtrace.c, dtrace_glue.h, OSKext.cpp, locore.s, lowmem_vectors.s,
// misc_protos.h, db_low_trace.c, kgmacros
// 'kmod' is a holdover from the old kmod system, we can't rename it.
kmod_info_t * kmod = NULL;
#define KEXT_PANICLIST_SIZE (2 * PAGE_SIZE)
static char * loaded_kext_paniclist = NULL;
static uint32_t loaded_kext_paniclist_size = 0;
AbsoluteTime last_loaded_timestamp;
static char last_loaded_str_buf[2 * KMOD_MAX_NAME];
static u_long last_loaded_strlen = 0;
static void * last_loaded_address = NULL;
static u_long last_loaded_size = 0;
AbsoluteTime last_unloaded_timestamp;
static char last_unloaded_str_buf[2 * KMOD_MAX_NAME];
static u_long last_unloaded_strlen = 0;
static void * last_unloaded_address = NULL;
static u_long last_unloaded_size = 0;
// Statically linked kmods described by several mach-o sections:
//
// kPrelinkInfoSegment:kBuiltinInfoSection
// Array of pointers to kmod_info_t structs.
//
// kPrelinkInfoSegment:kBuiltinInfoSection
// Array of pointers to an embedded mach-o header.
//
// __DATA:kBuiltinInitSection, kBuiltinTermSection
// Structors for all kmods. Has to be filtered by proc address.
//
static uint32_t gBuiltinKmodsCount;
static kernel_section_t * gBuiltinKmodsSectionInfo;
static kernel_section_t * gBuiltinKmodsSectionStart;
const OSSymbol * gIOSurfaceIdentifier;
vm_tag_t gIOSurfaceTag;
/*********************************************************************
* sKextInnerLock protects against cross-calls with IOService and
* IOCatalogue, and owns the variables declared immediately below.
*
* Note that sConsiderUnloadsExecuted above belongs to sKextLock!
*
* When both sKextLock and sKextInnerLock need to be taken,
* always lock sKextLock first and unlock it second. Never take both
* locks in an entry point to OSKext; if you need to do so, you must
* spawn an independent thread to avoid potential deadlocks for threads
* calling into OSKext.
**********/
static IORecursiveLock * sKextInnerLock = NULL;
static bool sAutounloadEnabled = true;
static bool sConsiderUnloadsCalled = false;
static bool sConsiderUnloadsPending = false;
static unsigned int sConsiderUnloadDelay = 60; // seconds
static thread_call_t sUnloadCallout = NULL;
#if CONFIG_KXLD
static thread_call_t sDestroyLinkContextThread = NULL; // one-shot, one-at-a-time thread
#endif // CONFIG_KXLD
static bool sSystemSleep = false; // true when system going to sleep
static AbsoluteTime sLastWakeTime; // last time we woke up
/*********************************************************************
* Backtraces can be printed at various times so we need a tight lock
* on data used for that. sKextSummariesLock protects the variables
* declared immediately below.
*
* gLoadedKextSummaries is accessed by other modules, but only during
* a panic so the lock isn't needed then.
*
* gLoadedKextSummaries has the "used" attribute in order to ensure
* that it remains visible even when we are performing extremely
* aggressive optimizations, as it is needed to allow the debugger
* to automatically parse the list of loaded kexts.
**********/
static IOLock * sKextSummariesLock = NULL;
extern "C" lck_spin_t vm_allocation_sites_lock;
static IOSimpleLock * sKextAccountsLock = &vm_allocation_sites_lock;
void(*const sLoadedKextSummariesUpdated)(void) = OSKextLoadedKextSummariesUpdated;
OSKextLoadedKextSummaryHeader * gLoadedKextSummaries __attribute__((used)) = NULL;
uint64_t gLoadedKextSummariesTimestamp __attribute__((used)) = 0;
static size_t sLoadedKextSummariesAllocSize = 0;
static OSKextActiveAccount * sKextAccounts;
static uint32_t sKextAccountsCount;
};
/*********************************************************************
* sKextLoggingLock protects the logging variables declared immediately below.
**********/
static IOLock * sKextLoggingLock = NULL;
static const OSKextLogSpec kDefaultKernelLogFilter = kOSKextLogBasicLevel |
kOSKextLogVerboseFlagsMask;
static OSKextLogSpec sKernelLogFilter = kDefaultKernelLogFilter;
static bool sBootArgLogFilterFound = false;
SYSCTL_UINT(_debug, OID_AUTO, kextlog, CTLFLAG_RW | CTLFLAG_LOCKED, &sKernelLogFilter,
0, "kernel kext logging");
static OSKextLogSpec sUserSpaceKextLogFilter = kOSKextLogSilentFilter;
static OSSharedPtr<OSArray> sUserSpaceLogSpecArray;
static OSSharedPtr<OSArray> sUserSpaceLogMessageArray;
/*********
* End scope for sKextInnerLock-protected variables.
*********************************************************************/
/*********************************************************************
* helper function used for collecting PGO data upon unload of a kext
*/
static int OSKextGrabPgoDataLocked(OSKext *kext,
bool metadata,
uuid_t instance_uuid,
uint64_t *pSize,
char *pBuffer,
uint64_t bufferSize);
/**********************************************************************/
#if PRAGMA_MARK
#pragma mark OSData callbacks (need to move to OSData)
#endif
/*********************************************************************
* C functions used for callbacks.
*********************************************************************/
extern "C" {
void
osdata_kmem_free(void * ptr, unsigned int length)
{
kmem_free(kernel_map, (vm_address_t)ptr, length);
return;
}
void
osdata_phys_free(void * ptr, unsigned int length)
{
ml_static_mfree((vm_offset_t)ptr, length);
return;
}
void
osdata_vm_deallocate(void * ptr, unsigned int length)
{
(void)vm_deallocate(kernel_map, (vm_offset_t)ptr, length);
return;
}
void
osdata_kext_free(void * ptr, unsigned int length)
{
(void)kext_free((vm_offset_t)ptr, length);
}
};
#if PRAGMA_MARK
#pragma mark KXLD Allocation Callback
#endif
#if CONFIG_KXLD
/*********************************************************************
* KXLD Allocation Callback
*********************************************************************/
kxld_addr_t
kern_allocate(
u_long size,
KXLDAllocateFlags * flags,
void * user_data)
{
vm_address_t result = 0; // returned
kern_return_t mach_result = KERN_FAILURE;
bool success = false;
OSKext * theKext = (OSKext *)user_data;
unsigned int roundSize = 0;
OSSharedPtr<OSData> linkBuffer;
if (round_page(size) > UINT_MAX) {
OSKextLog(theKext,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"%s: Requested memory size is greater than UINT_MAX.",
theKext->getIdentifierCString());
goto finish;
}
roundSize = (unsigned int)round_page(size);
mach_result = kext_alloc(&result, roundSize, /* fixed */ FALSE);
if (mach_result != KERN_SUCCESS) {
OSKextLog(theKext,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Can't allocate kernel memory to link %s.",
theKext->getIdentifierCString());
goto finish;
}
/* Create an OSData wrapper for the allocated buffer.
*/
linkBuffer = OSData::withBytesNoCopy((void *)result, roundSize);
if (!linkBuffer) {
OSKextLog(theKext,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Can't allocate linked executable wrapper for %s.",
theKext->getIdentifierCString());
goto finish;
}
linkBuffer->setDeallocFunction(osdata_kext_free);
OSKextLog(theKext,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag | kOSKextLogLinkFlag,
"Allocated link buffer for kext %s at %p (%lu bytes).",
theKext->getIdentifierCString(),
(void *)result, (unsigned long)roundSize);
theKext->setLinkedExecutable(linkBuffer.get());
*flags = kKxldAllocateWritable;
success = true;
finish:
if (!success && result) {
kext_free(result, roundSize);
result = 0;
}
return (kxld_addr_t)result;
}
/*********************************************************************
*********************************************************************/
void
kxld_log_callback(
KXLDLogSubsystem subsystem,
KXLDLogLevel level,
const char * format,
va_list argList,
void * user_data)
{
OSKext *theKext = (OSKext *) user_data;
OSKextLogSpec logSpec = 0;
switch (subsystem) {
case kKxldLogLinking:
logSpec |= kOSKextLogLinkFlag;
break;
case kKxldLogPatching:
logSpec |= kOSKextLogPatchFlag;
break;
}
switch (level) {
case kKxldLogExplicit:
logSpec |= kOSKextLogExplicitLevel;
break;
case kKxldLogErr:
logSpec |= kOSKextLogErrorLevel;
break;
case kKxldLogWarn:
logSpec |= kOSKextLogWarningLevel;
break;
case kKxldLogBasic:
logSpec |= kOSKextLogProgressLevel;
break;
case kKxldLogDetail:
logSpec |= kOSKextLogDetailLevel;
break;
case kKxldLogDebug:
logSpec |= kOSKextLogDebugLevel;
break;
}
OSKextVLog(theKext, logSpec, format, argList);
}
#endif // CONFIG_KXLD
#if PRAGMA_MARK
#pragma mark IOStatistics defines
#endif
#if IOKITSTATS
#define notifyKextLoadObservers(kext, kmod_info) \
do { \
IOStatistics::onKextLoad(kext, kmod_info); \
} while (0)
#define notifyKextUnloadObservers(kext) \
do { \
IOStatistics::onKextUnload(kext); \
} while (0)
#define notifyAddClassObservers(kext, addedClass, flags) \
do { \
IOStatistics::onClassAdded(kext, addedClass); \
} while (0)
#define notifyRemoveClassObservers(kext, removedClass, flags) \
do { \
IOStatistics::onClassRemoved(kext, removedClass); \
} while (0)
#else
#define notifyKextLoadObservers(kext, kmod_info)
#define notifyKextUnloadObservers(kext)
#define notifyAddClassObservers(kext, addedClass, flags)
#define notifyRemoveClassObservers(kext, removedClass, flags)
#endif /* IOKITSTATS */
#if PRAGMA_MARK
#pragma mark Module Config (Startup & Shutdown)
#endif
/*********************************************************************
* Module Config (Class Definition & Class Methods)
*********************************************************************/
#define super OSObject
OSDefineMetaClassAndStructors(OSKext, OSObject)
OSDefineMetaClassAndStructors(OSKextSavedMutableSegment, OSObject);
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::initialize(void)
{
OSSharedPtr<OSData> kernelExecutable = NULL;// do not release
u_char * kernelStart = NULL;// do not free
size_t kernelLength = 0;
IORegistryEntry * registryRoot = NULL;// do not release
OSSharedPtr<OSNumber> kernelCPUType;
OSSharedPtr<OSNumber> kernelCPUSubtype;
OSKextLogSpec bootLogFilter = kOSKextLogSilentFilter;
bool setResult = false;
uint64_t * timestamp = NULL;
__unused char bootArgBuffer[16];// for PE_parse_boot_argn w/strings
/* This must be the first thing allocated. Everything else grabs this lock.
*/
sKextLock = IORecursiveLockAlloc();
sKextInnerLock = IORecursiveLockAlloc();
sKextSummariesLock = IOLockAlloc();
sKextLoggingLock = IOLockAlloc();
assert(sKextLock);
assert(sKextInnerLock);
assert(sKextSummariesLock);
assert(sKextLoggingLock);
sKextsByID = OSDictionary::withCapacity(kOSKextTypicalLoadCount);
sLoadedKexts = OSArray::withCapacity(kOSKextTypicalLoadCount);
sLoadedDriverKitKexts = OSArray::withCapacity(kOSKextTypicalLoadCount);
sUnloadedPrelinkedKexts = OSArray::withCapacity(kOSKextTypicalLoadCount / 10);
sKernelRequests = OSArray::withCapacity(0);
sPostedKextLoadIdentifiers = OSSet::withCapacity(0);
sAllKextLoadIdentifiers = OSSet::withCapacity(kOSKextTypicalLoadCount);
sRequestCallbackRecords = OSArray::withCapacity(0);
assert(sKextsByID && sLoadedKexts && sLoadedDriverKitKexts && sKernelRequests &&
sPostedKextLoadIdentifiers && sAllKextLoadIdentifiers &&
sRequestCallbackRecords && sUnloadedPrelinkedKexts);
/* Read the log flag boot-args and set the log flags.
*/
if (PE_parse_boot_argn("kextlog", &bootLogFilter, sizeof(bootLogFilter))) {
sBootArgLogFilterFound = true;
sKernelLogFilter = bootLogFilter;
// log this if any flags are set
OSKextLog(/* kext */ NULL,
kOSKextLogBasicLevel |
kOSKextLogFlagsMask,
"Kernel kext log filter 0x%x per kextlog boot arg.",
(unsigned)sKernelLogFilter);
}
#if !defined(__arm__) && !defined(__arm64__)
/*
* On our ARM targets, the kernelcache/boot kernel collection contains
* the set of kexts required to boot, as specified by KCB. Safeboot is
* either unsupported, or is supported by the bootloader only loading
* the boot kernel collection; as a result OSKext has no role to play
* in safeboot policy on ARM.
*/
sSafeBoot = PE_parse_boot_argn("-x", bootArgBuffer,
sizeof(bootArgBuffer)) ? true : false;
#endif /* defined(__arm__) && defined(__arm64__) */
if (sSafeBoot) {
OSKextLog(/* kext */ NULL,
kOSKextLogWarningLevel |
kOSKextLogGeneralFlag,
"SAFE BOOT DETECTED - "
"only valid OSBundleRequired kexts will be loaded.");
}
PE_parse_boot_argn("keepsyms", &sKeepSymbols, sizeof(sKeepSymbols));
#if CONFIG_DTRACE
if (dtrace_keep_kernel_symbols()) {
sKeepSymbols = true;
}
#endif /* CONFIG_DTRACE */
#if KASAN_DYNAMIC_BLACKLIST
/* needed for function lookup */
sKeepSymbols = true;
#endif
/*
* Should we panic when the SystemKC is not linked against the
* BootKC that was loaded by the booter? By default: yes, if the
* "-nokcmismatchpanic" boot-arg is passed, then we _don't_ panic
* on mis-match and instead just print an error and continue.
*/
sPanicOnKCMismatch = PE_parse_boot_argn("-nokcmismatchpanic", bootArgBuffer,
sizeof(bootArgBuffer)) ? false : true;
/* Set up an OSKext instance to represent the kernel itself.
*/
sKernelKext = new OSKext;
assert(sKernelKext);
kernelStart = (u_char *)&_mh_execute_header;
kernelLength = getlastaddr() - (vm_offset_t)kernelStart;
assert(kernelLength <= UINT_MAX);
kernelExecutable = OSData::withBytesNoCopy(
kernelStart, (unsigned int)kernelLength);
assert(kernelExecutable);
#if KASLR_KEXT_DEBUG
IOLog("kaslr: kernel start 0x%lx end 0x%lx length %lu vm_kernel_slide %lu (0x%016lx) \n",
(unsigned long)kernelStart,
(unsigned long)getlastaddr(),
kernelLength,
(unsigned long)vm_kernel_slide,
(unsigned long)vm_kernel_slide);
#endif
sKernelKext->loadTag = sNextLoadTag++; // the kernel is load tag 0
sKernelKext->bundleID = OSSymbol::withCString(kOSKextKernelIdentifier);
sKernelKext->version = OSKextParseVersionString(osrelease);
sKernelKext->compatibleVersion = sKernelKext->version;
sKernelKext->linkedExecutable = os::move(kernelExecutable);
sKernelKext->interfaceUUID = sKernelKext->copyUUID();
sKernelKext->flags.hasAllDependencies = 1;
sKernelKext->flags.kernelComponent = 1;
sKernelKext->flags.prelinked = 0;
sKernelKext->flags.loaded = 1;
sKernelKext->flags.started = 1;
sKernelKext->flags.CPPInitialized = 0;
sKernelKext->flags.jettisonLinkeditSeg = 0;
sKernelKext->kmod_info = &g_kernel_kmod_info;
strlcpy(g_kernel_kmod_info.version, osrelease,
sizeof(g_kernel_kmod_info.version));
g_kernel_kmod_info.size = kernelLength;
g_kernel_kmod_info.id = sKernelKext->loadTag;
/* Cons up an info dict, so we don't have to have special-case
* checking all over.
*/
sKernelKext->infoDict = OSDictionary::withCapacity(5);
assert(sKernelKext->infoDict);
setResult = sKernelKext->infoDict->setObject(kCFBundleIdentifierKey,
sKernelKext->bundleID.get());
assert(setResult);
setResult = sKernelKext->infoDict->setObject(kOSKernelResourceKey,
kOSBooleanTrue);
assert(setResult);
{
OSSharedPtr<OSString> scratchString(OSString::withCStringNoCopy(osrelease));
assert(scratchString);
setResult = sKernelKext->infoDict->setObject(kCFBundleVersionKey,
scratchString.get());
assert(setResult);
}
{
OSSharedPtr<OSString> scratchString(OSString::withCStringNoCopy("mach_kernel"));
assert(scratchString);
setResult = sKernelKext->infoDict->setObject(kCFBundleNameKey,
scratchString.get());
assert(setResult);
}
/* Add the kernel kext to the bookkeeping dictionaries. Note that
* the kernel kext doesn't have a kmod_info struct. copyInfo()
* gathers info from other places anyhow.
*/
setResult = sKextsByID->setObject(sKernelKext->bundleID.get(), sKernelKext);
assert(setResult);
setResult = sLoadedKexts->setObject(sKernelKext);
assert(setResult);
// XXX: better way with OSSharedPtr?
// sKernelKext remains a valid pointer even after the decref
sKernelKext->release();
registryRoot = IORegistryEntry::getRegistryRoot();
kernelCPUType = OSNumber::withNumber(
(long long unsigned int)_mh_execute_header.cputype,
8 * sizeof(_mh_execute_header.cputype));
kernelCPUSubtype = OSNumber::withNumber(
(long long unsigned int)_mh_execute_header.cpusubtype,
8 * sizeof(_mh_execute_header.cpusubtype));
assert(registryRoot && kernelCPUSubtype && kernelCPUType);
registryRoot->setProperty(kOSKernelCPUTypeKey, kernelCPUType.get());
registryRoot->setProperty(kOSKernelCPUSubtypeKey, kernelCPUSubtype.get());
gBuiltinKmodsSectionInfo = getsectbyname(kPrelinkInfoSegment, kBuiltinInfoSection);
if (gBuiltinKmodsSectionInfo) {
uint32_t count;
assert(gBuiltinKmodsSectionInfo->addr);
assert(gBuiltinKmodsSectionInfo->size);
assert(gBuiltinKmodsSectionInfo->size / sizeof(kmod_info_t *) <= UINT_MAX);
gBuiltinKmodsCount = (unsigned int)(gBuiltinKmodsSectionInfo->size / sizeof(kmod_info_t *));
gBuiltinKmodsSectionStart = getsectbyname(kPrelinkInfoSegment, kBuiltinStartSection);
assert(gBuiltinKmodsSectionStart);
assert(gBuiltinKmodsSectionStart->addr);
assert(gBuiltinKmodsSectionStart->size);
assert(gBuiltinKmodsSectionStart->size / sizeof(uintptr_t) <= UINT_MAX);
count = (unsigned int)(gBuiltinKmodsSectionStart->size / sizeof(uintptr_t));
// one extra pointer for the end of last kmod
assert(count == (gBuiltinKmodsCount + 1));
vm_kernel_builtinkmod_text = ((uintptr_t *)gBuiltinKmodsSectionStart->addr)[0];
vm_kernel_builtinkmod_text_end = ((uintptr_t *)gBuiltinKmodsSectionStart->addr)[count - 1];
}
// Don't track this object -- it's never released
gIOSurfaceIdentifier = OSSymbol::withCStringNoCopy("com.apple.iokit.IOSurface").detach();
timestamp = __OSAbsoluteTimePtr(&last_loaded_timestamp);
*timestamp = 0;
timestamp = __OSAbsoluteTimePtr(&last_unloaded_timestamp);
*timestamp = 0;
timestamp = __OSAbsoluteTimePtr(&sLastWakeTime);
*timestamp = 0;
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogGeneralFlag,
"Kext system initialized.");
notifyKextLoadObservers(sKernelKext, sKernelKext->kmod_info);
return;
}
/*********************************************************************
* This is expected to be called exactly once, from exactly one thread
* context, during kernel bootstrap.
*********************************************************************/
/* static */
OSReturn
OSKext::removeKextBootstrap(void)
{
OSReturn result = kOSReturnError;
const char * dt_kernel_header_name = "Kernel-__HEADER";
const char * dt_kernel_symtab_name = "Kernel-__SYMTAB";
kernel_mach_header_t * dt_mach_header = NULL;
int dt_mach_header_size = 0;
struct symtab_command * dt_symtab = NULL;
int dt_symtab_size = 0;
int dt_result = 0;
kernel_segment_command_t * seg_kld = NULL;
kernel_segment_command_t * seg_klddata = NULL;
kernel_segment_command_t * seg_linkedit = NULL;
const char __unused * dt_segment_name = NULL;
void __unused * segment_paddress = NULL;
int __unused segment_size = 0;
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogGeneralFlag,
"Jettisoning kext bootstrap segments.");
/*
* keep the linkedit segment around when booted from a new MH_FILESET
* KC because all the kexts shared a linkedit segment.
*/
kc_format_t kc_format;
if (!PE_get_primary_kc_format(&kc_format)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Unable to determine primary KC format");
}
/*****
* Dispose of unnecessary stuff that the booter didn't need to load.
*/
dt_result = IODTGetLoaderInfo(dt_kernel_header_name,
(void **)&dt_mach_header, &dt_mach_header_size);
if (dt_result == 0 && dt_mach_header) {
IODTFreeLoaderInfo(dt_kernel_header_name, (void *)dt_mach_header,
round_page_32(dt_mach_header_size));
}
dt_result = IODTGetLoaderInfo(dt_kernel_symtab_name,
(void **)&dt_symtab, &dt_symtab_size);
if (dt_result == 0 && dt_symtab) {
IODTFreeLoaderInfo(dt_kernel_symtab_name, (void *)dt_symtab,
round_page_32(dt_symtab_size));
}
/*****
* KLD & KLDDATA bootstrap segments.
*/
// xxx - should rename KLD segment
seg_kld = getsegbyname("__KLD");
seg_klddata = getsegbyname("__KLDDATA");
if (seg_klddata) {
// __mod_term_func is part of __KLDDATA
OSRuntimeUnloadCPPForSegment(seg_klddata);
}
#if __arm__ || __arm64__
/* Free the memory that was set up by iBoot.
*/
#if !defined(KERNEL_INTEGRITY_KTRR) && !defined(KERNEL_INTEGRITY_CTRR)
/* We cannot free the KLD segment with CTRR enabled as it contains text and
* is covered by the contiguous rorgn.
*/
dt_segment_name = "Kernel-__KLD";
if (0 == IODTGetLoaderInfo(dt_segment_name, &segment_paddress, &segment_size)) {
IODTFreeLoaderInfo(dt_segment_name, (void *)segment_paddress,
(int)segment_size); // calls ml_static_mfree
} else if (seg_kld && seg_kld->vmaddr && seg_kld->vmsize) {
/* With fileset KCs, the Kernel KLD segment is not recorded in the DT. */
ml_static_mfree(ml_static_ptovirt(seg_kld->vmaddr - gVirtBase + gPhysBase),
seg_kld->vmsize);
}
#endif
dt_segment_name = "Kernel-__KLDDATA";
if (0 == IODTGetLoaderInfo(dt_segment_name, &segment_paddress, &segment_size)) {
IODTFreeLoaderInfo(dt_segment_name, (void *)segment_paddress,
(int)segment_size); // calls ml_static_mfree
} else if (seg_klddata && seg_klddata->vmaddr && seg_klddata->vmsize) {
/* With fileset KCs, the Kernel KLDDATA segment is not recorded in the DT. */
ml_static_mfree(ml_static_ptovirt(seg_klddata->vmaddr - gVirtBase + gPhysBase),
seg_klddata->vmsize);
}
#elif __i386__ || __x86_64__
/* On x86, use the mapping data from the segment load command to
* unload KLD & KLDDATA directly.
* This may invalidate any assumptions about "avail_start"
* defining the lower bound for valid physical addresses.
*/
if (seg_kld && seg_kld->vmaddr && seg_kld->vmsize) {
bzero((void *)seg_kld->vmaddr, seg_kld->vmsize);
ml_static_mfree(seg_kld->vmaddr, seg_kld->vmsize);
}
if (seg_klddata && seg_klddata->vmaddr && seg_klddata->vmsize) {
bzero((void *)seg_klddata->vmaddr, seg_klddata->vmsize);
ml_static_mfree(seg_klddata->vmaddr, seg_klddata->vmsize);
}
#else
#error arch
#endif
/*****
* Prelinked kernel's symtab (if there is one).
*/
if (kc_format != KCFormatFileset) {
kernel_section_t * sect;
sect = getsectbyname("__PRELINK", "__symtab");
if (sect && sect->addr && sect->size) {
ml_static_mfree(sect->addr, sect->size);
}
}
seg_linkedit = (kernel_segment_command_t *)getsegbyname("__LINKEDIT");
/* kxld always needs the kernel's __LINKEDIT segment, but we can make it
* pageable, unless keepsyms is set. To do that, we have to copy it from
* its booter-allocated memory, free the booter memory, reallocate proper
* managed memory, then copy the segment back in.
*
* NOTE: This optimization is not valid for fileset KCs because each
* fileset entry (kext or xnu) in an MH_FILESET has a LINKEDIT segment
* that points to one fileset-global LINKEDIT segment. This
* optimization is also only valid for platforms that support vm
* mapped kexts or mapped kext collections (pageable KCs)
*/
#if VM_MAPPED_KEXTS
if (!sKeepSymbols && kc_format != KCFormatFileset) {
kern_return_t mem_result;
void *seg_copy = NULL;
void *seg_data = NULL;
vm_map_offset_t seg_offset = 0;
vm_map_offset_t seg_copy_offset = 0;
vm_map_size_t seg_length = 0;
seg_data = (void *) seg_linkedit->vmaddr;
seg_offset = (vm_map_offset_t) seg_linkedit->vmaddr;
seg_length = (vm_map_size_t) seg_linkedit->vmsize;
/* Allocate space for the LINKEDIT copy.
*/
mem_result = kmem_alloc(kernel_map, (vm_offset_t *) &seg_copy,
seg_length, VM_KERN_MEMORY_KEXT);
if (mem_result != KERN_SUCCESS) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag | kOSKextLogArchiveFlag,
"Can't copy __LINKEDIT segment for VM reassign.");
return result;
}
seg_copy_offset = (vm_map_offset_t) seg_copy;
/* Copy it out.
*/
memcpy(seg_copy, seg_data, seg_length);
/* Dump the booter memory.
*/
ml_static_mfree(seg_offset, seg_length);
/* Set up the VM region.
*/
mem_result = vm_map_enter_mem_object(
kernel_map,
&seg_offset,
seg_length, /* mask */ 0,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
VM_MAP_KERNEL_FLAGS_NONE,
VM_KERN_MEMORY_NONE,
(ipc_port_t)NULL,
(vm_object_offset_t) 0,
/* copy */ FALSE,
/* cur_protection */ VM_PROT_READ | VM_PROT_WRITE,
/* max_protection */ VM_PROT_ALL,
/* inheritance */ VM_INHERIT_DEFAULT);
if ((mem_result != KERN_SUCCESS) ||
(seg_offset != (vm_map_offset_t) seg_data)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag | kOSKextLogArchiveFlag,
"Can't create __LINKEDIT VM entry at %p, length 0x%llx (error 0x%x).",
seg_data, seg_length, mem_result);
return result;
}
/* And copy it back.
*/
memcpy(seg_data, seg_copy, seg_length);
/* Free the copy.
*/
kmem_free(kernel_map, seg_copy_offset, seg_length);
} else if (!sKeepSymbols && kc_format == KCFormatFileset) {
/* Remove the linkedit segment of the Boot KC */
kernel_mach_header_t *mh = (kernel_mach_header_t *)PE_get_kc_header(KCKindPrimary);
OSKext::jettisonFileSetLinkeditSegment(mh);
}
#else // !VM_MAPPED_KEXTS
/*****
* Dump the LINKEDIT segment, unless keepsyms is set.
*/
if (!sKeepSymbols && kc_format != KCFormatFileset) {
dt_segment_name = "Kernel-__LINKEDIT";
if (0 == IODTGetLoaderInfo(dt_segment_name,
&segment_paddress, &segment_size)) {
#ifdef SECURE_KERNEL
vm_offset_t vmaddr = ml_static_ptovirt((vm_offset_t)segment_paddress);
bzero((void*)vmaddr, segment_size);
#endif
IODTFreeLoaderInfo(dt_segment_name, (void *)segment_paddress,
(int)segment_size);
}
} else {
OSKextLog(/* kext */ NULL,
kOSKextLogBasicLevel |
kOSKextLogGeneralFlag,
"keepsyms boot arg specified; keeping linkedit segment for symbols.");
}
#endif // VM_MAPPED_KEXTS
result = kOSReturnSuccess;
return result;
}
#if CONFIG_KXLD
/*********************************************************************
*********************************************************************/
void
OSKext::flushNonloadedKexts(
Boolean flushPrelinkedKexts)
{
OSSharedPtr<OSSet> keepKexts;
/* TODO: make this more efficient with MH_FILESET kexts */
// Do not unload prelinked kexts on arm because the kernelcache is not
// structured in a way that allows them to be unmapped
#if !defined(__x86_64__)
flushPrelinkedKexts = false;
#endif /* defined(__x86_64__) */
IORecursiveLockLock(sKextLock);
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogKextBookkeepingFlag,
"Flushing nonloaded kexts and other unused data.");
OSKext::considerDestroyingLinkContext();
/* If we aren't flushing unused prelinked kexts, we have to put them
* aside while we flush everything else so make a container for them.
*/
keepKexts = OSSet::withCapacity(16);
if (!keepKexts) {
goto finish;
}
/* Set aside prelinked kexts (in-use or not) and break
* any lingering inter-kext references for nonloaded kexts
* so they have min. retain counts.
*/
{
sKextsByID->iterateObjects(^bool (const OSSymbol * thisID __unused, OSObject * obj) {
OSKext * thisKext = OSDynamicCast(OSKext, obj);
if (!thisKext) {
return false;
}
if (!flushPrelinkedKexts && thisKext->isPrelinked()) {
keepKexts->setObject(thisKext);
} else if (!thisKext->declaresExecutable()) {
/*
* Don't unload codeless kexts, because they never appear in the loadedKexts array.
* Requesting one from the IOKit daemon will load it and then immediately remove it by calling
* flushNonloadedKexts().
* And adding one to loadedKexts breaks code assuming they have kmod_info etc.
*/
keepKexts->setObject(thisKext);
} else if (thisKext->isInFileset()) {
/* keep all kexts in the new MH_FILESET KC */
keepKexts->setObject(thisKext);
}
thisKext->flushDependencies(/* forceIfLoaded */ false);
return false;
});
}
/* Dump all the kexts in the ID dictionary; we'll repopulate it shortly.
*/
sKextsByID->flushCollection();
/* Now put the loaded kexts back into the ID dictionary.
*/
sLoadedKexts->iterateObjects(^bool (OSObject * obj) {
OSKext * thisKext = OSDynamicCast(OSKext, obj);
if (!thisKext) {
return false;
}
sKextsByID->setObject(thisKext->getIdentifierCString(), thisKext);
return false;
});
/* Finally, put back the kept kexts if we saved any.
*/
keepKexts->iterateObjects(^bool (OSObject * obj) {
OSKext * thisKext = OSDynamicCast(OSKext, obj);
if (!thisKext) {
return false;
}
sKextsByID->setObject(thisKext->getIdentifierCString(), thisKext);
return false;
});
finish:
IORecursiveLockUnlock(sKextLock);
return;
}
#else /* !CONFIG_KXLD */
void
OSKext::flushNonloadedKexts(
Boolean flushPrelinkedKexts __unused)
{
IORecursiveLockLock(sKextLock);
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogKextBookkeepingFlag,
"Flushing dependency info for non-loaded kexts.");
/*
* In a world where we don't dynamically link kexts, they all come
* from a kext collection that's either in wired memory, or
* wire-on-demand. We don't need to mess around with moving kexts in
* and out of the sKextsByID array - they can all just stay there.
* Here we just flush the dependency list for kexts that are not
* loaded.
*/
sKextsByID->iterateObjects(^bool (const OSSymbol * thisID __unused, OSObject * obj) {
OSKext * thisKext = OSDynamicCast(OSKext, obj);
if (!thisKext) {
return false;
}
thisKext->flushDependencies(/* forceIfLoaded */ false);
return false;
});
IORecursiveLockUnlock(sKextLock);
return;
}
#endif /* CONFIG_KXLD */
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::setIOKitDaemonActive(bool active)
{
IOServiceTrace(IOSERVICE_KEXTD_ALIVE, 0, 0, 0, 0);
IORecursiveLockLock(sKextLock);
sIOKitDaemonActive = active;
if (sKernelRequests->getCount()) {
OSKext::pingIOKitDaemon();
}
IORecursiveLockUnlock(sKextLock);
return;
}
/*********************************************************************
* OSKextLib.cpp might need access to this someday but for now it's
* private.
*********************************************************************/
extern "C" {
extern void ipc_port_release_send(ipc_port_t);
};
/* static */
OSReturn
OSKext::pingIOKitDaemon(void)
{
OSReturn result = kOSReturnError;
#if !NO_KEXTD
mach_port_t kextd_port = IPC_PORT_NULL;
if (!sIOKitDaemonActive) {
result = kOSKextReturnDisabled; // basically unavailable
goto finish;
}
result = host_get_kextd_port(host_priv_self(), &kextd_port);
if (result != KERN_SUCCESS || !IPC_PORT_VALID(kextd_port)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Can't get " kIOKitDaemonName " port.");
goto finish;
}
result = kextd_ping(kextd_port);
if (result != KERN_SUCCESS) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
kIOKitDaemonName " ping failed (0x%x).", (int)result);
goto finish;
}
finish:
if (IPC_PORT_VALID(kextd_port)) {
ipc_port_release_send(kextd_port);
}
#endif
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::setDeferredLoadSucceeded(Boolean succeeded)
{
IORecursiveLockLock(sKextLock);
sDeferredLoadSucceeded = succeeded;
IORecursiveLockUnlock(sKextLock);
return;
}
/*********************************************************************
* Called from IOSystemShutdownNotification.
*********************************************************************/
/* static */
void
OSKext::willShutdown(void)
{
#if !NO_KEXTD
OSReturn checkResult = kOSReturnError;
#endif
OSSharedPtr<OSDictionary> exitRequest;
IORecursiveLockLock(sKextLock);
OSKext::setLoadEnabled(false);
OSKext::setUnloadEnabled(false);
OSKext::setAutounloadsEnabled(false);
OSKext::setKernelRequestsEnabled(false);
#if defined(__x86_64__) || defined(__i386__)
if (IOPMRootDomainGetWillShutdown()) {
OSKext::freeKCFileSetcontrol();
}
#endif // (__x86_64__) || defined(__i386__)
#if !NO_KEXTD
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogGeneralFlag,
"System shutdown; requesting immediate " kIOKitDaemonName " exit.");
checkResult = _OSKextCreateRequest(kKextRequestPredicateRequestDaemonExit,
exitRequest);
if (checkResult != kOSReturnSuccess) {
goto finish;
}
if (!sKernelRequests->setObject(exitRequest.get())) {
goto finish;
}
OSKext::pingIOKitDaemon();
finish:
#endif
IORecursiveLockUnlock(sKextLock);
return;
}
void
OSKext::willUserspaceReboot(void)
{
OSKext::willShutdown();
IOService::userSpaceWillReboot();
gIOCatalogue->terminateDriversForUserspaceReboot();
}
void
OSKext::resetAfterUserspaceReboot(void)
{
OSSharedPtr<OSArray> arr = OSArray::withCapacity(1);
IOService::updateConsoleUsers(arr.get(), 0, true /* after_userspace_reboot */);
IORecursiveLockLock(sKextLock);
gIOCatalogue->resetAfterUserspaceReboot();
IOService::userSpaceDidReboot();
OSKext::setLoadEnabled(true);
OSKext::setUnloadEnabled(true);
OSKext::setAutounloadsEnabled(true);
OSKext::setKernelRequestsEnabled(true);
sOSKextWasResetAfterUserspaceReboot = true;
IORecursiveLockUnlock(sKextLock);
}
extern "C" void
OSKextResetAfterUserspaceReboot(void)
{
OSKext::resetAfterUserspaceReboot();
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::getLoadEnabled(void)
{
bool result;
IORecursiveLockLock(sKextLock);
result = sLoadEnabled;
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::setLoadEnabled(bool flag)
{
bool result;
IORecursiveLockLock(sKextLock);
result = sLoadEnabled;
sLoadEnabled = (flag ? true : false);
if (sLoadEnabled != result) {
OSKextLog(/* kext */ NULL,
kOSKextLogBasicLevel |
kOSKextLogLoadFlag,
"Kext loading now %sabled.", sLoadEnabled ? "en" : "dis");
}
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::getUnloadEnabled(void)
{
bool result;
IORecursiveLockLock(sKextLock);
result = sUnloadEnabled;
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::setUnloadEnabled(bool flag)
{
bool result;
IORecursiveLockLock(sKextLock);
result = sUnloadEnabled;
sUnloadEnabled = (flag ? true : false);
IORecursiveLockUnlock(sKextLock);
if (sUnloadEnabled != result) {
OSKextLog(/* kext */ NULL,
kOSKextLogBasicLevel |
kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Kext unloading now %sabled.", sUnloadEnabled ? "en" : "dis");
}
return result;
}
/*********************************************************************
* Do not call any function that takes sKextLock here!
*********************************************************************/
/* static */
bool
OSKext::getAutounloadEnabled(void)
{
bool result;
IORecursiveLockLock(sKextInnerLock);
result = sAutounloadEnabled ? true : false;
IORecursiveLockUnlock(sKextInnerLock);
return result;
}
/*********************************************************************
* Do not call any function that takes sKextLock here!
*********************************************************************/
/* static */
bool
OSKext::setAutounloadsEnabled(bool flag)
{
bool result;
IORecursiveLockLock(sKextInnerLock);
result = sAutounloadEnabled;
sAutounloadEnabled = (flag ? true : false);
if (!sAutounloadEnabled && sUnloadCallout) {
thread_call_cancel(sUnloadCallout);
}
if (sAutounloadEnabled != result) {
OSKextLog(/* kext */ NULL,
kOSKextLogBasicLevel |
kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Kext autounloading now %sabled.",
sAutounloadEnabled ? "en" : "dis");
}
IORecursiveLockUnlock(sKextInnerLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* instance method operating on OSKext field */
bool
OSKext::setAutounloadEnabled(bool flag)
{
bool result = flags.autounloadEnabled ? true : false;
flags.autounloadEnabled = flag ? (0 == flags.unloadUnsupported) : 0;
if (result != (flag ? true : false)) {
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag,
"Autounloading for kext %s now %sabled.",
getIdentifierCString(),
flags.autounloadEnabled ? "en" : "dis");
}
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::setKernelRequestsEnabled(bool flag)
{
bool result;
IORecursiveLockLock(sKextLock);
result = sKernelRequestsEnabled;
sKernelRequestsEnabled = flag ? true : false;
if (sKernelRequestsEnabled != result) {
OSKextLog(/* kext */ NULL,
kOSKextLogBasicLevel |
kOSKextLogGeneralFlag,
"Kernel requests now %sabled.",
sKernelRequestsEnabled ? "en" : "dis");
}
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::getKernelRequestsEnabled(void)
{
bool result;
IORecursiveLockLock(sKextLock);
result = sKernelRequestsEnabled;
IORecursiveLockUnlock(sKextLock);
return result;
}
static bool
segmentIsMutable(kernel_segment_command_t *seg)
{
/* Mutable segments have to have VM_PROT_WRITE */
if ((seg->maxprot & VM_PROT_WRITE) == 0) {
return false;
}
/* Exclude the __DATA_CONST segment */
if (strncmp(seg->segname, "__DATA_CONST", sizeof(seg->segname)) == 0) {
return false;
}
/* Exclude __LINKEDIT */
if (strncmp(seg->segname, "__LINKEDIT", sizeof(seg->segname)) == 0) {
return false;
}
return true;
}
#if PRAGMA_MARK
#pragma mark Kext Life Cycle
#endif
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSKext>
OSKext::withPrelinkedInfoDict(
OSDictionary * anInfoDict,
bool doCoalescedSlides,
kc_kind_t type)
{
OSSharedPtr<OSKext> newKext(OSMakeShared<OSKext>());
if (newKext && !newKext->initWithPrelinkedInfoDict(anInfoDict, doCoalescedSlides, type)) {
return NULL;
}
return newKext;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::initWithPrelinkedInfoDict(
OSDictionary * anInfoDict,
bool doCoalescedSlides,
kc_kind_t type)
{
bool result = false;
OSString * kextPath = NULL; // do not release
OSNumber * addressNum = NULL; // reused; do not release
OSNumber * lengthNum = NULL; // reused; do not release
OSBoolean * scratchBool = NULL; // do not release
void * data = NULL; // do not free
void * srcData = NULL; // do not free
OSSharedPtr<OSData> prelinkedExecutable;
uint32_t length = 0; // reused
uintptr_t kext_slide = PE_get_kc_slide(type);
bool shouldSaveSegments = false;
if (!super::init()) {
goto finish;
}
/* Get the path. Don't look for an arch-specific path property.
*/
kextPath = OSDynamicCast(OSString,
anInfoDict->getObject(kPrelinkBundlePathKey));
if (!setInfoDictionaryAndPath(anInfoDict, kextPath)) {
goto finish;
}
#if KASLR_KEXT_DEBUG
IOLog("kaslr: doCoalescedSlides %d kext %s \n", doCoalescedSlides, getIdentifierCString());
#endif
/* Also get the executable's bundle-relative path if present.
* Don't look for an arch-specific path property.
*/
executableRelPath.reset(OSDynamicCast(OSString,
anInfoDict->getObject(kPrelinkExecutableRelativePathKey)), OSRetain);
userExecutableRelPath.reset(OSDynamicCast(OSString,
anInfoDict->getObject(kCFBundleDriverKitExecutableKey)), OSRetain);
/* Don't need the paths to be in the info dictionary any more.
*/
anInfoDict->removeObject(kPrelinkBundlePathKey);
anInfoDict->removeObject(kPrelinkExecutableRelativePathKey);
scratchBool = OSDynamicCast(OSBoolean,
getPropertyForHostArch(kOSBundleRequireExplicitLoadKey));
if (scratchBool == kOSBooleanTrue) {
flags.requireExplicitLoad = 1;
}
/* Create an OSData wrapper around the linked executable.
*/
addressNum = OSDynamicCast(OSNumber,
anInfoDict->getObject(kPrelinkExecutableLoadKey));
if (addressNum && addressNum->unsigned64BitValue() != kOSKextCodelessKextLoadAddr) {
lengthNum = OSDynamicCast(OSNumber,
anInfoDict->getObject(kPrelinkExecutableSizeKey));
if (!lengthNum) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Kext %s can't find prelinked kext executable size.",
getIdentifierCString());
return result;
}
data = (void *) (((uintptr_t) (addressNum->unsigned64BitValue())) + kext_slide);
length = (uint32_t) (lengthNum->unsigned32BitValue());
#if KASLR_KEXT_DEBUG
IOLog("kaslr: unslid 0x%lx slid 0x%lx length %u - prelink executable \n",
(unsigned long)ml_static_unslide((vm_offset_t)data),
(unsigned long)data,
length);
#endif
anInfoDict->removeObject(kPrelinkExecutableLoadKey);
anInfoDict->removeObject(kPrelinkExecutableSizeKey);
/* If the kext's load address differs from its source address, allocate
* space in the kext map at the load address and copy the kext over.
*/
addressNum = OSDynamicCast(OSNumber, anInfoDict->getObject(kPrelinkExecutableSourceKey));
if (addressNum) {
srcData = (void *) (((uintptr_t) (addressNum->unsigned64BitValue())) + kext_slide);
#if KASLR_KEXT_DEBUG
IOLog("kaslr: unslid 0x%lx slid 0x%lx - prelink executable source \n",
(unsigned long)ml_static_unslide((vm_offset_t)srcData),
(unsigned long)srcData);
#endif
if (data != srcData) {
#if __LP64__
kern_return_t alloc_result;
alloc_result = kext_alloc((vm_offset_t *)&data, length, /* fixed */ TRUE);
if (alloc_result != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Failed to allocate space for prelinked kext %s.",
getIdentifierCString());
goto finish;
}
memcpy(data, srcData, length);
#else
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Error: prelinked kext %s - source and load addresses "
"differ on ILP32 architecture.",
getIdentifierCString());
goto finish;
#endif /* __LP64__ */
}
anInfoDict->removeObject(kPrelinkExecutableSourceKey);
}
prelinkedExecutable = OSData::withBytesNoCopy(data, length);
if (!prelinkedExecutable) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag | kOSKextLogArchiveFlag,
"Kext %s failed to create executable wrapper.",
getIdentifierCString());
goto finish;
}
#if VM_MAPPED_KEXTS
prelinkedExecutable->setDeallocFunction(osdata_kext_free);
#else
prelinkedExecutable->setDeallocFunction(osdata_phys_free);
#endif
setLinkedExecutable(prelinkedExecutable.get());
addressNum = OSDynamicCast(OSNumber,
anInfoDict->getObject(kPrelinkKmodInfoKey));
if (!addressNum) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Kext %s can't find prelinked kext kmod_info address.",
getIdentifierCString());
goto finish;
}
if (addressNum->unsigned64BitValue() != 0) {
kmod_info = (kmod_info_t *) (((uintptr_t) (addressNum->unsigned64BitValue())) + kext_slide);
if (kmod_info->address) {
kmod_info->address = (((uintptr_t)(kmod_info->address)) + kext_slide);
} else {
kmod_info->address = (uintptr_t)data;
kmod_info->size = length;
}
#if KASLR_KEXT_DEBUG
IOLog("kaslr: unslid 0x%lx slid 0x%lx - kmod_info \n",
(unsigned long)((vm_offset_t)kmod_info) - kext_slide,
(unsigned long)kmod_info);
IOLog("kaslr: unslid 0x%lx slid 0x%lx - kmod_info->address \n",
(unsigned long)((vm_offset_t)kmod_info->address) - kext_slide,
(unsigned long)kmod_info->address);
#endif
}
anInfoDict->removeObject(kPrelinkKmodInfoKey);
}
if ((addressNum = OSDynamicCast(OSNumber, anInfoDict->getObject("ModuleIndex")))) {
uintptr_t builtinTextStart;
uintptr_t builtinTextEnd;
flags.builtin = true;
builtinKmodIdx = addressNum->unsigned32BitValue();
assert(builtinKmodIdx < gBuiltinKmodsCount);
builtinTextStart = ((uintptr_t *)gBuiltinKmodsSectionStart->addr)[builtinKmodIdx];
builtinTextEnd = ((uintptr_t *)gBuiltinKmodsSectionStart->addr)[builtinKmodIdx + 1];
kmod_info = ((kmod_info_t **)gBuiltinKmodsSectionInfo->addr)[builtinKmodIdx];
kmod_info->address = builtinTextStart;
kmod_info->size = builtinTextEnd - builtinTextStart;
}
/* If the plist has a UUID for an interface, save that off.
*/
if (isInterface()) {
interfaceUUID.reset(OSDynamicCast(OSData,
anInfoDict->getObject(kPrelinkInterfaceUUIDKey)), OSRetain);
if (interfaceUUID) {
anInfoDict->removeObject(kPrelinkInterfaceUUIDKey);
}
}
result = (kOSReturnSuccess == slidePrelinkedExecutable(doCoalescedSlides));
if (!result) {
goto finish;
}
kc_type = type;
/* Exclude builtin and codeless kexts */
if (prelinkedExecutable && kmod_info) {
switch (kc_type) {
case KCKindPrimary:
shouldSaveSegments = (
getPropertyForHostArch(kOSMutableSegmentCopy) == kOSBooleanTrue ||
getPropertyForHostArch(kOSBundleAllowUserLoadKey) == kOSBooleanTrue);
if (shouldSaveSegments) {
flags.resetSegmentsFromImmutableCopy = 1;
} else {
flags.unloadUnsupported = 1;
}
break;
case KCKindPageable:
flags.resetSegmentsFromVnode = 1;
break;
case KCKindAuxiliary:
if (!pageableKCloaded) {
flags.resetSegmentsFromImmutableCopy = 1;
} else if (resetAuxKCSegmentOnUnload) {
flags.resetSegmentsFromVnode = 1;
} else {
flags.unloadUnsupported = 1;
}
break;
default:
break;
}
}
if (flags.resetSegmentsFromImmutableCopy) {
/* Save a pristine copy of the mutable segments */
kernel_segment_command_t *seg = NULL;
kernel_mach_header_t *k_mh = (kernel_mach_header_t *)kmod_info->address;
savedMutableSegments = OSArray::withCapacity(0);
for (seg = firstsegfromheader(k_mh); seg; seg = nextsegfromheader(k_mh, seg)) {
if (!segmentIsMutable(seg)) {
continue;
}
uint64_t unslid_vmaddr = seg->vmaddr - kext_slide;
uint64_t vmsize = seg->vmsize;
OSKextLog(this, kOSKextLogDebugLevel | kOSKextLogLoadFlag,
"Saving kext %s mutable segment %.*s %llx->%llx.", getIdentifierCString(), (int)strnlen(seg->segname, sizeof(seg->segname)), seg->segname, unslid_vmaddr, unslid_vmaddr + vmsize - 1);
OSSharedPtr<OSKextSavedMutableSegment> savedSegment = OSKextSavedMutableSegment::withSegment(seg);
if (!savedSegment) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Kext %s failed to save mutable segment %llx->%llx.", getIdentifierCString(), unslid_vmaddr, unslid_vmaddr + vmsize - 1);
result = kOSKextReturnInternalError;
goto finish;
}
savedMutableSegments->setObject(savedSegment);
}
}
if (doCoalescedSlides == false && !flags.resetSegmentsFromVnode) {
/*
* set VM protections now, wire pages for the old style Aux KC now,
* wire pages for the rest of the KC types at load time.
*/
result = (kOSReturnSuccess == setVMAttributes(true, (type == KCKindAuxiliary) ? true : false));
if (!result) {
goto finish;
}
}
flags.prelinked = true;
/* If we created a kext from prelink info,
* we must be booting from a prelinked kernel.
*/
sPrelinkBoot = true;
result = registerIdentifier();
finish:
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
OSSharedPtr<OSKext>
OSKext::withCodelessInfo(OSDictionary * anInfoDict)
{
OSSharedPtr<OSKext> newKext = OSMakeShared<OSKext>();
if (newKext && !newKext->initWithCodelessInfo(anInfoDict)) {
return NULL;
}
return newKext;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::initWithCodelessInfo(OSDictionary * anInfoDict)
{
bool result = false;
OSString * kextPath = NULL; // do not release
OSBoolean * scratchBool = NULL; // do not release
if (anInfoDict == NULL || !super::init()) {
goto finish;
}
/*
* Get the path. Don't look for an arch-specific path property.
*/
kextPath = OSDynamicCast(OSString,
anInfoDict->getObject(kKextRequestArgumentCodelessInfoBundlePathKey));
if (!kextPath) {
OSKextLog(NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Requested codeless kext dictionary does not contain the '%s' key",
kKextRequestArgumentCodelessInfoBundlePathKey);
goto finish;
}
uniquePersonalityProperties(anInfoDict);
if (!setInfoDictionaryAndPath(anInfoDict, kextPath)) {
goto finish;
}
/*
* This path is meant to initialize codeless kexts only. Refuse
* anything that looks like it has an executable and/or declares
* itself as a kernel component.
*/
if (declaresExecutable() || isKernelComponent()) {
OSKextLog(NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Refusing to register codeless kext that declares an executable/kernel component: %s",
getIdentifierCString());
goto finish;
}
if (strcmp(getIdentifierCString(), kIOExcludeListBundleID) == 0) {
boolean_t updated = updateExcludeList(infoDict.get());
if (updated) {
OSKextLog(this,
kOSKextLogDebugLevel | kOSKextLogLoadFlag,
"KextExcludeList was updated to version: %lld", sExcludeListVersion);
}
}
kc_type = KCKindNone;
scratchBool = OSDynamicCast(OSBoolean,
getPropertyForHostArch(kOSBundleRequireExplicitLoadKey));
if (scratchBool == kOSBooleanTrue) {
flags.requireExplicitLoad = 1;
}
/* Also get the executable's bundle-relative path if present.
* Don't look for an arch-specific path property.
*/
userExecutableRelPath.reset(OSDynamicCast(OSString,
anInfoDict->getObject(kCFBundleDriverKitExecutableKey)), OSRetain);
/* remove unnecessary paths from the info dict */
anInfoDict->removeObject(kKextRequestArgumentCodelessInfoBundlePathKey);
result = registerIdentifier();
finish:
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::setAllVMAttributes(void)
{
OSSharedPtr<OSCollectionIterator> kextIterator;
const OSSymbol * thisID = NULL; // do not release
IORecursiveLockLock(sKextLock);
kextIterator = OSCollectionIterator::withCollection(sKextsByID.get());
if (!kextIterator) {
goto finish;
}
while ((thisID = OSDynamicCast(OSSymbol, kextIterator->getNextObject()))) {
OSKext * thisKext; // do not release
thisKext = OSDynamicCast(OSKext, sKextsByID->getObject(thisID));
if (!thisKext || thisKext->isInterface() || !thisKext->declaresExecutable()) {
continue;
}
if (!thisKext->flags.resetSegmentsFromVnode) {
/*
* set VM protections now, wire pages for the old style Aux KC now,
* wire pages for the rest of the KC types at load time.
*/
thisKext->setVMAttributes(true, (thisKext->kc_type == KCKindAuxiliary) ? true : false);
}
}
finish:
IORecursiveLockUnlock(sKextLock);
return;
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSKext>
OSKext::withBooterData(
OSString * deviceTreeName,
OSData * booterData)
{
OSSharedPtr<OSKext> newKext(OSMakeShared<OSKext>());
if (newKext && !newKext->initWithBooterData(deviceTreeName, booterData)) {
return NULL;
}
return newKext;
}
/*********************************************************************
*********************************************************************/
typedef struct _BooterKextFileInfo {
uint32_t infoDictPhysAddr;
uint32_t infoDictLength;
uint32_t executablePhysAddr;
uint32_t executableLength;
uint32_t bundlePathPhysAddr;
uint32_t bundlePathLength;
} _BooterKextFileInfo;
bool
OSKext::initWithBooterData(
OSString * deviceTreeName,
OSData * booterData)
{
bool result = false;
_BooterKextFileInfo * kextFileInfo = NULL; // do not free
char * infoDictAddr = NULL; // do not free
void * executableAddr = NULL; // do not free
char * bundlePathAddr = NULL; // do not free
OSDictionary * theInfoDict = NULL; // do not release
OSSharedPtr<OSObject> parsedXML;
OSSharedPtr<OSString> kextPath;
OSSharedPtr<OSString> errorString;
OSSharedPtr<OSData> executable;
if (!super::init()) {
goto finish;
}
kextFileInfo = (_BooterKextFileInfo *)booterData->getBytesNoCopy();
if (!kextFileInfo) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"No booter-provided data for kext device tree entry %s.",
deviceTreeName->getCStringNoCopy());
goto finish;
}
/* The info plist must exist or we can't read the kext.
*/
if (!kextFileInfo->infoDictPhysAddr || !kextFileInfo->infoDictLength) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"No kext info dictionary for booter device tree entry %s.",
deviceTreeName->getCStringNoCopy());
goto finish;
}
infoDictAddr = (char *)ml_static_ptovirt(kextFileInfo->infoDictPhysAddr);
if (!infoDictAddr) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Can't translate physical address 0x%x of kext info dictionary "
"for device tree entry %s.",
(int)kextFileInfo->infoDictPhysAddr,
deviceTreeName->getCStringNoCopy());
goto finish;
}
parsedXML = OSUnserializeXML(infoDictAddr, errorString);
if (parsedXML) {
theInfoDict = OSDynamicCast(OSDictionary, parsedXML.get());
}
if (!theInfoDict) {
const char * errorCString = "(unknown error)";
if (errorString && errorString->getCStringNoCopy()) {
errorCString = errorString->getCStringNoCopy();
} else if (parsedXML) {
errorCString = "not a dictionary";
}
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Error unserializing info dictionary for device tree entry %s: %s.",
deviceTreeName->getCStringNoCopy(), errorCString);
goto finish;
}
/* A bundle path is not mandatory.
*/
if (kextFileInfo->bundlePathPhysAddr && kextFileInfo->bundlePathLength) {
bundlePathAddr = (char *)ml_static_ptovirt(kextFileInfo->bundlePathPhysAddr);
if (!bundlePathAddr) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Can't translate physical address 0x%x of kext bundle path "
"for device tree entry %s.",
(int)kextFileInfo->bundlePathPhysAddr,
deviceTreeName->getCStringNoCopy());
goto finish;
}
bundlePathAddr[kextFileInfo->bundlePathLength - 1] = '\0'; // just in case!
kextPath = OSString::withCString(bundlePathAddr);
if (!kextPath) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Failed to create wrapper for device tree entry %s kext path %s.",
deviceTreeName->getCStringNoCopy(), bundlePathAddr);
goto finish;
}
}
if (!setInfoDictionaryAndPath(theInfoDict, kextPath.get())) {
goto finish;
}
/* An executable is not mandatory.
*/
if (kextFileInfo->executablePhysAddr && kextFileInfo->executableLength) {
executableAddr = (void *)ml_static_ptovirt(kextFileInfo->executablePhysAddr);
if (!executableAddr) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Can't translate physical address 0x%x of kext executable "
"for device tree entry %s.",
(int)kextFileInfo->executablePhysAddr,
deviceTreeName->getCStringNoCopy());
goto finish;
}
executable = OSData::withBytesNoCopy(executableAddr,
kextFileInfo->executableLength);
if (!executable) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Failed to create executable wrapper for device tree entry %s.",
deviceTreeName->getCStringNoCopy());
goto finish;
}
/* A kext with an executable needs to retain the whole booterData
* object to keep the executable in memory.
*/
if (!setExecutable(executable.get(), booterData)) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Failed to set kext executable for device tree entry %s.",
deviceTreeName->getCStringNoCopy());
goto finish;
}
}
result = registerIdentifier();
finish:
return result;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::registerIdentifier(void)
{
bool result = false;
OSKext * existingKext = NULL; // do not release
bool existingIsLoaded = false;
bool existingIsPrelinked = false;
bool existingIsCodeless = false;
bool existingIsDext = false;
OSKextVersion newVersion = -1;
OSKextVersion existingVersion = -1;
char newVersionCString[kOSKextVersionMaxLength];
char existingVersionCString[kOSKextVersionMaxLength];
OSSharedPtr<OSData> newUUID;
OSSharedPtr<OSData> existingUUID;
IORecursiveLockLock(sKextLock);
/* Get the new kext's version for checks & log messages.
*/
newVersion = getVersion();
OSKextVersionGetString(newVersion, newVersionCString,
kOSKextVersionMaxLength);
/* If we don't have an existing kext with this identifier,
* just record the new kext and we're done!
*/
existingKext = OSDynamicCast(OSKext, sKextsByID->getObject(bundleID.get()));
if (!existingKext) {
sKextsByID->setObject(bundleID.get(), this);
result = true;
goto finish;
}
/* Get the existing kext's version for checks & log messages.
*/
existingVersion = existingKext->getVersion();
OSKextVersionGetString(existingVersion,
existingVersionCString, kOSKextVersionMaxLength);
existingIsLoaded = existingKext->isLoaded();
existingIsPrelinked = existingKext->isPrelinked();
existingIsDext = existingKext->isDriverKit();
existingIsCodeless = !existingKext->declaresExecutable() && !existingIsDext;
/* If we have a non-codeless kext with this identifier that's already
* loaded/prelinked, we can't use the new one, but let's be really
* thorough and check how the two are related for a precise diagnostic
* log message.
*
* This check is valid for kexts that declare an executable and for
* dexts, but not for codeless kexts - we can just replace those.
*/
if ((!existingIsCodeless || existingIsDext) &&
(existingIsLoaded || existingIsPrelinked)) {
bool sameVersion = (newVersion == existingVersion);
bool sameExecutable = true; // assume true unless we have UUIDs
/* Only get the UUID if the existing kext is loaded. Doing so
* might have to uncompress an mkext executable and we shouldn't
* take that hit when neither kext is loaded.
*
* Note: there is no decompression that happens when all kexts
* are loaded from kext collecitons.
*/
newUUID = copyUUID();
existingUUID = existingKext->copyUUID();
if (existingIsDext && !isDriverKit()) {
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogKextBookkeepingFlag,
"Notice - new kext %s, v%s matches a %s dext"
"with the same bundle ID, v%s.",
getIdentifierCString(), newVersionCString,
(existingIsLoaded ? "loaded" : "prelinked"),
existingVersionCString);
goto finish;
}
/* I'm entirely too paranoid about checking equivalence of executables,
* but I remember nasty problems with it in the past.
*
* - If we have UUIDs for both kexts, compare them.
* - If only one kext has a UUID, they're definitely different.
*/
if (newUUID && existingUUID) {
sameExecutable = newUUID->isEqualTo(existingUUID.get());
} else if (newUUID || existingUUID) {
sameExecutable = false;
}
if (!newUUID && !existingUUID) {
/* If there are no UUIDs, we can't really tell that the executables
* are *different* without a lot of work; the loaded kext's
* unrelocated executable is no longer around (and we never had it
* in-kernel for a prelinked kext). We certainly don't want to do
* a whole fake link for the new kext just to compare, either.
*/
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogKextBookkeepingFlag,
"Notice - new kext %s, v%s matches %s kext "
"but can't determine if executables are the same (no UUIDs).",
getIdentifierCString(),
newVersionCString,
(existingIsLoaded ? "loaded" : "prelinked"));
}
if (sameVersion && sameExecutable) {
OSKextLog(this,
(existingIsLoaded ? kOSKextLogWarningLevel : kOSKextLogStepLevel) |
kOSKextLogKextBookkeepingFlag,
"Refusing new kext %s, v%s: a %s copy is already present "
"(same version and executable).",
getIdentifierCString(), newVersionCString,
(existingIsLoaded ? "loaded" : "prelinked"));
} else {
if (!sameVersion) {
/* This condition is significant so log it under warnings.
*/
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogKextBookkeepingFlag,
"Refusing new kext %s, v%s: already have %s v%s.",
getIdentifierCString(),
newVersionCString,
(existingIsLoaded ? "loaded" : "prelinked"),
existingVersionCString);
} else {
/* This condition is significant so log it under warnings.
*/
OSKextLog(this,
kOSKextLogWarningLevel | kOSKextLogKextBookkeepingFlag,
"Refusing new kext %s, v%s: a %s copy with a different "
"executable UUID is already present.",
getIdentifierCString(), newVersionCString,
(existingIsLoaded ? "loaded" : "prelinked"));
}
}
goto finish;
} /* if ((!existingIsCodeless || existingIsDext) && (existingIsLoaded || existingIsPrelinked)) */
/* Refuse to allow an existing loaded codeless kext be replaced by a
* normal kext with the same bundle ID.
*/
if (existingIsCodeless && declaresExecutable()) {
OSKextLog(this,
kOSKextLogWarningLevel | kOSKextLogKextBookkeepingFlag,
"Refusing new kext %s, v%s: a codeless copy is already %s",
getIdentifierCString(), newVersionCString,
(existingIsLoaded ? "loaded" : "prelinked"));
goto finish;
}
/* Dexts packaged in the BootKC will be protected against replacement
* by non-dexts by the logic above which checks if they are prelinked.
* Dexts which are prelinked into the System KC will be registered
* before any other kexts in the AuxKC are registered, and we never
* put dexts in the AuxKC. Therefore, there is no need to check if an
* existing object is a dext and is being replaced by a non-dext.
* The scenario cannot happen by construction.
*
* See: OSKext::loadFileSetKexts()
*/
/* We have two nonloaded/nonprelinked kexts, so our decision depends on whether
* user loads are happening or if we're still in early boot. User agents are
* supposed to resolve dependencies topside and include only the exact
* kexts needed; so we always accept the new kext (in fact we should never
* see an older unloaded copy hanging around).
*/
if (sUserLoadsActive) {
sKextsByID->setObject(bundleID.get(), this);
result = true;
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogKextBookkeepingFlag,
"Dropping old copy of kext %s (v%s) for newly-added (v%s).",
getIdentifierCString(),
existingVersionCString,
newVersionCString);
goto finish;
}
/* During early boot, the kext with the highest version always wins out.
* Prelinked kernels will never hit this, but mkexts and booter-read
* kexts might have duplicates.
*/
if (newVersion > existingVersion) {
sKextsByID->setObject(bundleID.get(), this);
result = true;
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogKextBookkeepingFlag,
"Dropping lower version (v%s) of registered kext %s for higher (v%s).",
existingVersionCString,
getIdentifierCString(),
newVersionCString);
} else {
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogKextBookkeepingFlag,
"Kext %s is already registered with a higher/same version (v%s); "
"dropping newly-added (v%s).",
getIdentifierCString(),
existingVersionCString,
newVersionCString);
}
/* result has been set appropriately by now. */
finish:
IORecursiveLockUnlock(sKextLock);
if (result) {
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogKextBookkeepingFlag,
"Kext %s, v%s registered and available for loading.",
getIdentifierCString(), newVersionCString);
}
return result;
}
/*********************************************************************
* Does the bare minimum validation to look up a kext.
* All other validation is done on the spot as needed.
**********************************************************************/
bool
OSKext::setInfoDictionaryAndPath(
OSDictionary * aDictionary,
OSString * aPath)
{
bool result = false;
OSString * bundleIDString = NULL; // do not release
OSString * versionString = NULL; // do not release
OSString * compatibleVersionString = NULL; // do not release
const char * versionCString = NULL; // do not free
const char * compatibleVersionCString = NULL; // do not free
OSBoolean * scratchBool = NULL; // do not release
OSDictionary * scratchDict = NULL; // do not release
if (infoDict) {
panic("Attempt to set info dictionary on a kext "
"that already has one (%s).",
getIdentifierCString());
}
if (!aDictionary || !OSDynamicCast(OSDictionary, aDictionary)) {
goto finish;
}
infoDict.reset(aDictionary, OSRetain);
/* Check right away if the info dictionary has any log flags.
*/
scratchBool = OSDynamicCast(OSBoolean,
getPropertyForHostArch(kOSBundleEnableKextLoggingKey));
if (scratchBool == kOSBooleanTrue) {
flags.loggingEnabled = 1;
}
/* The very next thing to get is the bundle identifier. Unlike
* in user space, a kext with no bundle identifier gets axed
* immediately.
*/
bundleIDString = OSDynamicCast(OSString,
getPropertyForHostArch(kCFBundleIdentifierKey));
if (!bundleIDString) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag,
"CFBundleIdentifier missing/invalid type in kext %s.",
aPath ? aPath->getCStringNoCopy() : "(unknown)");
goto finish;
}
bundleID = OSSymbol::withString(bundleIDString);
if (!bundleID) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag,
"Can't copy bundle identifier as symbol for kext %s.",
bundleIDString->getCStringNoCopy());
goto finish;
}
/* Save the path if we got one (it should always be available but it's
* just something nice to have for bookkeeping).
*/
if (aPath) {
path.reset(aPath, OSRetain);
}
/*****
* Minimal validation to initialize. We'll do other validation on the spot.
*/
if (bundleID->getLength() >= KMOD_MAX_NAME) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag,
"Kext %s error - CFBundleIdentifier over max length %d.",
getIdentifierCString(), KMOD_MAX_NAME - 1);
goto finish;
}
version = compatibleVersion = -1;
versionString = OSDynamicCast(OSString,
getPropertyForHostArch(kCFBundleVersionKey));
if (!versionString) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag,
"Kext %s error - CFBundleVersion missing/invalid type.",
getIdentifierCString());
goto finish;
}
versionCString = versionString->getCStringNoCopy();
version = OSKextParseVersionString(versionCString);
if (version < 0) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag,
"Kext %s error - CFBundleVersion bad value '%s'.",
getIdentifierCString(), versionCString);
goto finish;
}
compatibleVersion = -1; // set to illegal value for kexts that don't have
compatibleVersionString = OSDynamicCast(OSString,
getPropertyForHostArch(kOSBundleCompatibleVersionKey));
if (compatibleVersionString) {
compatibleVersionCString = compatibleVersionString->getCStringNoCopy();
compatibleVersion = OSKextParseVersionString(compatibleVersionCString);
if (compatibleVersion < 0) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag,
"Kext %s error - OSBundleCompatibleVersion bad value '%s'.",
getIdentifierCString(), compatibleVersionCString);
goto finish;
}
if (compatibleVersion > version) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag,
"Kext %s error - %s %s > %s %s (must be <=).",
getIdentifierCString(),
kOSBundleCompatibleVersionKey, compatibleVersionCString,
kCFBundleVersionKey, versionCString);
goto finish;
}
}
/* Check to see if this kext is in exclude list */
if (isInExcludeList()) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Kext %s is in exclude list, not loadable",
getIdentifierCString());
goto finish;
}
/* Set flags for later use if the infoDict gets flushed. We only
* check for true values, not false ones(!)
*/
scratchBool = OSDynamicCast(OSBoolean,
getPropertyForHostArch(kOSBundleIsInterfaceKey));
if (scratchBool == kOSBooleanTrue) {
flags.interface = 1;
}
scratchBool = OSDynamicCast(OSBoolean,
getPropertyForHostArch(kOSKernelResourceKey));
if (scratchBool == kOSBooleanTrue) {
flags.kernelComponent = 1;
flags.interface = 1; // xxx - hm. the kernel itself isn't an interface...
flags.started = 1;
/* A kernel component has one implicit dependency on the kernel.
*/
flags.hasAllDependencies = 1;
}
/* Make sure common string values in personalities are uniqued to OSSymbols.
*/
scratchDict = OSDynamicCast(OSDictionary,
getPropertyForHostArch(kIOKitPersonalitiesKey));
if (scratchDict) {
uniquePersonalityProperties(scratchDict);
}
result = true;
finish:
return result;
}
/*********************************************************************
* Not used for prelinked kernel boot as there is no unrelocated
* executable.
*********************************************************************/
bool
OSKext::setExecutable(
OSData * anExecutable,
OSData * externalData,
bool externalDataIsMkext)
{
bool result = false;
const char * executableKey = NULL; // do not free
if (!anExecutable) {
infoDict->removeObject(_kOSKextExecutableKey);
infoDict->removeObject(_kOSKextMkextExecutableReferenceKey);
infoDict->removeObject(_kOSKextExecutableExternalDataKey);
result = true;
goto finish;
}
if (infoDict->getObject(_kOSKextExecutableKey) ||
infoDict->getObject(_kOSKextMkextExecutableReferenceKey)) {
panic("Attempt to set an executable on a kext "
"that already has one (%s).",
getIdentifierCString());
goto finish;
}
if (externalDataIsMkext) {
executableKey = _kOSKextMkextExecutableReferenceKey;
} else {
executableKey = _kOSKextExecutableKey;
}
if (anExecutable) {
infoDict->setObject(executableKey, anExecutable);
if (externalData) {
infoDict->setObject(_kOSKextExecutableExternalDataKey, externalData);
}
}
result = true;
finish:
return result;
}
/*********************************************************************
*********************************************************************/
static void
uniqueStringPlistProperty(OSDictionary * dict, const char * key)
{
OSObject * value = NULL; // do not release
OSString * stringValue = NULL; // do not release
OSSharedPtr<const OSSymbol> symbolValue;
value = dict->getObject(key);
if (!value) {
goto finish;
}
if (OSDynamicCast(OSSymbol, value)) {
/* this is already an OSSymbol: we're good */
goto finish;
}
stringValue = OSDynamicCast(OSString, value);
if (!stringValue) {
goto finish;
}
symbolValue = OSSymbol::withString(stringValue);
if (!symbolValue) {
goto finish;
}
dict->setObject(key, symbolValue.get());
finish:
return;
}
/*********************************************************************
*********************************************************************/
static void
uniqueStringPlistProperty(OSDictionary * dict, const OSString * key)
{
OSObject * value = NULL; // do not release
OSString * stringValue = NULL; // do not release
OSSharedPtr<const OSSymbol> symbolValue;
value = dict->getObject(key);
if (!value) {
goto finish;
}
if (OSDynamicCast(OSSymbol, value)) {
/* this is already an OSSymbol: we're good */
goto finish;
}
stringValue = OSDynamicCast(OSString, value);
if (!stringValue) {
goto finish;
}
symbolValue = OSSymbol::withString(stringValue);
if (!symbolValue) {
goto finish;
}
dict->setObject(key, symbolValue.get());
finish:
return;
}
void
OSKext::uniquePersonalityProperties(OSDictionary * personalityDict)
{
OSKext::uniquePersonalityProperties(personalityDict, true);
}
/*********************************************************************
* Replace common personality property values with uniqued instances
* to save on wired memory.
*********************************************************************/
/* static */
void
OSKext::uniquePersonalityProperties(OSDictionary * personalityDict, bool defaultAddKernelBundleIdentifier)
{
/* Properties every personality has.
*/
uniqueStringPlistProperty(personalityDict, kCFBundleIdentifierKey);
uniqueStringPlistProperty(personalityDict, kIOProviderClassKey);
uniqueStringPlistProperty(personalityDict, gIOClassKey.get());
if (personalityDict->getObject(kCFBundleIdentifierKernelKey)) {
uniqueStringPlistProperty(personalityDict, kCFBundleIdentifierKernelKey);
} else if (defaultAddKernelBundleIdentifier) {
personalityDict->setObject(kCFBundleIdentifierKernelKey, personalityDict->getObject(kCFBundleIdentifierKey));
}
/* Other commonly used properties.
*/
uniqueStringPlistProperty(personalityDict, gIOMatchCategoryKey);
uniqueStringPlistProperty(personalityDict, gIOResourceMatchKey);
uniqueStringPlistProperty(personalityDict, gIOUserClientClassKey);
uniqueStringPlistProperty(personalityDict, "HIDDefaultBehavior");
uniqueStringPlistProperty(personalityDict, "HIDPointerAccelerationType");
uniqueStringPlistProperty(personalityDict, "HIDRemoteControlType");
uniqueStringPlistProperty(personalityDict, "HIDScrollAccelerationType");
uniqueStringPlistProperty(personalityDict, "IOPersonalityPublisher");
uniqueStringPlistProperty(personalityDict, "Physical Interconnect");
uniqueStringPlistProperty(personalityDict, "Physical Interconnect Location");
uniqueStringPlistProperty(personalityDict, "Vendor");
uniqueStringPlistProperty(personalityDict, "Vendor Identification");
uniqueStringPlistProperty(personalityDict, "Vendor Name");
uniqueStringPlistProperty(personalityDict, "bConfigurationValue");
uniqueStringPlistProperty(personalityDict, "bInterfaceNumber");
uniqueStringPlistProperty(personalityDict, "idProduct");
return;
}
/*********************************************************************
*********************************************************************/
void
OSKext::free(void)
{
if (isLoaded()) {
panic("Attempt to free loaded kext %s.", getIdentifierCString());
}
infoDict.reset();
bundleID.reset();
path.reset();
executableRelPath.reset();
userExecutableRelPath.reset();
dependencies.reset();
linkedExecutable.reset();
metaClasses.reset();
interfaceUUID.reset();
driverKitUUID.reset();
if (isInterface() && kmod_info) {
kfree(kmod_info, sizeof(kmod_info_t));
}
super::free();
return;
}
#if PRAGMA_MARK
#pragma mark Mkext files
#endif
#if CONFIG_KXLD
/*
* mkext archives are really only relevant on kxld-enabled kernels.
* Without a dynamic kernel linker, we don't need to support any mkexts.
*/
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::readMkextArchive(OSData * mkextData,
uint32_t * checksumPtr)
{
OSReturn result = kOSKextReturnBadData;
uint32_t mkextLength = 0;
mkext_header * mkextHeader = NULL; // do not free
uint32_t mkextVersion = 0;
/* Note default return of kOSKextReturnBadData above.
*/
mkextLength = mkextData->getLength();
if (mkextLength < sizeof(mkext_basic_header)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive too small to be valid.");
goto finish;
}
mkextHeader = (mkext_header *)mkextData->getBytesNoCopy();
if (MKEXT_GET_MAGIC(mkextHeader) != MKEXT_MAGIC ||
MKEXT_GET_SIGNATURE(mkextHeader) != MKEXT_SIGN) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive has invalid magic or signature.");
goto finish;
}
if (MKEXT_GET_LENGTH(mkextHeader) != mkextLength) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive recorded length doesn't match actual file length.");
goto finish;
}
mkextVersion = MKEXT_GET_VERSION(mkextHeader);
if (mkextVersion == MKEXT_VERS_2) {
result = OSKext::readMkext2Archive(mkextData, NULL, checksumPtr);
} else {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive of unsupported mkext version 0x%x.", mkextVersion);
result = kOSKextReturnUnsupported;
}
finish:
return result;
}
/*********************************************************************
* Assumes magic, signature, version, length have been checked.
* xxx - need to add further bounds checking for each file entry
*
* Should keep track of all kexts created so far, and if we hit a
* fatal error halfway through, remove those kexts. If we've dropped
* an older version that had already been read, whoops! Might want to
* add a level of buffering?
*********************************************************************/
/* static */
OSReturn
OSKext::readMkext2Archive(
OSData * mkextData,
OSDictionary ** mkextPlistOut,
uint32_t * checksumPtr)
{
OSReturn result = kOSReturnError;
uint32_t mkextLength;
mkext2_header * mkextHeader = NULL; // do not free
void * mkextEnd = NULL; // do not free
uint32_t mkextVersion;
uint8_t * crc_address = NULL;
size_t crc_buffer_size = 0;
uint32_t checksum;
uint32_t mkextPlistOffset;
uint32_t mkextPlistCompressedSize;
char * mkextPlistEnd = NULL; // do not free
uint32_t mkextPlistFullSize;
OSSharedPtr<OSString> errorString;
OSSharedPtr<OSData> mkextPlistUncompressedData;
const char * mkextPlistDataBuffer = NULL; // do not free
OSSharedPtr<OSObject> parsedXML;
OSDictionary * mkextPlist = NULL; // do not release
OSArray * mkextInfoDictArray = NULL; // do not release
uint32_t count, i;
kc_format_t kc_format;
if (!PE_get_primary_kc_format(&kc_format)) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Unable to determine primary KC format");
goto finish;
}
mkextLength = mkextData->getLength();
mkextHeader = (mkext2_header *)mkextData->getBytesNoCopy();
mkextEnd = (char *)mkextHeader + mkextLength;
mkextVersion = MKEXT_GET_VERSION(mkextHeader);
crc_address = (u_int8_t *)&mkextHeader->version;
crc_buffer_size = (uintptr_t)mkextHeader +
MKEXT_GET_LENGTH(mkextHeader) - (uintptr_t)crc_address;
if (crc_buffer_size > INT32_MAX) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive size is too large (%lu > INT32_MAX).",
crc_buffer_size);
result = kOSKextReturnBadData;
goto finish;
}
checksum = mkext_adler32(crc_address, (int32_t)crc_buffer_size);
if (MKEXT_GET_CHECKSUM(mkextHeader) != checksum) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive has bad checksum.");
result = kOSKextReturnBadData;
goto finish;
}
if (checksumPtr) {
*checksumPtr = checksum;
}
/* Check that the CPU type & subtype match that of the running kernel. */
if (MKEXT_GET_CPUTYPE(mkextHeader) == (UInt32)CPU_TYPE_ANY) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive must have a specific CPU type.");
result = kOSKextReturnBadData;
goto finish;
} else {
if ((UInt32)_mh_execute_header.cputype !=
MKEXT_GET_CPUTYPE(mkextHeader)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive does not match the running kernel's CPU type.");
result = kOSKextReturnArchNotFound;
goto finish;
}
}
mkextPlistOffset = MKEXT2_GET_PLIST(mkextHeader);
mkextPlistCompressedSize = MKEXT2_GET_PLIST_COMPSIZE(mkextHeader);
mkextPlistEnd = (char *)mkextHeader + mkextPlistOffset +
mkextPlistCompressedSize;
if (mkextPlistEnd > mkextEnd) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive file overrun.");
result = kOSKextReturnBadData;
}
mkextPlistFullSize = MKEXT2_GET_PLIST_FULLSIZE(mkextHeader);
if (mkextPlistCompressedSize) {
mkextPlistUncompressedData = sKernelKext->extractMkext2FileData(
(UInt8 *)mkextHeader + mkextPlistOffset,
"plist",
mkextPlistCompressedSize, mkextPlistFullSize);
if (!mkextPlistUncompressedData) {
goto finish;
}
mkextPlistDataBuffer = (const char *)
mkextPlistUncompressedData->getBytesNoCopy();
} else {
mkextPlistDataBuffer = (const char *)mkextHeader + mkextPlistOffset;
}
/* IOCFSerialize added a nul byte to the end of the string. Very nice of it.
*/
parsedXML = OSUnserializeXML(mkextPlistDataBuffer, errorString);
if (parsedXML) {
mkextPlist = OSDynamicCast(OSDictionary, parsedXML.get());
}
if (!mkextPlist) {
const char * errorCString = "(unknown error)";
if (errorString && errorString->getCStringNoCopy()) {
errorCString = errorString->getCStringNoCopy();
} else if (parsedXML) {
errorCString = "not a dictionary";
}
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Error unserializing mkext plist: %s.", errorCString);
goto finish;
}
mkextInfoDictArray = OSDynamicCast(OSArray,
mkextPlist->getObject(kMKEXTInfoDictionariesKey));
if (!mkextInfoDictArray) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext archive contains no kext info dictionaries.");
goto finish;
}
count = mkextInfoDictArray->getCount();
for (i = 0; i < count; i++) {
OSDictionary * infoDict;
infoDict = OSDynamicCast(OSDictionary,
mkextInfoDictArray->getObject(i));
/* Create the kext for the entry, then release it, because the
* kext system keeps them around until explicitly removed.
* Any creation/registration failures are already logged for us.
*/
if (infoDict) {
OSSharedPtr<OSKext> newKext = OSKext::withMkext2Info(infoDict, mkextData);
/* Fail dynamic loading of a kext when booted from MH_FILESET */
if (kc_format == KCFormatFileset &&
newKext &&
!(newKext->isPrelinked()) &&
newKext->declaresExecutable()) {
result = kOSReturnError;
printf("Kext LOG: Dynamic loading of kext denied for kext %s\n",
newKext->getIdentifier() ? newKext->getIdentifierCString() : "unknown kext");
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Dynamic loading of kext denied for kext %s\n",
newKext->getIdentifier() ? newKext->getIdentifierCString() : "unknown kext");
goto finish;
}
}
}
/* If the caller needs the plist, hand them back our copy
*/
if (mkextPlistOut) {
*mkextPlistOut = mkextPlist;
parsedXML.detach();
}
/* Even if we didn't keep any kexts from the mkext, we may have a load
* request to process, so we are successful (no errors occurred).
*/
result = kOSReturnSuccess;
finish:
return result;
}
/* static */
OSReturn
OSKext::readMkext2Archive(
OSData * mkextData,
OSSharedPtr<OSDictionary> &mkextPlistOut,
uint32_t * checksumPtr)
{
OSDictionary * mkextPlist = NULL;
OSReturn ret;
if (kOSReturnSuccess == (ret = readMkext2Archive(mkextData,
&mkextPlist,
checksumPtr))) {
mkextPlistOut.reset(mkextPlist, OSNoRetain);
}
return ret;
}
/*********************************************************************
*********************************************************************/
/* static */
OSSharedPtr<OSKext>
OSKext::withMkext2Info(
OSDictionary * anInfoDict,
OSData * mkextData)
{
OSSharedPtr<OSKext> newKext = OSMakeShared<OSKext>();
if (newKext && !newKext->initWithMkext2Info(anInfoDict, mkextData)) {
return NULL;
}
return newKext;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::initWithMkext2Info(
OSDictionary * anInfoDict,
OSData * mkextData)
{
bool result = false;
OSString * kextPath = NULL; // do not release
OSNumber * executableOffsetNum = NULL; // do not release
OSSharedPtr<OSData> executable;
if (anInfoDict == NULL || !super::init()) {
goto finish;
}
/* Get the path. Don't look for an arch-specific path property.
*/
kextPath = OSDynamicCast(OSString,
anInfoDict->getObject(kMKEXTBundlePathKey));
if (!setInfoDictionaryAndPath(anInfoDict, kextPath)) {
goto finish;
}
/* If we have a path to the executable, save it.
*/
executableRelPath.reset(OSDynamicCast(OSString,
anInfoDict->getObject(kMKEXTExecutableRelativePathKey)), OSRetain);
/* Don't need the paths to be in the info dictionary any more.
*/
anInfoDict->removeObject(kMKEXTBundlePathKey);
anInfoDict->removeObject(kMKEXTExecutableRelativePathKey);
executableOffsetNum = OSDynamicCast(OSNumber,
infoDict->getObject(kMKEXTExecutableKey));
if (executableOffsetNum) {
executable = createMkext2FileEntry(mkextData,
executableOffsetNum, "executable");
infoDict->removeObject(kMKEXTExecutableKey);
if (!executable) {
goto finish;
}
if (!setExecutable(executable.get(), mkextData, true)) {
goto finish;
}
}
result = registerIdentifier();
finish:
return result;
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSData>
OSKext::createMkext2FileEntry(
OSData * mkextData,
OSNumber * offsetNum,
const char * name)
{
OSSharedPtr<OSData> result;
MkextEntryRef entryRef;
uint8_t * mkextBuffer = (uint8_t *)mkextData->getBytesNoCopy();
uint32_t entryOffset = offsetNum->unsigned32BitValue();
result = OSData::withCapacity(sizeof(entryRef));
if (!result) {
goto finish;
}
entryRef.mkext = (mkext_basic_header *)mkextBuffer;
entryRef.fileinfo = mkextBuffer + entryOffset;
if (!result->appendBytes(&entryRef, sizeof(entryRef))) {
result.reset();
goto finish;
}
finish:
if (!result) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Can't create wrapper for mkext file entry '%s' of kext %s.",
name, getIdentifierCString());
}
return result;
}
/*********************************************************************
*********************************************************************/
extern "C" {
static void * z_alloc(void *, u_int items, u_int size);
static void z_free(void *, void *ptr);
typedef struct z_mem {
uint32_t alloc_size;
uint8_t data[0];
} z_mem;
/*
* Space allocation and freeing routines for use by zlib routines.
*/
void *
z_alloc(void * notused __unused, u_int num_items, u_int size)
{
void * result = NULL;
z_mem * zmem = NULL;
uint64_t total = ((uint64_t)num_items) * ((uint64_t)size);
//Check for overflow due to multiplication
if (total > UINT32_MAX) {
panic("z_alloc(%p, %x, %x): overflow caused by %x * %x\n",
notused, num_items, size, num_items, size);
}
uint64_t allocSize64 = total + ((uint64_t)sizeof(zmem));
//Check for overflow due to addition
if (allocSize64 > UINT32_MAX) {
panic("z_alloc(%p, %x, %x): overflow caused by %x + %lx\n",
notused, num_items, size, (uint32_t)total, sizeof(zmem));
}
uint32_t allocSize = (uint32_t)allocSize64;
zmem = (z_mem *)kheap_alloc_tag(KHEAP_DATA_BUFFERS, allocSize,
Z_WAITOK, VM_KERN_MEMORY_OSKEXT);
if (!zmem) {
goto finish;
}
zmem->alloc_size = allocSize;
result = (void *)&(zmem->data);
finish:
return result;
}
void
z_free(void * notused __unused, void * ptr)
{
uint32_t * skipper = (uint32_t *)ptr - 1;
z_mem * zmem = (z_mem *)skipper;
kheap_free(KHEAP_DATA_BUFFERS, zmem, zmem->alloc_size);
return;
}
};
OSSharedPtr<OSData>
OSKext::extractMkext2FileData(
UInt8 * data,
const char * name,
uint32_t compressedSize,
uint32_t fullSize)
{
OSSharedPtr<OSData> result;
OSSharedPtr<OSData> uncompressedData; // release on error
uint8_t * uncompressedDataBuffer = NULL; // do not free
unsigned long uncompressedSize;
z_stream zstream;
bool zstream_inited = false;
int zlib_result;
/* If the file isn't compressed, we want to make a copy
* so that we don't have the tie to the larger mkext file buffer any more.
*/
if (!compressedSize) {
uncompressedData = OSData::withBytes(data, fullSize);
// xxx - no check for failure?
result = uncompressedData;
goto finish;
}
if (KERN_SUCCESS != kmem_alloc(kernel_map,
(vm_offset_t*)&uncompressedDataBuffer, fullSize, VM_KERN_MEMORY_OSKEXT)) {
/* How's this for cheesy? The kernel is only asked to extract
* kext plists so we tailor the log messages.
*/
if (isKernel()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Allocation failure extracting %s from mkext.", name);
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Allocation failure extracting %s from mkext for kext %s.",
name, getIdentifierCString());
}
goto finish;
}
uncompressedData = OSData::withBytesNoCopy(uncompressedDataBuffer, fullSize);
if (!uncompressedData) {
if (isKernel()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Allocation failure extracting %s from mkext.", name);
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Allocation failure extracting %s from mkext for kext %s.",
name, getIdentifierCString());
}
goto finish;
}
uncompressedData->setDeallocFunction(&osdata_kmem_free);
if (isKernel()) {
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogArchiveFlag,
"Kernel extracted %s from mkext - compressed size %d, uncompressed size %d.",
name, compressedSize, fullSize);
} else {
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogArchiveFlag,
"Kext %s extracted %s from mkext - compressed size %d, uncompressed size %d.",
getIdentifierCString(), name, compressedSize, fullSize);
}
bzero(&zstream, sizeof(zstream));
zstream.next_in = (UInt8 *)data;
zstream.avail_in = compressedSize;
zstream.next_out = uncompressedDataBuffer;
zstream.avail_out = fullSize;
zstream.zalloc = z_alloc;
zstream.zfree = z_free;
zlib_result = inflateInit(&zstream);
if (Z_OK != zlib_result) {
if (isKernel()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext error; zlib inflateInit failed (%d) for %s.",
zlib_result, name);
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Kext %s - mkext error; zlib inflateInit failed (%d) for %s .",
getIdentifierCString(), zlib_result, name);
}
goto finish;
} else {
zstream_inited = true;
}
zlib_result = inflate(&zstream, Z_FINISH);
if (zlib_result == Z_STREAM_END || zlib_result == Z_OK) {
uncompressedSize = zstream.total_out;
} else {
if (isKernel()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext error; zlib inflate failed (%d) for %s.",
zlib_result, name);
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Kext %s - mkext error; zlib inflate failed (%d) for %s .",
getIdentifierCString(), zlib_result, name);
}
if (zstream.msg) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"zlib error: %s.", zstream.msg);
}
goto finish;
}
if (uncompressedSize != fullSize) {
if (isKernel()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Mkext error; zlib inflate discrepancy for %s, "
"uncompressed size != original size.", name);
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Kext %s - mkext error; zlib inflate discrepancy for %s, "
"uncompressed size != original size.",
getIdentifierCString(), name);
}
goto finish;
}
result = os::move(uncompressedData);
finish:
/* Don't bother checking return, nothing we can do on fail.
*/
if (zstream_inited) {
inflateEnd(&zstream);
}
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::loadFromMkext(
OSKextLogSpec clientLogFilter,
char * mkextBuffer,
uint32_t mkextBufferLength,
char ** logInfoOut,
uint32_t * logInfoLengthOut)
{
OSReturn result = kOSReturnError;
OSReturn tempResult = kOSReturnError;
OSSharedPtr<OSData> mkextData;
OSSharedPtr<OSDictionary> mkextPlist;
OSSharedPtr<OSArray> logInfoArray;
OSSharedPtr<OSSerialize> serializer;
OSString * predicate = NULL; // do not release
OSDictionary * requestArgs = NULL; // do not release
OSString * kextIdentifier = NULL; // do not release
OSNumber * startKextExcludeNum = NULL; // do not release
OSNumber * startMatchingExcludeNum = NULL; // do not release
OSBoolean * delayAutounloadBool = NULL; // do not release
OSArray * personalityNames = NULL; // do not release
/* Default values for these two options: regular autounload behavior,
* load all kexts, send no personalities.
*/
Boolean delayAutounload = false;
OSKextExcludeLevel startKextExcludeLevel = kOSKextExcludeNone;
OSKextExcludeLevel startMatchingExcludeLevel = kOSKextExcludeAll;
IORecursiveLockLock(sKextLock);
if (logInfoOut) {
*logInfoOut = NULL;
*logInfoLengthOut = 0;
}
OSKext::setUserSpaceLogFilter(clientLogFilter, logInfoOut ? true : false);
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogIPCFlag,
"Received kext load request from user space.");
/* Regardless of processing, the fact that we have gotten here means some
* user-space program is up and talking to us, so we'll switch our kext
* registration to reflect that.
*/
if (!sUserLoadsActive) {
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Switching to late startup (user-space) kext loading policy.");
sUserLoadsActive = true;
}
if (!sLoadEnabled) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext loading is disabled.");
result = kOSKextReturnDisabled;
goto finish;
}
/* Note that we do not set a dealloc function on this OSData
* object! No references to it can remain after the loadFromMkext()
* call since we are in a MIG function, and will vm_deallocate()
* the buffer.
*/
mkextData = OSData::withBytesNoCopy(mkextBuffer,
mkextBufferLength);
if (!mkextData) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogIPCFlag,
"Failed to create wrapper for kext load request.");
result = kOSKextReturnNoMemory;
goto finish;
}
result = readMkext2Archive(mkextData.get(), mkextPlist, NULL);
if (result != kOSReturnSuccess) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to read kext load request.");
goto finish;
}
predicate = _OSKextGetRequestPredicate(mkextPlist.get());
if (!predicate || !predicate->isEqualTo(kKextRequestPredicateLoad)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Received kext load request with no predicate; skipping.");
result = kOSKextReturnInvalidArgument;
goto finish;
}
requestArgs = OSDynamicCast(OSDictionary,
mkextPlist->getObject(kKextRequestArgumentsKey));
if (!requestArgs || !requestArgs->getCount()) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Received kext load request with no arguments.");
result = kOSKextReturnInvalidArgument;
goto finish;
}
kextIdentifier = OSDynamicCast(OSString,
requestArgs->getObject(kKextRequestArgumentBundleIdentifierKey));
if (!kextIdentifier) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Received kext load request with no kext identifier.");
result = kOSKextReturnInvalidArgument;
goto finish;
}
startKextExcludeNum = OSDynamicCast(OSNumber,
requestArgs->getObject(kKextRequestArgumentStartExcludeKey));
startMatchingExcludeNum = OSDynamicCast(OSNumber,
requestArgs->getObject(kKextRequestArgumentStartMatchingExcludeKey));
delayAutounloadBool = OSDynamicCast(OSBoolean,
requestArgs->getObject(kKextRequestArgumentDelayAutounloadKey));
personalityNames = OSDynamicCast(OSArray,
requestArgs->getObject(kKextRequestArgumentPersonalityNamesKey));
if (delayAutounloadBool) {
delayAutounload = delayAutounloadBool->getValue();
}
if (startKextExcludeNum) {
startKextExcludeLevel = startKextExcludeNum->unsigned8BitValue();
}
if (startMatchingExcludeNum) {
startMatchingExcludeLevel = startMatchingExcludeNum->unsigned8BitValue();
}
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogIPCFlag,
"Received request from user space to load kext %s.",
kextIdentifier->getCStringNoCopy());
/* Load the kext, with no deferral, since this is a load from outside
* the kernel.
* xxx - Would like a better way to handle the default values for the
* xxx - start/match opt args.
*/
result = OSKext::loadKextWithIdentifier(
kextIdentifier,
/* kextRef */ NULL,
/* allowDefer */ false,
delayAutounload,
startKextExcludeLevel,
startMatchingExcludeLevel,
personalityNames);
if (result != kOSReturnSuccess) {
goto finish;
}
/* If the load came down from the IOKit daemon, it will shortly inform IOCatalogue
* for matching via a separate IOKit calldown.
*/
finish:
/* Gather up the collected log messages for user space. Any
* error messages past this call will not make it up as log messages
* but will be in the system log.
*/
logInfoArray = OSKext::clearUserSpaceLogFilter();
if (logInfoArray && logInfoOut && logInfoLengthOut) {
tempResult = OSKext::serializeLogInfo(logInfoArray.get(),
logInfoOut, logInfoLengthOut);
if (tempResult != kOSReturnSuccess) {
result = tempResult;
}
}
OSKext::flushNonloadedKexts(/* flushPrelinkedKexts */ false);
IORecursiveLockUnlock(sKextLock);
/* Note: mkextDataObject will have been retained by every kext w/an
* executable in it. That should all have been flushed out at the
* and of the load operation, but you never know....
*/
if (mkextData && mkextData->getRetainCount() > 1) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogIPCFlag,
"Kext load request buffer from user space still retained by a kext; "
"probable memory leak.");
}
return result;
}
#endif // CONFIG_KXLD
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::serializeLogInfo(
OSArray * logInfoArray,
char ** logInfoOut,
uint32_t * logInfoLengthOut)
{
OSReturn result = kOSReturnError;
char * buffer = NULL;
kern_return_t kmem_result = KERN_FAILURE;
OSSharedPtr<OSSerialize> serializer;
char * logInfo = NULL; // returned by reference
uint32_t logInfoLength = 0;
if (!logInfoArray || !logInfoOut || !logInfoLengthOut) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Internal error; invalid arguments to OSKext::serializeLogInfo().");
/* Bad programmer. */
result = kOSKextReturnInvalidArgument;
goto finish;
}
serializer = OSSerialize::withCapacity(0);
if (!serializer) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Failed to create serializer on log info for request from user space.");
/* Incidental error; we're going to (try to) allow the request
* itself to succeed. */
}
if (!logInfoArray->serialize(serializer.get())) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Failed to serialize log info for request from user space.");
/* Incidental error; we're going to (try to) allow the request
* itself to succeed. */
} else {
logInfo = serializer->text();
logInfoLength = serializer->getLength();
kmem_result = kmem_alloc(kernel_map, (vm_offset_t *)&buffer, round_page(logInfoLength), VM_KERN_MEMORY_OSKEXT);
if (kmem_result != KERN_SUCCESS) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Failed to copy log info for request from user space.");
/* Incidental error; we're going to (try to) allow the request
* to succeed. */
} else {
/* 11981737 - clear uninitialized data in last page */
bzero((void *)(buffer + logInfoLength),
(round_page(logInfoLength) - logInfoLength));
memcpy(buffer, logInfo, logInfoLength);
*logInfoOut = buffer;
*logInfoLengthOut = logInfoLength;
}
}
result = kOSReturnSuccess;
finish:
return result;
}
#if PRAGMA_MARK
#pragma mark Instance Management Methods
#endif
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSKext>
OSKext::lookupKextWithIdentifier(const char * kextIdentifier)
{
OSSharedPtr<OSKext> foundKext;
IORecursiveLockLock(sKextLock);
foundKext.reset(OSDynamicCast(OSKext, sKextsByID->getObject(kextIdentifier)), OSRetain);
IORecursiveLockUnlock(sKextLock);
return foundKext;
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSKext>
OSKext::lookupKextWithIdentifier(OSString * kextIdentifier)
{
return OSKext::lookupKextWithIdentifier(kextIdentifier->getCStringNoCopy());
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSKext>
OSKext::lookupKextWithLoadTag(uint32_t aTag)
{
OSSharedPtr<OSKext> foundKext; // returned
uint32_t i, j;
OSArray *list[2] = {sLoadedKexts.get(), sLoadedDriverKitKexts.get()};
uint32_t count[2] = {sLoadedKexts->getCount(), sLoadedDriverKitKexts->getCount()};
IORecursiveLockLock(sKextLock);
for (j = 0; j < (sizeof(list) / sizeof(list[0])); j++) {
for (i = 0; i < count[j]; i++) {
OSKext * thisKext = OSDynamicCast(OSKext, list[j]->getObject(i));
if (thisKext->getLoadTag() == aTag) {
foundKext.reset(thisKext, OSRetain);
goto finish;
}
}
}
finish:
IORecursiveLockUnlock(sKextLock);
return foundKext;
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSKext>
OSKext::lookupKextWithAddress(vm_address_t address)
{
OSSharedPtr<OSKext> foundKext; // returned
uint32_t count, i;
kmod_info_t *kmod_info;
vm_address_t originalAddress;
#if defined(__arm64__)
uint64_t textExecBase;
size_t textExecSize;
#endif /* defined(__arm64__) */
originalAddress = address;
#if __has_feature(ptrauth_calls)
address = (vm_address_t)VM_KERNEL_STRIP_PTR(address);
#endif /* __has_feature(ptrauth_calls) */
IORecursiveLockLock(sKextLock);
count = sLoadedKexts->getCount();
for (i = 0; i < count; i++) {
OSKext * thisKext = OSDynamicCast(OSKext, sLoadedKexts->getObject(i));
if (thisKext == sKernelKext) {
continue;
}
if (thisKext->kmod_info && thisKext->kmod_info->address) {
kmod_info = thisKext->kmod_info;
vm_address_t kext_start = kmod_info->address;
vm_address_t kext_end = kext_start + kmod_info->size;
if ((kext_start <= address) && (address < kext_end)) {
foundKext.reset(thisKext, OSRetain);
goto finish;
}
#if defined(__arm64__)
textExecBase = (uintptr_t) getsegdatafromheader((kernel_mach_header_t *)kmod_info->address, "__TEXT_EXEC", &textExecSize);
if ((textExecBase <= address) && (address < textExecBase + textExecSize)) {
foundKext.reset(thisKext, OSRetain);
goto finish;
}
#endif /* defined (__arm64__) */
}
}
if ((address >= vm_kernel_stext) && (address < vm_kernel_etext)) {
foundKext.reset(sKernelKext, OSRetain);
goto finish;
}
/*
* DriverKit userspace executables do not have a kernel linkedExecutable,
* so we "fake" their address range with the LoadTag. We cannot use the ptrauth-stripped address
* here, so use the original address passed to this method.
*
* This is supposed to be used for logging reasons only. When logd
* calls this function it ors the address with FIREHOSE_TRACEPOINT_PC_KERNEL_MASK, so we
* remove it here before checking it against the LoadTag.
* Also we need to remove FIREHOSE_TRACEPOINT_PC_DYNAMIC_BIT set when emitting the log line.
*/
address = originalAddress & ~(FIREHOSE_TRACEPOINT_PC_KERNEL_MASK | FIREHOSE_TRACEPOINT_PC_DYNAMIC_BIT);
count = sLoadedDriverKitKexts->getCount();
for (i = 0; i < count; i++) {
OSKext * thisKext = OSDynamicCast(OSKext, sLoadedDriverKitKexts->getObject(i));
if (thisKext->getLoadTag() == address) {
foundKext.reset(thisKext, OSRetain);
}
}
finish:
IORecursiveLockUnlock(sKextLock);
return foundKext;
}
OSSharedPtr<OSData>
OSKext::copyKextUUIDForAddress(OSNumber *address)
{
OSSharedPtr<OSData> uuid;
OSSharedPtr<OSKext> kext;
if (!address) {
return NULL;
}
#if CONFIG_MACF
/* Is the calling process allowed to query kext info? */
if (current_task() != kernel_task) {
int macCheckResult = 0;
kauth_cred_t cred = NULL;
cred = kauth_cred_get_with_ref();
macCheckResult = mac_kext_check_query(cred);
kauth_cred_unref(&cred);
if (macCheckResult != 0) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Failed to query kext UUID (MAC policy error 0x%x).",
macCheckResult);
return NULL;
}
}
#endif
uintptr_t slidAddress = ml_static_slide((uintptr_t)address->unsigned64BitValue());
if (slidAddress != 0) {
kext = lookupKextWithAddress(slidAddress);
if (kext) {
uuid = kext->copyTextUUID();
}
}
if (!uuid) {
/*
* If we still don't have a UUID, then we failed to match the slid + stripped address with
* a kext. This might have happened because the log message came from a dext.
*
* Try again with the original address.
*/
kext = lookupKextWithAddress((vm_address_t)address->unsigned64BitValue());
if (kext && kext->isDriverKit()) {
uuid = kext->copyTextUUID();
}
}
return uuid;
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSKext>
OSKext::lookupKextWithUUID(uuid_t wanted)
{
OSSharedPtr<OSKext> foundKext; // returned
uint32_t j, i;
OSArray *list[2] = {sLoadedKexts.get(), sLoadedDriverKitKexts.get()};
uint32_t count[2] = {sLoadedKexts->getCount(), sLoadedDriverKitKexts->getCount()};
IORecursiveLockLock(sKextLock);
for (j = 0; j < (sizeof(list) / sizeof(list[0])); j++) {
for (i = 0; i < count[j]; i++) {
OSKext * thisKext = NULL;
thisKext = OSDynamicCast(OSKext, list[j]->getObject(i));
if (!thisKext) {
continue;
}
OSSharedPtr<OSData> uuid_data = thisKext->copyUUID();
if (!uuid_data) {
continue;
}
uuid_t uuid;
memcpy(&uuid, uuid_data->getBytesNoCopy(), sizeof(uuid));
if (0 == uuid_compare(wanted, uuid)) {
foundKext.reset(thisKext, OSRetain);
goto finish;
}
}
}
finish:
IORecursiveLockUnlock(sKextLock);
return foundKext;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::isKextWithIdentifierLoaded(const char * kextIdentifier)
{
bool result = false;
OSKext * foundKext = NULL; // returned
IORecursiveLockLock(sKextLock);
foundKext = OSDynamicCast(OSKext, sKextsByID->getObject(kextIdentifier));
if (foundKext && foundKext->isLoaded()) {
result = true;
}
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
* xxx - should spawn a separate thread so a kext can safely have
* xxx - itself unloaded.
*********************************************************************/
/* static */
OSReturn
OSKext::removeKext(
OSKext * aKext,
#if CONFIG_EMBEDDED
__unused
#endif
bool terminateServicesAndRemovePersonalitiesFlag)
{
#if CONFIG_EMBEDDED
OSKextLog(aKext,
kOSKextLogErrorLevel |
kOSKextLogKextBookkeepingFlag,
"removeKext() called for %s, not supported on embedded",
aKext->getIdentifier() ? aKext->getIdentifierCString() : "unknown kext");
return kOSReturnSuccess;
#else /* CONFIG_EMBEDDED */
OSReturn result = kOSKextReturnInUse;
OSKext * checkKext = NULL; // do not release
#if CONFIG_MACF
int macCheckResult = 0;
kauth_cred_t cred = NULL;
#endif
IORecursiveLockLock(sKextLock);
/* If the kext has no identifier, it failed to init
* so isn't in sKextsByID and it isn't loaded.
*/
if (!aKext->getIdentifier()) {
result = kOSReturnSuccess;
goto finish;
}
checkKext = OSDynamicCast(OSKext,
sKextsByID->getObject(aKext->getIdentifier()));
if (checkKext != aKext) {
result = kOSKextReturnNotFound;
goto finish;
}
if (aKext->isLoaded()) {
#if CONFIG_MACF
if (current_task() != kernel_task) {
cred = kauth_cred_get_with_ref();
macCheckResult = mac_kext_check_unload(cred, aKext->getIdentifierCString());
kauth_cred_unref(&cred);
}
if (macCheckResult != 0) {
result = kOSReturnError;
OSKextLog(aKext,
kOSKextLogErrorLevel |
kOSKextLogKextBookkeepingFlag,
"Failed to remove kext %s (MAC policy error 0x%x).",
aKext->getIdentifierCString(), macCheckResult);
goto finish;
}
#endif
/* make sure there are no resource requests in flight - 17187548 */
if (aKext->countRequestCallbacks()) {
goto finish;
}
if (aKext->flags.unloadUnsupported) {
result = kOSKextReturnInUse;
OSKextLog(aKext,
kOSKextLogErrorLevel |
kOSKextLogKextBookkeepingFlag,
"Can't remove kext %s; unsupported by cache.",
aKext->getIdentifierCString());
goto finish;
}
/* If we are terminating, send the request to the IOCatalogue
* (which will actually call us right back but that's ok we have
* a recursive lock don't you know) but do not ask the IOCatalogue
* to call back with an unload, we'll do that right here.
*/
if (terminateServicesAndRemovePersonalitiesFlag) {
result = gIOCatalogue->terminateDriversForModule(
aKext->getIdentifierCString(), /* unload */ false);
if (result != kOSReturnSuccess) {
OSKextLog(aKext,
kOSKextLogErrorLevel |
kOSKextLogKextBookkeepingFlag,
"Can't remove kext %s; services failed to terminate - 0x%x.",
aKext->getIdentifierCString(), result);
goto finish;
}
}
result = aKext->unload();
if (result != kOSReturnSuccess) {
goto finish;
}
}
/* Remove personalities as requested. This is a bit redundant for a loaded
* kext as IOCatalogue::terminateDriversForModule() removes driver
* personalities, but it doesn't restart matching, which we always want
* coming from here, and OSKext::removePersonalitiesFromCatalog() ensures
* that happens.
*/
if (terminateServicesAndRemovePersonalitiesFlag) {
aKext->removePersonalitiesFromCatalog();
}
if (aKext->isInFileset()) {
OSKextLog(aKext,
kOSKextLogProgressLevel |
kOSKextLogKextBookkeepingFlag,
"Fileset kext %s unloaded.",
aKext->getIdentifierCString());
} else {
OSKextLog(aKext,
kOSKextLogProgressLevel |
kOSKextLogKextBookkeepingFlag,
"Removing kext %s.",
aKext->getIdentifierCString());
sKextsByID->removeObject(aKext->getIdentifier());
}
result = kOSReturnSuccess;
finish:
IORecursiveLockUnlock(sKextLock);
return result;
#endif /* CONFIG_EMBEDDED */
}
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::removeKextWithIdentifier(
const char * kextIdentifier,
bool terminateServicesAndRemovePersonalitiesFlag)
{
OSReturn result = kOSReturnError;
IORecursiveLockLock(sKextLock);
OSKext * aKext = OSDynamicCast(OSKext,
sKextsByID->getObject(kextIdentifier));
if (!aKext) {
result = kOSKextReturnNotFound;
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogKextBookkeepingFlag,
"Can't remove kext %s - not found.",
kextIdentifier);
goto finish;
}
result = OSKext::removeKext(aKext,
terminateServicesAndRemovePersonalitiesFlag);
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::removeKextWithLoadTag(
OSKextLoadTag loadTag,
bool terminateServicesAndRemovePersonalitiesFlag)
{
OSReturn result = kOSReturnError;
OSKext * foundKext = NULL;
uint32_t i, j;
OSArray *list[2] = {sLoadedKexts.get(), sLoadedDriverKitKexts.get()};
uint32_t count[2] = {sLoadedKexts->getCount(), sLoadedDriverKitKexts->getCount()};
IORecursiveLockLock(sKextLock);
for (j = 0; j < (sizeof(list) / sizeof(list[0])); j++) {
for (i = 0; i < count[j]; i++) {
OSKext * thisKext = OSDynamicCast(OSKext, list[j]->getObject(i));
if (thisKext->loadTag == loadTag) {
foundKext = thisKext;
break;
}
}
}
if (!foundKext) {
result = kOSKextReturnNotFound;
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag,
"Can't remove kext with load tag %d - not found.",
loadTag);
goto finish;
}
result = OSKext::removeKext(foundKext,
terminateServicesAndRemovePersonalitiesFlag);
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSDictionary>
OSKext::copyKexts(void)
{
OSSharedPtr<OSDictionary> result;
IORecursiveLockLock(sKextLock);
result = OSDynamicPtrCast<OSDictionary>(sKextsByID->copyCollection());
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
#define BOOTER_KEXT_PREFIX "Driver-"
typedef struct _DeviceTreeBuffer {
uint32_t paddr;
uint32_t length;
} _DeviceTreeBuffer;
/*********************************************************************
* Create a dictionary of excluded kexts from the given booter data.
*********************************************************************/
/* static */
void
OSKext::createExcludeListFromBooterData(
OSDictionary * theDictionary,
OSCollectionIterator * theIterator )
{
OSString * deviceTreeName = NULL; // do not release
const _DeviceTreeBuffer * deviceTreeBuffer = NULL; // do not release
char * booterDataPtr = NULL; // do not release
_BooterKextFileInfo * kextFileInfo = NULL; // do not release
char * infoDictAddr = NULL; // do not release
OSSharedPtr<OSObject> parsedXML;
OSDictionary * theInfoDict = NULL; // do not release
theIterator->reset();
/* look for AppleKextExcludeList.kext */
while ((deviceTreeName =
OSDynamicCast(OSString, theIterator->getNextObject()))) {
const char * devTreeNameCString;
OSData * deviceTreeEntry; // do not release
OSString * myBundleID; // do not release
deviceTreeEntry =
OSDynamicCast(OSData, theDictionary->getObject(deviceTreeName));
if (!deviceTreeEntry) {
continue;
}
/* Make sure it is a kext */
devTreeNameCString = deviceTreeName->getCStringNoCopy();
if (strncmp(devTreeNameCString, BOOTER_KEXT_PREFIX,
(sizeof(BOOTER_KEXT_PREFIX) - 1)) != 0) {
OSKextLog(NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"\"%s\" not a kext",
devTreeNameCString);
continue;
}
deviceTreeBuffer = (const _DeviceTreeBuffer *)
deviceTreeEntry->getBytesNoCopy(0, sizeof(deviceTreeBuffer));
if (!deviceTreeBuffer) {
continue;
}
booterDataPtr = (char *)ml_static_ptovirt(deviceTreeBuffer->paddr);
if (!booterDataPtr) {
continue;
}
kextFileInfo = (_BooterKextFileInfo *) booterDataPtr;
if (!kextFileInfo->infoDictPhysAddr ||
!kextFileInfo->infoDictLength) {
continue;
}
infoDictAddr = (char *)
ml_static_ptovirt(kextFileInfo->infoDictPhysAddr);
if (!infoDictAddr) {
continue;
}
parsedXML = OSUnserializeXML(infoDictAddr);
if (!parsedXML) {
continue;
}
theInfoDict = OSDynamicCast(OSDictionary, parsedXML.get());
if (!theInfoDict) {
continue;
}
myBundleID =
OSDynamicCast(OSString,
theInfoDict->getObject(kCFBundleIdentifierKey));
if (myBundleID &&
strcmp( myBundleID->getCStringNoCopy(), kIOExcludeListBundleID ) == 0) {
boolean_t updated = updateExcludeList(theInfoDict);
if (!updated) {
/* 25322874 */
panic("Missing OSKextExcludeList dictionary\n");
}
break;
}
} // while ( (deviceTreeName = ...) )
return;
}
/*********************************************************************
* Create a dictionary of excluded kexts from the given prelink
* info (kernelcache).
*********************************************************************/
/* static */
void
OSKext::createExcludeListFromPrelinkInfo( OSArray * theInfoArray )
{
OSDictionary * myInfoDict = NULL; // do not release
OSString * myBundleID; // do not release
u_int i;
/* Find the Apple Kext Exclude List. */
for (i = 0; i < theInfoArray->getCount(); i++) {
myInfoDict = OSDynamicCast(OSDictionary, theInfoArray->getObject(i));
if (!myInfoDict) {
continue;
}
myBundleID =
OSDynamicCast(OSString,
myInfoDict->getObject(kCFBundleIdentifierKey));
if (myBundleID &&
strcmp( myBundleID->getCStringNoCopy(), kIOExcludeListBundleID ) == 0) {
boolean_t updated = updateExcludeList(myInfoDict);
if (!updated) {
/* 25322874 */
panic("Missing OSKextExcludeList dictionary\n");
}
break;
}
} // for (i = 0; i < theInfoArray->getCount()...
return;
}
/* static */
boolean_t
OSKext::updateExcludeList(OSDictionary *infoDict)
{
OSDictionary *myTempDict = NULL; // do not free
OSString *myTempString = NULL; // do not free
OSKextVersion newVersion = 0;
boolean_t updated = false;
if (!infoDict) {
return false;
}
myTempDict = OSDynamicCast(OSDictionary, infoDict->getObject("OSKextExcludeList"));
if (!myTempDict) {
return false;
}
myTempString = OSDynamicCast(OSString, infoDict->getObject(kCFBundleVersionKey));
if (!myTempString) {
return false;
}
newVersion = OSKextParseVersionString(myTempString->getCStringNoCopy());
if (newVersion == 0) {
return false;
}
IORecursiveLockLock(sKextLock);
if (newVersion > sExcludeListVersion) {
sExcludeListByID = OSDictionary::withDictionary(myTempDict, 0);
sExcludeListVersion = newVersion;
updated = true;
}
IORecursiveLockUnlock(sKextLock);
return updated;
}
#if PRAGMA_MARK
#pragma mark Accessors
#endif
/*********************************************************************
*********************************************************************/
const OSSymbol *
OSKext::getIdentifier(void)
{
return bundleID.get();
}
/*********************************************************************
* A kext must have a bundle identifier to even survive initialization;
* this is guaranteed to exist past then.
*********************************************************************/
const char *
OSKext::getIdentifierCString(void)
{
return bundleID->getCStringNoCopy();
}
/*********************************************************************
*********************************************************************/
OSKextVersion
OSKext::getVersion(void)
{
return version;
}
/*********************************************************************
*********************************************************************/
OSKextVersion
OSKext::getCompatibleVersion(void)
{
return compatibleVersion;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isLibrary(void)
{
return getCompatibleVersion() > 0;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isCompatibleWithVersion(OSKextVersion aVersion)
{
if ((compatibleVersion > -1 && version > -1) &&
(compatibleVersion <= version && aVersion <= version)) {
return true;
}
return false;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::declaresExecutable(void)
{
if (isDriverKit()) {
return false;
}
return getPropertyForHostArch(kCFBundleExecutableKey) != NULL;
}
/*********************************************************************
*********************************************************************/
OSData *
OSKext::getExecutable(void)
{
OSData * result = NULL;
OSSharedPtr<OSData> extractedExecutable;
if (flags.builtin) {
return sKernelKext->linkedExecutable.get();
}
result = OSDynamicCast(OSData, infoDict->getObject(_kOSKextExecutableKey));
if (result) {
return result;
}
#if CONFIG_KXLD
OSData * mkextExecutableRef = NULL; // do not release
mkextExecutableRef = OSDynamicCast(OSData,
getPropertyForHostArch(_kOSKextMkextExecutableReferenceKey));
if (mkextExecutableRef) {
MkextEntryRef * mkextEntryRef = (MkextEntryRef *)
mkextExecutableRef->getBytesNoCopy();
uint32_t mkextVersion = MKEXT_GET_VERSION(mkextEntryRef->mkext);
if (mkextVersion == MKEXT_VERS_2) {
mkext2_file_entry * fileinfo =
(mkext2_file_entry *)mkextEntryRef->fileinfo;
uint32_t compressedSize = MKEXT2_GET_ENTRY_COMPSIZE(fileinfo);
uint32_t fullSize = MKEXT2_GET_ENTRY_FULLSIZE(fileinfo);
extractedExecutable = extractMkext2FileData(
MKEXT2_GET_ENTRY_DATA(fileinfo), "executable",
compressedSize, fullSize);
} else {
OSKextLog(this, kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Kext %s - unknown mkext version 0x%x for executable.",
getIdentifierCString(), mkextVersion);
}
/* Regardless of success, remove the mkext executable,
* and drop one reference on the mkext. (setExecutable() does not
* replace, it removes, or panics if asked to replace.)
*/
infoDict->removeObject(_kOSKextMkextExecutableReferenceKey);
infoDict->removeObject(_kOSKextExecutableExternalDataKey);
if (extractedExecutable && extractedExecutable->getLength()) {
if (!setExecutable(extractedExecutable.get())) {
goto finish;
}
result = extractedExecutable.get();
} else {
goto finish;
}
}
finish:
#endif // CONFIG_KXLD
return result;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isInterface(void)
{
return flags.interface;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isKernel(void)
{
return this == sKernelKext;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isKernelComponent(void)
{
return flags.kernelComponent ? true : false;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isExecutable(void)
{
return !isKernel() && !isInterface() && declaresExecutable();
}
/*********************************************************************
* We might want to check this recursively for all dependencies,
* since a subtree of dependencies could get loaded before we hit
* a dependency that isn't safe-boot-loadable.
*
* xxx - Might want to return false if OSBundleEnableKextLogging or
* OSBundleDebugLevel
* or IOKitDebug is nonzero too (we used to do that, but I don't see
* the point except it's usually development drivers, which might
* cause panics on startup, that have those properties). Heh; could
* use a "kx" boot-arg!
*********************************************************************/
bool
OSKext::isLoadableInSafeBoot(void)
{
bool result = false;
OSString * required = NULL; // do not release
if (isKernel()) {
result = true;
goto finish;
}
if (isDriverKit()) {
result = true;
goto finish;
}
required = OSDynamicCast(OSString,
getPropertyForHostArch(kOSBundleRequiredKey));
if (!required) {
goto finish;
}
if (required->isEqualTo(kOSBundleRequiredRoot) ||
required->isEqualTo(kOSBundleRequiredLocalRoot) ||
required->isEqualTo(kOSBundleRequiredNetworkRoot) ||
required->isEqualTo(kOSBundleRequiredSafeBoot) ||
required->isEqualTo(kOSBundleRequiredConsole)) {
result = true;
}
finish:
return result;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isPrelinked(void)
{
return flags.prelinked ? true : false;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isLoaded(void)
{
return flags.loaded ? true : false;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isStarted(void)
{
return flags.started ? true : false;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::isCPPInitialized(void)
{
return flags.CPPInitialized;
}
/*********************************************************************
*********************************************************************/
void
OSKext::setCPPInitialized(bool initialized)
{
flags.CPPInitialized = initialized;
}
/*********************************************************************
*********************************************************************/
uint32_t
OSKext::getLoadTag(void)
{
return loadTag;
}
/*********************************************************************
*********************************************************************/
void
OSKext::getSizeInfo(uint32_t *loadSize, uint32_t *wiredSize)
{
if (linkedExecutable) {
*loadSize = linkedExecutable->getLength();
/* If we have a kmod_info struct, calculated the wired size
* from that. Otherwise it's the full load size.
*/
if (kmod_info) {
*wiredSize = *loadSize - (uint32_t)kmod_info->hdr_size;
} else {
*wiredSize = *loadSize;
}
} else {
*wiredSize = 0;
*loadSize = 0;
}
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSData>
OSKext::copyUUID(void)
{
OSSharedPtr<OSData> result;
OSData * theExecutable = NULL; // do not release
const kernel_mach_header_t * header;
/* An interface kext doesn't have a linked executable with an LC_UUID,
* we create one when it's linked.
*/
if (interfaceUUID) {
result = interfaceUUID;
goto finish;
}
if (flags.builtin || isInterface()) {
return sKernelKext->copyUUID();
}
if (isDriverKit() && infoDict) {
return driverKitUUID;
}
/* For real kexts, try to get the UUID from the linked executable,
* or if is hasn't been linked yet, the unrelocated executable.
*/
theExecutable = linkedExecutable.get();
if (!theExecutable) {
theExecutable = getExecutable();
}
if (!theExecutable) {
goto finish;
}
header = (const kernel_mach_header_t *)theExecutable->getBytesNoCopy();
result = copyMachoUUID(header);
finish:
return result;
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSData>
OSKext::copyTextUUID(void)
{
if (flags.builtin) {
return copyMachoUUID((const kernel_mach_header_t *)kmod_info->address);
}
return copyUUID();
}
/*********************************************************************
*********************************************************************/
OSSharedPtr<OSData>
OSKext::copyMachoUUID(const kernel_mach_header_t * header)
{
OSSharedPtr<OSData> result;
const struct load_command * load_cmd = NULL;
const struct uuid_command * uuid_cmd = NULL;
uint32_t i;
load_cmd = (const struct load_command *)&header[1];
if (header->magic != MH_MAGIC_KERNEL) {
OSKextLog(NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"%s: bad header %p",
__func__,
header);
goto finish;
}
for (i = 0; i < header->ncmds; i++) {
if (load_cmd->cmd == LC_UUID) {
uuid_cmd = (struct uuid_command *)load_cmd;
result = OSData::withBytes(uuid_cmd->uuid, sizeof(uuid_cmd->uuid));
goto finish;
}
load_cmd = (struct load_command *)((caddr_t)load_cmd + load_cmd->cmdsize);
}
finish:
return result;
}
void
OSKext::setDriverKitUUID(OSData *uuid)
{
if (!OSCompareAndSwapPtr(nullptr, uuid, &driverKitUUID)) {
OSSafeReleaseNULL(uuid);
}
}
/*********************************************************************
*********************************************************************/
#if defined (__arm__)
#include <arm/arch.h>
#endif
#if defined (__x86_64__)
#define ARCHNAME "x86_64"
#elif defined (__arm64__)
#define ARCHNAME "arm64"
#elif defined (__arm__)
#if defined (__ARM_ARCH_7S__)
#define ARCHNAME "armv7s"
#elif defined (__ARM_ARCH_7F__)
#define ARCHNAME "armv7f"
#elif defined (__ARM_ARCH_7K__)
#define ARCHNAME "armv7k"
#elif defined (_ARM_ARCH_7) /* umbrella for all remaining */
#define ARCHNAME "armv7"
#elif defined (_ARM_ARCH_6) /* umbrella for all armv6 */
#define ARCHNAME "armv6"
#endif
#elif defined (__arm64__)
#define ARCHNAME "arm64"
#else
#error architecture not supported
#endif
#define ARCH_SEPARATOR_CHAR '_'
static char *
makeHostArchKey(const char * key, size_t * keySizeOut)
{
char * result = NULL;
size_t keyLength = strlen(key);
size_t keySize;
/* Add 1 for the ARCH_SEPARATOR_CHAR, and 1 for the '\0'.
*/
keySize = 1 + 1 + keyLength + strlen(ARCHNAME);
result = (char *)kheap_alloc_tag(KHEAP_TEMP, keySize,
Z_WAITOK, VM_KERN_MEMORY_OSKEXT);
if (!result) {
goto finish;
}
strlcpy(result, key, keySize);
result[keyLength++] = ARCH_SEPARATOR_CHAR;
result[keyLength] = '\0';
strlcat(result, ARCHNAME, keySize);
*keySizeOut = keySize;
finish:
return result;
}
/*********************************************************************
*********************************************************************/
OSObject *
OSKext::getPropertyForHostArch(const char * key)
{
OSObject * result = NULL;// do not release
size_t hostArchKeySize = 0;
char * hostArchKey = NULL;// must kfree
if (!key || !infoDict) {
goto finish;
}
/* Some properties are not allowed to be arch-variant:
* - Any CFBundle... property.
* - OSBundleIsInterface.
* - OSKernelResource.
*/
if (STRING_HAS_PREFIX(key, "OS") ||
STRING_HAS_PREFIX(key, "IO")) {
hostArchKey = makeHostArchKey(key, &hostArchKeySize);
if (!hostArchKey) {
OSKextLog(/* kext (this isn't about a kext) */ NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Allocation failure.");
goto finish;
}
result = infoDict->getObject(hostArchKey);
}
if (!result) {
result = infoDict->getObject(key);
}
finish:
if (hostArchKey) {
kheap_free(KHEAP_TEMP, hostArchKey, hostArchKeySize);
}
return result;
}
#if PRAGMA_MARK
#pragma mark Load/Start/Stop/Unload
#endif
#define isWhiteSpace(c) ((c) == ' ' || (c) == '\t' || (c) == '\r' || (c) == ',' || (c) == '\n')
/*********************************************************************
* sExcludeListByID is a dictionary with keys / values of:
* key = bundleID string of kext we will not allow to load
* value = version string(s) of the kext that is to be denied loading.
* The version strings can be comma delimited. For example if kext
* com.foocompany.fookext has two versions that we want to deny
* loading then the version strings might look like:
* 1.0.0, 1.0.1
* If the current fookext has a version of 1.0.0 OR 1.0.1 we will
* not load the kext.
*
* Value may also be in the form of "LE 2.0.0" (version numbers
* less than or equal to 2.0.0 will not load) or "LT 2.0.0" (version
* number less than 2.0.0 will not load)
*
* NOTE - we cannot use the characters "<=" or "<" because we have code
* that serializes plists and treats '<' as a special character.
*********************************************************************/
bool
OSKext::isInExcludeList(void)
{
OSString * versionString = NULL; // do not release
char * versionCString = NULL; // do not free
size_t i;
boolean_t wantLessThan = false;
boolean_t wantLessThanEqualTo = false;
boolean_t isInExcludeList = true;
char myBuffer[32];
IORecursiveLockLock(sKextLock);
if (!sExcludeListByID) {
isInExcludeList = false;
} else {
/* look up by bundleID in our exclude list and if found get version
* string (or strings) that we will not allow to load
*/
versionString = OSDynamicCast(OSString, sExcludeListByID->getObject(bundleID.get()));
if (versionString == NULL || versionString->getLength() > (sizeof(myBuffer) - 1)) {
isInExcludeList = false;
}
}
IORecursiveLockUnlock(sKextLock);
if (!isInExcludeList) {
return false;
}
/* parse version strings */
versionCString = (char *) versionString->getCStringNoCopy();
/* look for "LT" or "LE" form of version string, must be in first two
* positions.
*/
if (*versionCString == 'L' && *(versionCString + 1) == 'T') {
wantLessThan = true;
versionCString += 2;
} else if (*versionCString == 'L' && *(versionCString + 1) == 'E') {
wantLessThanEqualTo = true;
versionCString += 2;
}
for (i = 0; *versionCString != 0x00; versionCString++) {
/* skip whitespace */
if (isWhiteSpace(*versionCString)) {
continue;
}
/* peek ahead for version string separator or null terminator */
if (*(versionCString + 1) == ',' || *(versionCString + 1) == 0x00) {
/* OK, we have a version string */
myBuffer[i++] = *versionCString;
myBuffer[i] = 0x00;
OSKextVersion excludeVers;
excludeVers = OSKextParseVersionString(myBuffer);
if (wantLessThanEqualTo) {
if (version <= excludeVers) {
return true;
}
} else if (wantLessThan) {
if (version < excludeVers) {
return true;
}
} else if (version == excludeVers) {
return true;
}
/* reset for the next (if any) version string */
i = 0;
wantLessThan = false;
wantLessThanEqualTo = false;
} else {
/* save valid version character */
myBuffer[i++] = *versionCString;
/* make sure bogus version string doesn't overrun local buffer */
if (i >= sizeof(myBuffer)) {
break;
}
}
}
return false;
}
/*********************************************************************
* sNonLoadableKextsByID is a dictionary with keys / values of:
* key = bundleID string of kext we will not allow to load
* value = boolean (true == loadable, false == not loadable)
*
* Only kexts which are in the AuxKC will be marked as "not loadble,"
* i.e., the value for the kext's bundleID will be false. All kexts in
* the primary and system KCs will always be marked as "loadable."
*
* This list ultimately comes from kexts which have been uninstalled
* in user space by deleting the kext from disk, but which have not
* yet been removed from the AuxKC. Because the user could choose to
* re-install the exact same version of the kext, we need to keep
* a dictionary of boolean values so that user space only needs to
* keep a simple list of "uninstalled" or "missing" bundles. When
* a bundle is re-installed, the iokit daemon can use the
* AucKCBundleAvailable predicate to set the individual kext's
* availability to true.
*********************************************************************/
bool
OSKext::isLoadable(void)
{
bool isLoadable = true;
if (kc_type != KCKindAuxiliary) {
/* this filtering only applies to kexts in the auxkc */
return true;
}
IORecursiveLockLock(sKextLock);
if (sNonLoadableKextsByID) {
/* look up by bundleID in our exclude list and if found get version
* string (or strings) that we will not allow to load
*/
OSBoolean *loadableVal;
loadableVal = OSDynamicCast(OSBoolean, sNonLoadableKextsByID->getObject(bundleID.get()));
if (loadableVal && !loadableVal->getValue()) {
isLoadable = false;
}
}
IORecursiveLockUnlock(sKextLock);
return isLoadable;
}
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::loadKextWithIdentifier(
const char * kextIdentifierCString,
Boolean allowDeferFlag,
Boolean delayAutounloadFlag,
OSKextExcludeLevel startOpt,
OSKextExcludeLevel startMatchingOpt,
OSArray * personalityNames)
{
OSReturn result = kOSReturnError;
OSSharedPtr<OSString> kextIdentifier;
kextIdentifier = OSString::withCString(kextIdentifierCString);
if (!kextIdentifier) {
result = kOSKextReturnNoMemory;
goto finish;
}
result = OSKext::loadKextWithIdentifier(kextIdentifier.get(),
NULL /* kextRef */,
allowDeferFlag, delayAutounloadFlag,
startOpt, startMatchingOpt, personalityNames);
finish:
return result;
}
OSReturn
OSKext::loadKextWithIdentifier(
OSString * kextIdentifier,
OSSharedPtr<OSObject> &kextRef,
Boolean allowDeferFlag,
Boolean delayAutounloadFlag,
OSKextExcludeLevel startOpt,
OSKextExcludeLevel startMatchingOpt,
OSArray * personalityNames)
{
OSObject * kextRefRaw = NULL;
OSReturn result;
result = loadKextWithIdentifier(kextIdentifier,
&kextRefRaw,
allowDeferFlag,
delayAutounloadFlag,
startOpt,
startMatchingOpt,
personalityNames);
if ((kOSReturnSuccess == result) && kextRefRaw) {
kextRef.reset(kextRefRaw, OSNoRetain);
}
return result;
}
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::loadKextWithIdentifier(
OSString * kextIdentifier,
OSObject ** kextRef,
Boolean allowDeferFlag,
Boolean delayAutounloadFlag,
OSKextExcludeLevel startOpt,
OSKextExcludeLevel startMatchingOpt,
OSArray * personalityNames)
{
OSReturn result = kOSReturnError;
OSReturn pingResult = kOSReturnError;
OSKext * theKext = NULL; // do not release
OSSharedPtr<OSDictionary> loadRequest;
OSSharedPtr<const OSSymbol> kextIdentifierSymbol;
if (kextRef) {
*kextRef = NULL;
}
IORecursiveLockLock(sKextLock);
if (!kextIdentifier) {
result = kOSKextReturnInvalidArgument;
goto finish;
}
OSKext::recordIdentifierRequest(kextIdentifier);
theKext = OSDynamicCast(OSKext, sKextsByID->getObject(kextIdentifier));
if (!theKext) {
if (!allowDeferFlag) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Can't load kext %s - not found.",
kextIdentifier->getCStringNoCopy());
goto finish;
}
if (!sKernelRequestsEnabled) {
OSKextLog(theKext,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Can't load kext %s - requests to user space are disabled.",
kextIdentifier->getCStringNoCopy());
result = kOSKextReturnDisabled;
goto finish;
}
/* Create a new request unless one is already sitting
* in sKernelRequests for this bundle identifier
*/
kextIdentifierSymbol = OSSymbol::withString(kextIdentifier);
if (!sPostedKextLoadIdentifiers->containsObject(kextIdentifierSymbol.get())) {
result = _OSKextCreateRequest(kKextRequestPredicateRequestLoad,
loadRequest);
if (result != kOSReturnSuccess) {
goto finish;
}
if (!_OSKextSetRequestArgument(loadRequest.get(),
kKextRequestArgumentBundleIdentifierKey, kextIdentifier)) {
result = kOSKextReturnNoMemory;
goto finish;
}
if (!sKernelRequests->setObject(loadRequest.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
if (!sPostedKextLoadIdentifiers->setObject(kextIdentifierSymbol.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
OSKextLog(theKext,
kOSKextLogDebugLevel |
kOSKextLogLoadFlag,
"Kext %s not found; queued load request to user space.",
kextIdentifier->getCStringNoCopy());
}
pingResult = OSKext::pingIOKitDaemon();
if (pingResult == kOSKextReturnDisabled) {
OSKextLog(/* kext */ NULL,
((sPrelinkBoot) ? kOSKextLogDebugLevel : kOSKextLogErrorLevel) |
kOSKextLogLoadFlag,
"Kext %s might not load - " kIOKitDaemonName " is currently unavailable.",
kextIdentifier->getCStringNoCopy());
}
result = kOSKextReturnDeferred;
goto finish;
}
result = theKext->load(startOpt, startMatchingOpt, personalityNames);
if (result != kOSReturnSuccess) {
OSKextLog(theKext,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to load kext %s (error 0x%x).",
kextIdentifier->getCStringNoCopy(), (int)result);
if (theKext->kc_type == KCKindUnknown) {
OSKext::removeKext(theKext,
/* terminateService/removePersonalities */ true);
}
goto finish;
}
if (delayAutounloadFlag) {
OSKextLog(theKext,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag,
"Setting delayed autounload for %s.",
kextIdentifier->getCStringNoCopy());
theKext->flags.delayAutounload = 1;
}
finish:
if ((kOSReturnSuccess == result) && kextRef) {
*kextRef = theKext;
theKext->matchingRefCount++;
theKext->retain();
}
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::loadKextFromKC(OSKext *theKext, OSDictionary *requestDict)
{
OSReturn result = kOSReturnError;
OSBoolean *delayAutounloadBool = NULL; // do not release
OSNumber *startKextExcludeNum = NULL; // do not release
OSNumber *startMatchingExcludeNum = NULL; // do not release
OSArray *personalityNames = NULL; // do not release
/*
* Default values for these options:
* regular autounload behavior
* start the kext
* send all personalities to the catalog
*/
Boolean delayAutounload = false;
OSKextExcludeLevel startKextExcludeLevel = kOSKextExcludeNone;
OSKextExcludeLevel startMatchingExcludeLevel = kOSKextExcludeNone;
IORecursiveLockLock(sKextLock);
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogIPCFlag,
"Received kext KC load request from user space.");
/* Regardless of processing, the fact that we have gotten here means some
* user-space program is up and talking to us, so we'll switch our kext
* registration to reflect that.
*/
if (!sUserLoadsActive) {
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Switching to late startup (user-space) kext loading policy.");
sUserLoadsActive = true;
}
delayAutounloadBool = OSDynamicCast(OSBoolean,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentDelayAutounloadKey));
startKextExcludeNum = OSDynamicCast(OSNumber,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentStartExcludeKey));
startMatchingExcludeNum = OSDynamicCast(OSNumber,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentStartMatchingExcludeKey));
personalityNames = OSDynamicCast(OSArray,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentPersonalityNamesKey));
if (delayAutounloadBool) {
delayAutounload = delayAutounloadBool->getValue();
}
if (startKextExcludeNum) {
startKextExcludeLevel = startKextExcludeNum->unsigned8BitValue();
}
if (startMatchingExcludeNum) {
startMatchingExcludeLevel = startMatchingExcludeNum->unsigned8BitValue();
}
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogIPCFlag,
"Received request from user space to load KC kext %s.",
theKext->getIdentifierCString());
/* this could be in the Auxiliary KC, so record the load request */
OSKext::recordIdentifierRequest(OSDynamicCast(OSString, theKext->getIdentifier()));
/*
* Load the kext
*/
result = theKext->load(startKextExcludeLevel,
startMatchingExcludeLevel, personalityNames);
if (result != kOSReturnSuccess) {
OSKextLog(theKext,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to load kext %s (error 0x%x).",
theKext->getIdentifierCString(), (int)result);
OSKext::removeKext(theKext,
/* terminateService/removePersonalities */ true);
goto finish;
} else {
OSKextLog(theKext,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"Kext %s Loaded successfully from %s KC",
theKext->getIdentifierCString(), theKext->getKCTypeString());
}
if (delayAutounload) {
OSKextLog(theKext,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag,
"Setting delayed autounload for %s.",
theKext->getIdentifierCString());
theKext->flags.delayAutounload = 1;
}
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::loadCodelessKext(OSString *kextIdentifier, OSDictionary *requestDict)
{
OSReturn result = kOSReturnError;
OSDictionary *anInfoDict = NULL; // do not release
anInfoDict = OSDynamicCast(OSDictionary,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentCodelessInfoKey));
if (anInfoDict == NULL) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Missing 'Codeless Kext Info' dictionary in codeless kext load request of %s.",
kextIdentifier->getCStringNoCopy());
return kOSKextReturnInvalidArgument;
}
IORecursiveLockLock(sKextLock);
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogIPCFlag,
"Received request from user space to load codeless kext %s.",
kextIdentifier->getCStringNoCopy());
{
// instantiate a new kext, and don't hold a reference
// (the kext subsystem will hold one implicitly)
OSSharedPtr<OSKext> newKext = OSKext::withCodelessInfo(anInfoDict);
if (!newKext) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Could not instantiate codeless kext.");
result = kOSKextReturnNotLoadable;
goto finish;
}
if (!kextIdentifier->isEqualTo(newKext->getIdentifierCString())) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag | kOSKextLogLoadFlag,
"Codeless kext identifiers don't match '%s' != '%s'",
kextIdentifier->getCStringNoCopy(), newKext->getIdentifierCString());
OSKext::removeKext(newKext.get(), false);
result = kOSKextReturnInvalidArgument;
goto finish;
}
/* Record the request for the codeless kext */
OSKext::recordIdentifierRequest(OSDynamicCast(OSString, newKext->getIdentifier()));
result = kOSReturnSuccess;
/* Send the kext's personalities to the IOCatalog. This is an explicit load. */
result = newKext->sendPersonalitiesToCatalog(true, NULL);
}
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::dropMatchingReferences(
OSSet * kexts)
{
IORecursiveLockLock(sKextLock);
kexts->iterateObjects(^bool (OSObject * obj) {
OSKext * thisKext = OSDynamicCast(OSKext, obj);
if (!thisKext) {
return false;
}
thisKext->matchingRefCount--;
return false;
});
IORecursiveLockUnlock(sKextLock);
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::recordIdentifierRequest(
OSString * kextIdentifier)
{
OSSharedPtr<const OSSymbol> kextIdentifierSymbol;
bool fail = false;
if (!sAllKextLoadIdentifiers || !kextIdentifier) {
goto finish;
}
kextIdentifierSymbol = OSSymbol::withString(kextIdentifier);
if (!kextIdentifierSymbol) {
// xxx - this is really a basic alloc failure
fail = true;
goto finish;
}
IORecursiveLockLock(sKextLock);
if (!sAllKextLoadIdentifiers->containsObject(kextIdentifierSymbol.get())) {
if (!sAllKextLoadIdentifiers->setObject(kextIdentifierSymbol.get())) {
fail = true;
} else {
// xxx - need to find a way to associate this whole func w/the kext
OSKextLog(/* kext */ NULL,
// xxx - check level
kOSKextLogStepLevel |
kOSKextLogArchiveFlag,
"Recorded kext %s as a candidate for inclusion in prelinked kernel.",
kextIdentifier->getCStringNoCopy());
}
}
IORecursiveLockUnlock(sKextLock);
finish:
if (fail) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogArchiveFlag,
"Failed to record kext %s as a candidate for inclusion in prelinked kernel.",
kextIdentifier->getCStringNoCopy());
}
return;
}
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::load(
OSKextExcludeLevel startOpt,
OSKextExcludeLevel startMatchingOpt,
OSArray * personalityNames)
{
OSReturn result = kOSReturnError;
OSKextExcludeLevel dependenciesStartOpt = startOpt;
OSKextExcludeLevel dependenciesStartMatchingOpt = startMatchingOpt;
unsigned int i, count;
Boolean alreadyLoaded = false;
OSKext * lastLoadedKext = NULL; // do not release
if (isInExcludeList()) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag |
kOSKextLogLoadFlag,
"Kext %s is in exclude list, not loadable",
getIdentifierCString());
result = kOSKextReturnNotLoadable;
goto finish;
}
if (!isLoadable()) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag |
kOSKextLogLoadFlag,
"Kext %s is not loadable",
getIdentifierCString());
result = kOSKextReturnNotLoadable;
goto finish;
}
if (isLoaded()) {
alreadyLoaded = true;
result = kOSReturnSuccess;
OSKextLog(this,
kOSKextLogDebugLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag,
"Kext %s is already loaded.",
getIdentifierCString());
goto loaded;
}
#if CONFIG_MACF && XNU_TARGET_OS_OSX
#if CONFIG_KXLD
if (current_task() != kernel_task) {
#else
/*
* On non-kxld systems, only check the mac-hook for kexts in the
* Pageable and Aux KCs. This means on Apple silicon devices that
* the mac hook will only be useful to block 3rd party kexts.
*
* Note that this should _not_ be called on kexts loaded from the
* kernel bootstrap thread as the kernel proc's cred struct is not
* yet initialized! This won't happen on macOS because all the kexts
* in the BootKC are self-contained and their kc_type = KCKindPrimary.
*/
if (kc_type != KCKindPrimary && kc_type != KCKindUnknown) {
#endif /* CONFIG_KXLD */
int macCheckResult = 0;
kauth_cred_t cred = NULL;
cred = kauth_cred_get_with_ref();
macCheckResult = mac_kext_check_load(cred, getIdentifierCString());
kauth_cred_unref(&cred);
if (macCheckResult != 0) {
result = kOSReturnError;
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Failed to load kext %s (MAC policy error 0x%x).",
getIdentifierCString(), macCheckResult);
goto finish;
}
}
#endif
if (!sLoadEnabled) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext loading is disabled (attempt to load kext %s).",
getIdentifierCString());
result = kOSKextReturnDisabled;
goto finish;
}
/* If we've pushed the next available load tag to the invalid value,
* we can't load any more kexts.
*/
if (sNextLoadTag == kOSKextInvalidLoadTag) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Can't load kext %s - no more load tags to assign.",
getIdentifierCString());
result = kOSKextReturnNoResources;
goto finish;
}
/* This is a bit of a hack, because we shouldn't be handling
* personalities within the load function.
*/
if (!declaresExecutable()) {
/* There is a special case where a non-executable kext can be loaded: the
* AppleKextExcludeList. Detect that special kext by bundle identifier and
* load its metadata into the global data structures, if appropriate
*/
if (strcmp(getIdentifierCString(), kIOExcludeListBundleID) == 0) {
boolean_t updated = updateExcludeList(infoDict.get());
if (updated) {
OSKextLog(this,
kOSKextLogDebugLevel | kOSKextLogLoadFlag,
"KextExcludeList was updated to version: %lld", sExcludeListVersion);
}
}
if (isDriverKit()) {
if (loadTag == 0) {
sLoadedDriverKitKexts->setObject(this);
loadTag = sNextLoadTag++;
}
}
result = kOSReturnSuccess;
goto loaded;
}
/* Are we in safe boot?
*/
if (sSafeBoot && !isLoadableInSafeBoot()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Can't load kext %s - not loadable during safe boot.",
getIdentifierCString());
result = kOSKextReturnBootLevel;
goto finish;
}
OSKextLog(this,
kOSKextLogProgressLevel | kOSKextLogLoadFlag,
"Loading kext %s.",
getIdentifierCString());
#if !VM_MAPPED_KEXTS
if (isPrelinked() == false) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Can't load kext %s - not in a kext collection.",
getIdentifierCString());
result = kOSKextReturnDisabled;
goto finish;
}
#endif /* defined(__x86_64__) */
#if CONFIG_KXLD
if (!sKxldContext) {
kern_return_t kxldResult;
kxldResult = kxld_create_context(&sKxldContext, &kern_allocate,
&kxld_log_callback, /* Flags */ (KXLDFlags) 0,
/* cputype */ 0, /* cpusubtype */ 0, /* page size */ 0);
if (kxldResult) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogLinkFlag,
"Can't load kext %s - failed to create link context.",
getIdentifierCString());
result = kOSKextReturnNoMemory;
goto finish;
}
}
#endif // CONFIG_KXLD
/* We only need to resolve dependencies once for the whole graph, but
* resolveDependencies will just return if there's no work to do, so it's
* safe to call it more than once.
*/
if (!resolveDependencies()) {
// xxx - check resolveDependencies() for log msg
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogDependenciesFlag,
"Can't load kext %s - failed to resolve library dependencies.",
getIdentifierCString());
result = kOSKextReturnDependencies;
goto finish;
}
/* If we are excluding just the kext being loaded now (and not its
* dependencies), drop the exclusion level to none so dependencies
* start and/or add their personalities.
*/
if (dependenciesStartOpt == kOSKextExcludeKext) {
dependenciesStartOpt = kOSKextExcludeNone;
}
if (dependenciesStartMatchingOpt == kOSKextExcludeKext) {
dependenciesStartMatchingOpt = kOSKextExcludeNone;
}
/* Load the dependencies, recursively.
*/
count = getNumDependencies();
for (i = 0; i < count; i++) {
OSKext * dependency = OSDynamicCast(OSKext,
dependencies->getObject(i));
if (dependency == NULL) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogDependenciesFlag,
"Internal error loading kext %s; dependency disappeared.",
getIdentifierCString());
result = kOSKextReturnInternalError;
goto finish;
}
/* Dependencies must be started accorting to the opt,
* but not given the personality names of the main kext.
*/
result = dependency->load(dependenciesStartOpt,
dependenciesStartMatchingOpt,
/* personalityNames */ NULL);
if (result != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogDependenciesFlag,
"Dependency %s of kext %s failed to load.",
dependency->getIdentifierCString(),
getIdentifierCString());
OSKext::removeKext(dependency,
/* terminateService/removePersonalities */ true);
result = kOSKextReturnDependencyLoadError;
goto finish;
}
}
result = loadExecutable();
if (result != KERN_SUCCESS) {
goto finish;
}
pendingPgoHead.next = &pendingPgoHead;
pendingPgoHead.prev = &pendingPgoHead;
// The kernel PRNG is not initialized when the first kext is
// loaded, so use early random
uuid_generate_early_random(instance_uuid);
account = IONew(OSKextAccount, 1);
if (!account) {
result = KERN_MEMORY_ERROR;
goto finish;
}
bzero(account, sizeof(*account));
account->loadTag = kmod_info->id;
account->site.refcount = 0;
account->site.flags = VM_TAG_KMOD;
account->kext = this;
if (gIOSurfaceIdentifier == bundleID) {
vm_tag_alloc(&account->site);
gIOSurfaceTag = account->site.tag;
}
flags.loaded = true;
/* Add the kext to the list of loaded kexts and update the kmod_info
* struct to point to that of the last loaded kext (which is the way
* it's always been done, though I'd rather do them in order now).
*/
lastLoadedKext = OSDynamicCast(OSKext, sLoadedKexts->getLastObject());
sLoadedKexts->setObject(this);
/* Keep the kernel itself out of the kmod list.
*/
if (lastLoadedKext->isKernel()) {
lastLoadedKext = NULL;
}
if (lastLoadedKext) {
kmod_info->next = lastLoadedKext->kmod_info;
}
notifyKextLoadObservers(this, kmod_info);
/* Make the global kmod list point at the just-loaded kext. Note that the
* __kernel__ kext isn't in this list, as it wasn't before SnowLeopard,
* although we do report it in kextstat these days by using the newer
* OSArray of loaded kexts, which does contain it.
*
* (The OSKext object representing the kernel doesn't even have a kmod_info
* struct, though I suppose we could stick a pointer to it from the
* static struct in OSRuntime.cpp.)
*/
kmod = kmod_info;
/* Save the list of loaded kexts in case we panic.
*/
OSKext::saveLoadedKextPanicList();
if (isExecutable()) {
OSKext::updateLoadedKextSummaries();
savePanicString(/* isLoading */ true);
#if CONFIG_DTRACE
registerWithDTrace();
#else
jettisonLinkeditSegment();
#endif /* CONFIG_DTRACE */
#if !VM_MAPPED_KEXTS
/* If there is a page (or more) worth of padding after the end
* of the last data section but before the end of the data segment
* then free it in the same manner the LinkeditSegment is freed
*/
jettisonDATASegmentPadding();
#endif
}
loaded:
if (isExecutable() && !flags.started) {
if (startOpt == kOSKextExcludeNone) {
result = start();
if (result != kOSReturnSuccess) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Kext %s start failed (result 0x%x).",
getIdentifierCString(), result);
result = kOSKextReturnStartStopError;
}
}
}
/* If not excluding matching, send the personalities to the kernel.
* This never affects the result of the load operation.
* This is a bit of a hack, because we shouldn't be handling
* personalities within the load function.
*/
if (result == kOSReturnSuccess && startMatchingOpt == kOSKextExcludeNone) {
result = sendPersonalitiesToCatalog(true, personalityNames);
}
finish:
if (result != kOSReturnSuccess) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s failed to load (0x%x).",
getIdentifierCString(), (int)result);
} else if (!alreadyLoaded) {
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"Kext %s loaded.",
getIdentifierCString());
queueKextNotification(kKextRequestPredicateLoadNotification,
OSDynamicCast(OSString, bundleID.get()));
}
return result;
}
#if CONFIG_KXLD
/*********************************************************************
*
*********************************************************************/
static char *
strdup(const char * string)
{
char * result = NULL;
size_t size;
if (!string) {
goto finish;
}
size = 1 + strlen(string);
result = (char *)kheap_alloc_tag(KHEAP_DATA_BUFFERS, size,
Z_WAITOK, VM_KERN_MEMORY_OSKEXT);
if (!result) {
goto finish;
}
memcpy(result, string, size);
finish:
return result;
}
#endif // CONFIG_KXLD
/*********************************************************************
*
*********************************************************************/
kernel_section_t *
OSKext::lookupSection(const char *segname, const char *secname)
{
kernel_section_t * found_section = NULL;
kernel_mach_header_t * mh = NULL;
kernel_segment_command_t * seg = NULL;
kernel_section_t * sec = NULL;
if (!linkedExecutable) {
return NULL;
}
mh = (kernel_mach_header_t *)linkedExecutable->getBytesNoCopy();
for (seg = firstsegfromheader(mh); seg != NULL; seg = nextsegfromheader(mh, seg)) {
if (0 != strncmp(seg->segname, segname, sizeof(seg->segname))) {
continue;
}
for (sec = firstsect(seg); sec != NULL; sec = nextsect(seg, sec)) {
if (0 == strncmp(sec->sectname, secname, sizeof(sec->sectname))) {
found_section = sec;
goto out;
}
}
}
out:
return found_section;
}
/*********************************************************************
*
*********************************************************************/
OSReturn
OSKext::slidePrelinkedExecutable(bool doCoalescedSlides)
{
OSReturn result = kOSKextReturnBadData;
kernel_mach_header_t * mh = NULL;
kernel_segment_command_t * seg = NULL;
kernel_segment_command_t * linkeditSeg = NULL;
kernel_section_t * sec = NULL;
char * linkeditBase = NULL;
bool haveLinkeditBase = false;
char * relocBase = NULL;
bool haveRelocBase = false;
struct dysymtab_command * dysymtab = NULL;
struct linkedit_data_command * segmentSplitInfo = NULL;
struct symtab_command * symtab = NULL;
kernel_nlist_t * sym = NULL;
struct relocation_info * reloc = NULL;
uint32_t i = 0;
int reloc_size;
vm_offset_t new_kextsize;
if (linkedExecutable == NULL || flags.builtin) {
result = kOSReturnSuccess;
goto finish;
}
mh = (kernel_mach_header_t *)linkedExecutable->getBytesNoCopy();
if (kernel_mach_header_is_in_fileset(mh)) {
// kexts in filesets are slid as part of collection sliding
result = kOSReturnSuccess;
goto finish;
}
segmentSplitInfo = (struct linkedit_data_command *) getcommandfromheader(mh, LC_SEGMENT_SPLIT_INFO);
for (seg = firstsegfromheader(mh); seg != NULL; seg = nextsegfromheader(mh, seg)) {
if (!seg->vmaddr) {
continue;
}
seg->vmaddr = ml_static_slide(seg->vmaddr);
#if KASLR_KEXT_DEBUG
IOLog("kaslr: segname %s unslid 0x%lx slid 0x%lx \n",
seg->segname,
(unsigned long)ml_static_unslide(seg->vmaddr),
(unsigned long)seg->vmaddr);
#endif
if (!haveRelocBase) {
relocBase = (char *) seg->vmaddr;
haveRelocBase = true;
}
if (!strcmp(seg->segname, "__LINKEDIT")) {
linkeditBase = (char *) seg->vmaddr - seg->fileoff;
haveLinkeditBase = true;
linkeditSeg = seg;
}
for (sec = firstsect(seg); sec != NULL; sec = nextsect(seg, sec)) {
sec->addr = ml_static_slide(sec->addr);
#if KASLR_KEXT_DEBUG
IOLog("kaslr: sectname %s unslid 0x%lx slid 0x%lx \n",
sec->sectname,
(unsigned long)ml_static_unslide(sec->addr),
(unsigned long)sec->addr);
#endif
}
}
dysymtab = (struct dysymtab_command *) getcommandfromheader(mh, LC_DYSYMTAB);
symtab = (struct symtab_command *) getcommandfromheader(mh, LC_SYMTAB);
if (symtab != NULL && doCoalescedSlides == false) {
/* Some pseudo-kexts have symbol tables without segments.
* Ignore them. */
if (symtab->nsyms > 0 && haveLinkeditBase) {
sym = (kernel_nlist_t *) (linkeditBase + symtab->symoff);
for (i = 0; i < symtab->nsyms; i++) {
if (sym[i].n_type & N_STAB) {
continue;
}
sym[i].n_value = ml_static_slide(sym[i].n_value);
#if KASLR_KEXT_DEBUG
#define MAX_SYMS_TO_LOG 5
if (i < MAX_SYMS_TO_LOG) {
IOLog("kaslr: LC_SYMTAB unslid 0x%lx slid 0x%lx \n",
(unsigned long)ml_static_unslide(sym[i].n_value),
(unsigned long)sym[i].n_value);
}
#endif
}
}
}
if (dysymtab != NULL && doCoalescedSlides == false) {
if (dysymtab->nextrel > 0) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag |
kOSKextLogLinkFlag,
"Sliding kext %s: External relocations found.",
getIdentifierCString());
goto finish;
}
if (dysymtab->nlocrel > 0) {
if (!haveLinkeditBase) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag |
kOSKextLogLinkFlag,
"Sliding kext %s: No linkedit segment.",
getIdentifierCString());
goto finish;
}
if (!haveRelocBase) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag |
kOSKextLogLinkFlag,
#if __x86_64__
"Sliding kext %s: No writable segments.",
#else
"Sliding kext %s: No segments.",
#endif
getIdentifierCString());
goto finish;
}
reloc = (struct relocation_info *) (linkeditBase + dysymtab->locreloff);
reloc_size = dysymtab->nlocrel * sizeof(struct relocation_info);
for (i = 0; i < dysymtab->nlocrel; i++) {
if (reloc[i].r_extern != 0
|| reloc[i].r_type != 0
|| reloc[i].r_length != (sizeof(void *) == 8 ? 3 : 2)
) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag |
kOSKextLogLinkFlag,
"Sliding kext %s: Unexpected relocation found.",
getIdentifierCString());
goto finish;
}
if (reloc[i].r_pcrel != 0) {
continue;
}
uintptr_t *relocAddr = (uintptr_t*)(relocBase + reloc[i].r_address);
*relocAddr = ml_static_slide(*relocAddr);
#if KASLR_KEXT_DEBUG
#define MAX_DYSYMS_TO_LOG 5
if (i < MAX_DYSYMS_TO_LOG) {
IOLog("kaslr: LC_DYSYMTAB unslid 0x%lx slid 0x%lx \n",
(unsigned long)ml_static_unslide(*((uintptr_t *)(relocAddr))),
(unsigned long)*((uintptr_t *)(relocBase + reloc[i].r_address)));
}
#endif
}
/* We should free these relocations, not just delete the reference to them.
* <rdar://problem/10535549> Free relocations from PIE kexts.
*
* For now, we do not free LINKEDIT for kexts with split segments.
*/
new_kextsize = round_page(kmod_info->size - reloc_size);
if (new_kextsize > UINT_MAX) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag |
kOSKextLogLinkFlag,
"Kext %s: new kext size is too large.",
getIdentifierCString());
goto finish;
}
if (((kmod_info->size - new_kextsize) > PAGE_SIZE) && (!segmentSplitInfo)) {
vm_offset_t endofkext = kmod_info->address + kmod_info->size;
vm_offset_t new_endofkext = kmod_info->address + new_kextsize;
vm_offset_t endofrelocInfo = (vm_offset_t) (((uint8_t *)reloc) + reloc_size);
size_t bytes_remaining = endofkext - endofrelocInfo;
OSSharedPtr<OSData> new_osdata;
/* fix up symbol offsets if they are after the dsymtab local relocs */
if (symtab) {
if (dysymtab->locreloff < symtab->symoff) {
symtab->symoff -= reloc_size;
}
if (dysymtab->locreloff < symtab->stroff) {
symtab->stroff -= reloc_size;
}
}
if (dysymtab->locreloff < dysymtab->extreloff) {
dysymtab->extreloff -= reloc_size;
}
/* move data behind reloc info down to new offset */
if (endofrelocInfo < endofkext) {
memcpy(reloc, (void *)endofrelocInfo, bytes_remaining);
}
/* Create a new OSData for the smaller kext object and reflect
* new linkedit segment size.
*/
linkeditSeg->vmsize = round_page(linkeditSeg->vmsize - reloc_size);
linkeditSeg->filesize = linkeditSeg->vmsize;
new_osdata = OSData::withBytesNoCopy((void *)kmod_info->address, (unsigned int)new_kextsize);
if (new_osdata) {
/* Fix up kmod info and linkedExecutable.
*/
kmod_info->size = new_kextsize;
#if VM_MAPPED_KEXTS
new_osdata->setDeallocFunction(osdata_kext_free);
#else
new_osdata->setDeallocFunction(osdata_phys_free);
#endif
linkedExecutable->setDeallocFunction(NULL);
linkedExecutable = os::move(new_osdata);
#if VM_MAPPED_KEXTS
kext_free(new_endofkext, (endofkext - new_endofkext));
#else
ml_static_mfree(new_endofkext, (endofkext - new_endofkext));
#endif
}
}
dysymtab->nlocrel = 0;
dysymtab->locreloff = 0;
}
}
result = kOSReturnSuccess;
finish:
return result;
}
/*********************************************************************
* called only by load()
*********************************************************************/
OSReturn
OSKext::loadExecutable()
{
OSReturn result = kOSReturnError;
OSSharedPtr<OSArray> linkDependencies;
uint32_t num_kmod_refs = 0;
OSData * theExecutable = NULL; // do not release
OSString * versString = NULL; // do not release
const char * versCString = NULL; // do not free
const char * string = NULL; // do not free
#if CONFIG_KXLD
unsigned int i;
uint32_t numDirectDependencies = 0;
kern_return_t kxldResult;
KXLDDependency * kxlddeps = NULL; // must kfree
uint32_t num_kxlddeps = 0;
struct mach_header ** kxldHeaderPtr = NULL; // do not free
struct mach_header * kxld_header = NULL; // xxx - need to free here?
#endif // CONFIG_KXLD
/* We need the version string for a variety of bits below.
*/
versString = OSDynamicCast(OSString,
getPropertyForHostArch(kCFBundleVersionKey));
if (!versString) {
goto finish;
}
versCString = versString->getCStringNoCopy();
if (isKernelComponent()) {
if (STRING_HAS_PREFIX(versCString, KERNEL_LIB_PREFIX)) {
if (strncmp(versCString, KERNEL6_VERSION, strlen(KERNEL6_VERSION))) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kernel component %s has incorrect version %s; "
"expected %s.",
getIdentifierCString(),
versCString, KERNEL6_VERSION);
result = kOSKextReturnInternalError;
goto finish;
} else if (strcmp(versCString, osrelease)) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kernel component %s has incorrect version %s; "
"expected %s.",
getIdentifierCString(),
versCString, osrelease);
result = kOSKextReturnInternalError;
goto finish;
}
}
}
#if defined(__x86_64__) || defined(__i386__)
if (flags.resetSegmentsFromVnode) {
/* Fixup the chains and slide the mach headers */
kernel_mach_header_t *mh = (kernel_mach_header_t *)kmod_info->address;
if (i386_slide_individual_kext(mh, PE_get_kc_slide(kc_type)) != KERN_SUCCESS) {
result = kOSKextReturnValidation;
goto finish;
}
}
#endif //(__x86_64__) || defined(__i386__)
if (isPrelinked()) {
goto register_kmod;
}
/* <rdar://problem/21444003> all callers must be entitled */
if (FALSE == IOTaskHasEntitlement(current_task(), kOSKextCollectionManagementEntitlement)) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Not entitled to link kext '%s'",
getIdentifierCString());
result = kOSKextReturnNotPrivileged;
goto finish;
}
theExecutable = getExecutable();
if (!theExecutable) {
if (declaresExecutable()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Can't load kext %s - executable is missing.",
getIdentifierCString());
result = kOSKextReturnValidation;
goto finish;
}
goto register_kmod;
}
if (isInterface()) {
OSSharedPtr<OSData> executableCopy = OSData::withData(theExecutable);
if (executableCopy) {
setLinkedExecutable(executableCopy.get());
}
goto register_kmod;
}
#if CONFIG_KXLD
numDirectDependencies = getNumDependencies();
if (flags.hasBleedthrough) {
linkDependencies = dependencies;
} else {
linkDependencies = OSArray::withArray(dependencies.get());
if (!linkDependencies) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogLinkFlag,
"Can't allocate link dependencies to load kext %s.",
getIdentifierCString());
goto finish;
}
for (i = 0; i < numDirectDependencies; ++i) {
OSKext * dependencyKext = OSDynamicCast(OSKext,
dependencies->getObject(i));
dependencyKext->addBleedthroughDependencies(linkDependencies.get());
}
}
num_kxlddeps = linkDependencies->getCount();
if (!num_kxlddeps) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogDependenciesFlag,
"Can't load kext %s - it has no library dependencies.",
getIdentifierCString());
goto finish;
}
kxlddeps = (KXLDDependency *)kalloc_tag(num_kxlddeps * sizeof(*kxlddeps), VM_KERN_MEMORY_OSKEXT);
if (!kxlddeps) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogLinkFlag,
"Can't allocate link context to load kext %s.",
getIdentifierCString());
goto finish;
}
bzero(kxlddeps, num_kxlddeps * sizeof(*kxlddeps));
for (i = 0; i < num_kxlddeps; ++i) {
OSKext * dependency = OSDynamicCast(OSKext, linkDependencies->getObject(i));
if (dependency->isInterface()) {
OSKext *interfaceTargetKext = NULL; //do not release
OSData * interfaceTarget = NULL; //do not release
if (dependency->isKernelComponent()) {
interfaceTargetKext = sKernelKext;
interfaceTarget = sKernelKext->linkedExecutable.get();
} else {
interfaceTargetKext = OSDynamicCast(OSKext,
dependency->dependencies->getObject(0));
interfaceTarget = interfaceTargetKext->linkedExecutable.get();
}
if (!interfaceTarget) {
// panic?
goto finish;
}
/* The names set here aren't actually logged yet <rdar://problem/7941514>,
* it will be useful to have them in the debugger.
* strdup() failing isn't critical right here so we don't check that.
*/
kxlddeps[i].kext = (u_char *) interfaceTarget->getBytesNoCopy();
kxlddeps[i].kext_size = interfaceTarget->getLength();
kxlddeps[i].kext_name = strdup(interfaceTargetKext->getIdentifierCString());
if (dependency->linkedExecutable != NULL) {
kxlddeps[i].interface = (u_char *) dependency->linkedExecutable->getBytesNoCopy();
kxlddeps[i].interface_size = dependency->linkedExecutable->getLength();
} else {
kxlddeps[i].interface = (u_char *) NULL;
kxlddeps[i].interface_size = 0;
}
kxlddeps[i].interface_name = strdup(dependency->getIdentifierCString());
} else {
kxlddeps[i].kext = (u_char *) dependency->linkedExecutable->getBytesNoCopy();
kxlddeps[i].kext_size = dependency->linkedExecutable->getLength();
kxlddeps[i].kext_name = strdup(dependency->getIdentifierCString());
}
kxlddeps[i].is_direct_dependency = (i < numDirectDependencies);
}
kxldHeaderPtr = &kxld_header;
#if DEBUG
OSKextLog(this,
kOSKextLogExplicitLevel |
kOSKextLogLoadFlag | kOSKextLogLinkFlag,
"Kext %s - calling kxld_link_file:\n"
" kxld_context: %p\n"
" executable: %p executable_length: %d\n"
" user_data: %p\n"
" kxld_dependencies: %p num_dependencies: %d\n"
" kxld_header_ptr: %p kmod_info_ptr: %p\n",
getIdentifierCString(), sKxldContext,
theExecutable->getBytesNoCopy(), theExecutable->getLength(),
this, kxlddeps, num_kxlddeps,
kxldHeaderPtr, &kmod_info);
#endif
/* After this call, the linkedExecutable instance variable
* should exist.
*/
kxldResult = kxld_link_file(sKxldContext,
(u_char *)theExecutable->getBytesNoCopy(),
theExecutable->getLength(),
getIdentifierCString(), this, kxlddeps, num_kxlddeps,
(u_char **)kxldHeaderPtr, (kxld_addr_t *)&kmod_info);
if (kxldResult != KERN_SUCCESS) {
// xxx - add kxldResult here?
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Can't load kext %s - link failed.",
getIdentifierCString());
result = kOSKextReturnLinkError;
goto finish;
}
/* We've written data & instructions into kernel memory, so flush the data
* cache and invalidate the instruction cache.
* I/D caches are coherent on x86
*/
#if !defined(__i386__) && !defined(__x86_64__)
flush_dcache(kmod_info->address, kmod_info->size, false);
invalidate_icache(kmod_info->address, kmod_info->size, false);
#endif
#else // !CONFIG_KXLD
OSKextLog(this, kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Refusing to link non-prelinked kext: %s (no kxld support)", getIdentifierCString());
result = kOSKextReturnLinkError;
goto finish;
#endif // CONFIG_KXLD
register_kmod:
if (isInterface()) {
/* Whip up a fake kmod_info entry for the interface kext.
*/
kmod_info = (kmod_info_t *)kalloc_tag(sizeof(kmod_info_t), VM_KERN_MEMORY_OSKEXT);
if (!kmod_info) {
result = KERN_MEMORY_ERROR;
goto finish;
}
/* A pseudokext has almost nothing in its kmod_info struct.
*/
bzero(kmod_info, sizeof(kmod_info_t));
kmod_info->info_version = KMOD_INFO_VERSION;
/* An interface kext doesn't have a linkedExecutable, so save a
* copy of the UUID out of the original executable via copyUUID()
* while we still have the original executable.
*/
interfaceUUID = copyUUID();
}
kmod_info->id = loadTag = sNextLoadTag++;
kmod_info->reference_count = 0; // KMOD_DECL... sets it to -1 (invalid).
/* Stamp the bundle ID and version from the OSKext over anything
* resident inside the kmod_info.
*/
string = getIdentifierCString();
strlcpy(kmod_info->name, string, sizeof(kmod_info->name));
string = versCString;
strlcpy(kmod_info->version, string, sizeof(kmod_info->version));
/* Add the dependencies' kmod_info structs as kmod_references.
*/
num_kmod_refs = getNumDependencies();
if (num_kmod_refs) {
kmod_info->reference_list = (kmod_reference_t *)kalloc_tag(
num_kmod_refs * sizeof(kmod_reference_t), VM_KERN_MEMORY_OSKEXT);
if (!kmod_info->reference_list) {
result = KERN_MEMORY_ERROR;
goto finish;
}
bzero(kmod_info->reference_list,
num_kmod_refs * sizeof(kmod_reference_t));
for (uint32_t refIndex = 0; refIndex < num_kmod_refs; refIndex++) {
kmod_reference_t * ref = &(kmod_info->reference_list[refIndex]);
OSKext * refKext = OSDynamicCast(OSKext, dependencies->getObject(refIndex));
ref->info = refKext->kmod_info;
ref->info->reference_count++;
if (refIndex + 1 < num_kmod_refs) {
ref->next = kmod_info->reference_list + refIndex + 1;
}
}
}
if (kmod_info->hdr_size > UINT32_MAX) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
#if __LP64__
"Kext %s header size is too large (%lu > UINT32_MAX).",
#else
"Kext %s header size is too large (%u > UINT32_MAX).",
#endif
kmod_info->name,
kmod_info->hdr_size);
result = KERN_FAILURE;
goto finish;
}
if (kmod_info->size > UINT32_MAX) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
#if __LP64__
"Kext %s size is too large (%lu > UINT32_MAX).",
#else
"Kext %s size is too large (%u > UINT32_MAX).",
#endif
kmod_info->name,
kmod_info->size);
result = KERN_FAILURE;
goto finish;
}
if (!isInterface() && linkedExecutable) {
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"Kext %s executable loaded; %u pages at 0x%lx (load tag %u).",
kmod_info->name,
(unsigned)kmod_info->size / PAGE_SIZE,
(unsigned long)ml_static_unslide(kmod_info->address),
(unsigned)kmod_info->id);
}
/* VM protections and wiring for the Aux KC are done at collection loading time */
if (kc_type != KCKindAuxiliary || flags.resetSegmentsFromVnode) {
/* if prelinked and primary KC, VM protections are already set */
result = setVMAttributes(!isPrelinked() || flags.resetSegmentsFromVnode, true);
if (result != KERN_SUCCESS) {
goto finish;
}
}
#if KASAN
if (linkedExecutable) {
kasan_load_kext((vm_offset_t)linkedExecutable->getBytesNoCopy(),
linkedExecutable->getLength(), getIdentifierCString());
}
#else
if (lookupSection(KASAN_GLOBAL_SEGNAME, KASAN_GLOBAL_SECTNAME)) {
OSKextLog(this,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"KASAN: cannot load KASAN-ified kext %s on a non-KASAN kernel\n",
getIdentifierCString()
);
result = KERN_FAILURE;
goto finish;
}
#endif
result = kOSReturnSuccess;
finish:
#if CONFIG_KXLD
/* Clear up locally allocated dependency info.
*/
for (i = 0; i < num_kxlddeps; ++i) {
size_t size;
if (kxlddeps[i].kext_name) {
size = 1 + strlen(kxlddeps[i].kext_name);
kheap_free(KHEAP_DATA_BUFFERS, kxlddeps[i].kext_name, size);
}
if (kxlddeps[i].interface_name) {
size = 1 + strlen(kxlddeps[i].interface_name);
kheap_free(KHEAP_DATA_BUFFERS, kxlddeps[i].interface_name, size);
}
}
if (kxlddeps) {
kfree(kxlddeps, (num_kxlddeps * sizeof(*kxlddeps)));
}
#endif // CONFIG_KXLD
/* We no longer need the unrelocated executable (which the linker
* has altered anyhow).
*/
setExecutable(NULL);
if (result != kOSReturnSuccess) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to load executable for kext %s.",
getIdentifierCString());
if (kmod_info && kmod_info->reference_list) {
kfree(kmod_info->reference_list,
num_kmod_refs * sizeof(kmod_reference_t));
}
if (isInterface()) {
kfree(kmod_info, sizeof(kmod_info_t));
kmod_info = NULL;
}
if (kc_type == KCKindUnknown) {
kmod_info = NULL;
if (linkedExecutable) {
linkedExecutable.reset();
}
}
}
return result;
}
#if VM_MAPPED_KEXTS
/* static */
void
OSKext::jettisonFileSetLinkeditSegment(kernel_mach_header_t *mh)
{
kernel_segment_command_t *linkeditseg = NULL;
linkeditseg = getsegbynamefromheader(mh, SEG_LINKEDIT);
assert(linkeditseg != NULL);
/* BootKC on x86_64 is not vm mapped */
ml_static_mfree(linkeditseg->vmaddr, linkeditseg->vmsize);
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogGeneralFlag,
"Jettisoning fileset Linkedit segments from vmaddr %llx with size %llu",
linkeditseg->vmaddr, linkeditseg->vmsize);
}
#endif /* VM_MAPPED_KEXTS */
/*********************************************************************
* The linkedit segment is used by the kext linker for dependency
* resolution, and by dtrace for probe initialization. We can free it
* for non-library kexts, since no kexts depend on non-library kexts
* by definition, once dtrace has been initialized.
*********************************************************************/
void
OSKext::jettisonLinkeditSegment(void)
{
kernel_mach_header_t * machhdr = (kernel_mach_header_t *)kmod_info->address;
kernel_segment_command_t * linkedit = NULL;
vm_offset_t start;
vm_size_t linkeditsize, kextsize;
OSSharedPtr<OSData> data;
if (isInFileset()) {
return;
}
#if NO_KEXTD
/* We can free symbol tables for all embedded kexts because we don't
* support runtime kext linking.
*/
if (sKeepSymbols || !isExecutable() || !linkedExecutable || flags.jettisonLinkeditSeg) {
#else
if (sKeepSymbols || isLibrary() || !isExecutable() || !linkedExecutable || flags.jettisonLinkeditSeg) {
#endif
goto finish;
}
/* Find the linkedit segment. If it's not the last segment, then freeing
* it will fragment the kext into multiple VM regions, which OSKext is not
* designed to handle, so we'll have to skip it.
*/
linkedit = getsegbynamefromheader(machhdr, SEG_LINKEDIT);
if (!linkedit) {
goto finish;
}
if (round_page(kmod_info->address + kmod_info->size) !=
round_page(linkedit->vmaddr + linkedit->vmsize)) {
goto finish;
}
/* Create a new OSData for the smaller kext object.
*/
linkeditsize = round_page(linkedit->vmsize);
kextsize = kmod_info->size - linkeditsize;
start = linkedit->vmaddr;
if (kextsize > UINT_MAX) {
goto finish;
}
data = OSData::withBytesNoCopy((void *)kmod_info->address, (unsigned int)kextsize);
if (!data) {
goto finish;
}
/* Fix the kmod info and linkedExecutable.
*/
kmod_info->size = kextsize;
#if VM_MAPPED_KEXTS
data->setDeallocFunction(osdata_kext_free);
#else
data->setDeallocFunction(osdata_phys_free);
#endif
linkedExecutable->setDeallocFunction(NULL);
linkedExecutable = os::move(data);
flags.jettisonLinkeditSeg = 1;
/* Free the linkedit segment.
*/
#if VM_MAPPED_KEXTS
kext_free(start, linkeditsize);
#else
ml_static_mfree(start, linkeditsize);
#endif
finish:
return;
}
/*********************************************************************
* If there are whole pages that are unused betweem the last section
* of the DATA segment and the end of the DATA segment then we can free
* them
*********************************************************************/
void
OSKext::jettisonDATASegmentPadding(void)
{
kernel_mach_header_t * mh;
kernel_segment_command_t * dataSeg;
kernel_section_t * sec, * lastSec;
vm_offset_t dataSegEnd, lastSecEnd;
vm_size_t padSize;
if (flags.builtin) {
return;
}
mh = (kernel_mach_header_t *)kmod_info->address;
if (isInFileset()) {
return;
}
dataSeg = getsegbynamefromheader(mh, SEG_DATA);
if (dataSeg == NULL) {
return;
}
lastSec = NULL;
sec = firstsect(dataSeg);
while (sec != NULL) {
lastSec = sec;
sec = nextsect(dataSeg, sec);
}
if (lastSec == NULL) {
return;
}
if ((dataSeg->vmaddr != round_page(dataSeg->vmaddr)) ||
(dataSeg->vmsize != round_page(dataSeg->vmsize))) {
return;
}
dataSegEnd = dataSeg->vmaddr + dataSeg->vmsize;
lastSecEnd = round_page(lastSec->addr + lastSec->size);
if (dataSegEnd <= lastSecEnd) {
return;
}
padSize = dataSegEnd - lastSecEnd;
if (padSize >= PAGE_SIZE) {
#if VM_MAPPED_KEXTS
kext_free(lastSecEnd, padSize);
#else
ml_static_mfree(lastSecEnd, padSize);
#endif
}
}
/*********************************************************************
*********************************************************************/
void
OSKext::setLinkedExecutable(OSData * anExecutable)
{
if (linkedExecutable) {
panic("Attempt to set linked executable on kext "
"that already has one (%s).\n",
getIdentifierCString());
}
linkedExecutable.reset(anExecutable, OSRetain);
return;
}
#if CONFIG_DTRACE
/*********************************************************************
* Go through all loaded kexts and tell them to register with dtrace.
* The instance method only registers if necessary.
*********************************************************************/
/* static */
void
OSKext::registerKextsWithDTrace(void)
{
uint32_t count = sLoadedKexts->getCount();
uint32_t i;
IORecursiveLockLock(sKextLock);
for (i = 0; i < count; i++) {
OSKext * thisKext = NULL; // do not release
thisKext = OSDynamicCast(OSKext, sLoadedKexts->getObject(i));
if (!thisKext || !thisKext->isExecutable()) {
continue;
}
thisKext->registerWithDTrace();
}
IORecursiveLockUnlock(sKextLock);
return;
}
extern "C" {
extern int (*dtrace_modload)(struct kmod_info *, uint32_t);
extern int (*dtrace_modunload)(struct kmod_info *);
};
/*********************************************************************
*********************************************************************/
void
OSKext::registerWithDTrace(void)
{
/* Register kext with dtrace. A dtrace_modload failure should not
* prevent a kext from loading, so we ignore the return code.
*/
if (!flags.dtraceInitialized && (dtrace_modload != NULL)) {
uint32_t modflag = 0;
OSObject * forceInit = getPropertyForHostArch("OSBundleForceDTraceInit");
#if VM_MAPPED_KEXTS
if (!sKeepSymbols && kc_type == KCKindPrimary) {
if (forceInit == kOSBooleanTrue) {
/* Make sure the kext is not from the Boot KC */
panic("OSBundleForceDTraceInit key specified for the Boot KC kext : %s", getIdentifierCString());
} else {
/* Linkedit segment of the Boot KC is gone, make sure fbt_provide_module don't use kernel symbols */
modflag |= KMOD_DTRACE_NO_KERNEL_SYMS;
}
}
#endif /* VM_MAPPED_KEXTS */
if (forceInit == kOSBooleanTrue) {
modflag |= KMOD_DTRACE_FORCE_INIT;
}
if (flags.builtin) {
modflag |= KMOD_DTRACE_STATIC_KEXT;
}
(void)(*dtrace_modload)(kmod_info, modflag);
flags.dtraceInitialized = true;
jettisonLinkeditSegment();
}
return;
}
/*********************************************************************
*********************************************************************/
void
OSKext::unregisterWithDTrace(void)
{
/* Unregister kext with dtrace. A dtrace_modunload failure should not
* prevent a kext from loading, so we ignore the return code.
*/
if (flags.dtraceInitialized && (dtrace_modunload != NULL)) {
(void)(*dtrace_modunload)(kmod_info);
flags.dtraceInitialized = false;
}
return;
}
#endif /* CONFIG_DTRACE */
/*********************************************************************
* called only by loadExecutable()
*********************************************************************/
#if !VM_MAPPED_KEXTS
#if defined(__arm__) || defined(__arm64__)
static inline kern_return_t
OSKext_protect(
kernel_mach_header_t *kext_mh,
vm_map_t map,
vm_map_offset_t start,
vm_map_offset_t end,
vm_prot_t new_prot,
boolean_t set_max,
kc_kind_t kc_type)
{
#pragma unused(kext_mh,map,kc_type)
assert(map == kernel_map); // we can handle KEXTs arising from the PRELINK segment and no others
assert(start <= end);
if (start >= end) {
return KERN_SUCCESS; // Punt segments of length zero (e.g., headers) or less (i.e., blunders)
} else if (set_max) {
return KERN_SUCCESS; // Punt set_max, as there's no mechanism to record that state
} else {
return ml_static_protect(start, end - start, new_prot);
}
}
static inline kern_return_t
OSKext_wire(
kernel_mach_header_t *kext_mh,
vm_map_t map,
vm_map_offset_t start,
vm_map_offset_t end,
vm_prot_t access_type,
boolean_t user_wire,
kc_kind_t kc_type)
{
#pragma unused(kext_mh,map,start,end,access_type,user_wire,kc_type)
return KERN_SUCCESS; // No-op as PRELINK kexts are cemented into physical memory at boot
}
#else
#error Unrecognized architecture
#endif
#else
static inline kern_return_t
OSKext_protect(
kernel_mach_header_t *kext_mh,
vm_map_t map,
vm_map_offset_t start,
vm_map_offset_t end,
vm_prot_t new_prot,
boolean_t set_max,
kc_kind_t kc_type)
{
if (start == end) { // 10538581
return KERN_SUCCESS;
}
if (kernel_mach_header_is_in_fileset(kext_mh) && kc_type == KCKindPrimary) {
/*
* XXX: This will probably need to be different for AuxKC and
* pageableKC!
*/
return ml_static_protect(start, end - start, new_prot);
}
return vm_map_protect(map, start, end, new_prot, set_max);
}
static inline kern_return_t
OSKext_wire(
kernel_mach_header_t *kext_mh,
vm_map_t map,
vm_map_offset_t start,
vm_map_offset_t end,
vm_prot_t access_type,
boolean_t user_wire,
kc_kind_t kc_type)
{
if (kernel_mach_header_is_in_fileset(kext_mh) && kc_type == KCKindPrimary) {
/* TODO: we may need to hook this for the pageableKC */
return KERN_SUCCESS;
}
return vm_map_wire_kernel(map, start, end, access_type, VM_KERN_MEMORY_KEXT, user_wire);
}
#endif
OSReturn
OSKext::setVMAttributes(bool protect, bool wire)
{
vm_map_t kext_map = NULL;
kernel_segment_command_t * seg = NULL;
vm_map_offset_t start_protect = 0;
vm_map_offset_t start_wire = 0;
vm_map_offset_t end_protect = 0;
vm_map_offset_t end_wire = 0;
OSReturn result = kOSReturnError;
if (isInterface() || !declaresExecutable() || flags.builtin) {
result = kOSReturnSuccess;
goto finish;
}
/* Get the kext's vm map */
kext_map = kext_get_vm_map(kmod_info);
if (!kext_map) {
result = KERN_MEMORY_ERROR;
goto finish;
}
#if !VM_MAPPED_KEXTS
if (getcommandfromheader((kernel_mach_header_t *)kmod_info->address, LC_SEGMENT_SPLIT_INFO)) {
/* This is a split kext in a prelinked kernelcache; we'll let the
* platform code take care of protecting it. It is already wired.
*/
/* TODO: Should this still allow protections for the first segment
* to go through, in the event that we have a mix of split and
* unsplit kexts?
*/
result = KERN_SUCCESS;
goto finish;
}
if (isInFileset() && kc_type != KCKindPageable) {
// kexts in filesets have protections setup as part of collection loading
result = KERN_SUCCESS;
goto finish;
}
#endif
/* Protect the headers as read-only; they do not need to be wired */
result = (protect) ? OSKext_protect((kernel_mach_header_t *)kmod_info->address,
kext_map, kmod_info->address,
kmod_info->address + kmod_info->hdr_size, VM_PROT_READ, TRUE, kc_type)
: KERN_SUCCESS;
if (result != KERN_SUCCESS) {
goto finish;
}
/* Set the VM protections and wire down each of the segments */
seg = firstsegfromheader((kernel_mach_header_t *)kmod_info->address);
while (seg) {
#if __arm__
/* We build all ARM kexts, so we can ensure they are aligned */
assert((seg->vmaddr & PAGE_MASK) == 0);
assert((seg->vmsize & PAGE_MASK) == 0);
#endif
/*
* For the non page aligned segments, the range calculation for protection
* and wiring differ as follows:
*
* Protection: The non page aligned data at the start or at the end of the
* segment is excluded from the protection. This exclusion is needed to make
* sure OSKext_protect is not called twice on same page, if the page is shared
* between two segments.
*
* Wiring: The non page aligned data at the start or at the end of the
* segment is included in the wiring range, this inclusion is needed to make sure
* all the data of the segment is wired.
*/
start_protect = round_page(seg->vmaddr);
end_protect = trunc_page(seg->vmaddr + seg->vmsize);
start_wire = trunc_page(seg->vmaddr);
end_wire = round_page(seg->vmaddr + seg->vmsize);
/*
* Linkedit and Linkinfo for the Pageable KC and the Aux KC are shared
* across kexts and data from kexts is not page aligned
*/
if (protect && (end_protect > start_protect) &&
((strncmp(seg->segname, SEG_LINKEDIT, sizeof(seg->segname)) != 0 &&
strncmp(seg->segname, SEG_LINKINFO, sizeof(seg->segname)) != 0) ||
(kc_type != KCKindPageable && kc_type != KCKindAuxiliary))) {
result = OSKext_protect((kernel_mach_header_t *)kmod_info->address,
kext_map, start_protect, end_protect, seg->maxprot, TRUE, kc_type);
if (result != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s failed to set maximum VM protections "
"for segment %s - 0x%x.",
getIdentifierCString(), seg->segname, (int)result);
goto finish;
}
result = OSKext_protect((kernel_mach_header_t *)kmod_info->address,
kext_map, start_protect, end_protect, seg->initprot, FALSE, kc_type);
if (result != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s failed to set initial VM protections "
"for segment %s - 0x%x.",
getIdentifierCString(), seg->segname, (int)result);
goto finish;
}
}
if (segmentShouldBeWired(seg) && wire) {
result = OSKext_wire((kernel_mach_header_t *)kmod_info->address,
kext_map, start_wire, end_wire, seg->initprot, FALSE, kc_type);
if (result != KERN_SUCCESS) {
goto finish;
}
}
seg = nextsegfromheader((kernel_mach_header_t *) kmod_info->address, seg);
}
finish:
return result;
}
/*********************************************************************
*********************************************************************/
boolean_t
OSKext::segmentShouldBeWired(kernel_segment_command_t *seg)
{
return sKeepSymbols || (strncmp(seg->segname, SEG_LINKEDIT, sizeof(seg->segname)) &&
strncmp(seg->segname, SEG_LINKINFO, sizeof(seg->segname)));
}
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::validateKextMapping(bool startFlag)
{
OSReturn result = kOSReturnError;
const char * whichOp = startFlag ? "start" : "stop";
kern_return_t kern_result = 0;
vm_map_t kext_map = NULL;
kernel_segment_command_t * seg = NULL;
mach_vm_address_t address = 0;
mach_vm_size_t size = 0;
uint32_t depth = 0;
uint64_t kext_segbase = 0;
uint64_t kext_segsize = 0;
mach_msg_type_number_t count;
vm_region_submap_short_info_data_64_t info;
uintptr_t kext_slide = PE_get_kc_slide(kc_type);
if (flags.builtin) {
return kOSReturnSuccess;
}
count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
bzero(&info, sizeof(info));
// xxx - do we need a distinct OSReturn value for these or is "bad data"
// xxx - sufficient?
/* Verify that the kmod_info and start/stop pointers are non-NULL.
*/
if (!kmod_info) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s - NULL kmod_info pointer.",
getIdentifierCString());
result = kOSKextReturnBadData;
goto finish;
}
if (startFlag) {
address = (mach_vm_address_t)kmod_info->start;
} else {
address = (mach_vm_address_t)kmod_info->stop;
}
if (!address) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s - NULL module %s pointer.",
getIdentifierCString(), whichOp);
result = kOSKextReturnBadData;
goto finish;
}
kext_map = kext_get_vm_map(kmod_info);
depth = (kernel_map == kext_map) ? 1 : 2;
if (isInFileset()) {
#if defined(HAS_APPLE_PAC)
address = (mach_vm_address_t)ptrauth_auth_data((void*)address, ptrauth_key_function_pointer, 0);
#endif /* defined(HAS_APPLE_PAC) */
}
/* Verify that the start/stop function lies within the kext's address range.
*/
if (getcommandfromheader((kernel_mach_header_t *)kmod_info->address, LC_SEGMENT_SPLIT_INFO) ||
isInFileset()) {
/* This will likely be how we deal with split kexts; walk the segments to
* check that the function lies inside one of the segments of this kext.
*/
for (seg = firstsegfromheader((kernel_mach_header_t *)kmod_info->address);
seg != NULL;
seg = nextsegfromheader((kernel_mach_header_t *)kmod_info->address, seg)) {
if ((address >= seg->vmaddr) && address < (seg->vmaddr + seg->vmsize)) {
kext_segbase = seg->vmaddr;
kext_segsize = seg->vmsize;
break;
}
}
if (!seg) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s module %s pointer is outside of kext range "
"(%s %p - kext starts at %p).",
getIdentifierCString(),
whichOp,
whichOp,
(void *)(((uintptr_t)address) - kext_slide),
(void *)(((uintptr_t)kmod_info->address) - kext_slide));
result = kOSKextReturnBadData;
goto finish;
}
seg = NULL;
} else {
if (address < kmod_info->address + kmod_info->hdr_size ||
kmod_info->address + kmod_info->size <= address) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s module %s pointer is outside of kext range "
"(%s %p - kext at %p-%p).",
getIdentifierCString(),
whichOp,
whichOp,
(void *)(((uintptr_t)address) - kext_slide),
(void *)(((uintptr_t)kmod_info->address) - kext_slide),
(void *)((((uintptr_t)kmod_info->address) - kext_slide) + kmod_info->size));
result = kOSKextReturnBadData;
goto finish;
}
}
/* Only do these checks before calling the start function;
* If anything goes wrong with the mapping while the kext is running,
* we'll likely have panicked well before any attempt to stop the kext.
*/
if (startFlag) {
if (!isInFileset() || kc_type != KCKindPrimary) {
/*
* Verify that the start/stop function is executable.
*/
kern_result = mach_vm_region_recurse(kernel_map, &address, &size, &depth,
(vm_region_recurse_info_t)&info, &count);
if (kern_result != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s - bad %s pointer %p.",
getIdentifierCString(),
whichOp, (void *)ml_static_unslide(address));
result = kOSKextReturnBadData;
goto finish;
}
} else {
/*
* Since kexts loaded from the primary KC are held in memory
* allocated by efiboot, we cannot use mach_vm_region_recurse() to
* discover that memory's protection flags. Instead, we need to
* get that information from the kernel pmap itself. Above, we
* (potentially) saved the size of the segment in which the address
* in question was located. If we have a non-zero size, verify
* that all pages in the (address, address + kext_segsize) range
* are marked executable. If we somehow did not record the size
* (or the base) just verify the single page that includes the address.
*/
if (kext_segbase == 0 || kext_segsize == 0) {
kext_segbase = address & ~(uint64_t)PAGE_MASK;
kext_segsize = PAGE_SIZE;
}
}
#if VM_MAPPED_KEXTS
if (((!isInFileset() || kc_type != KCKindPrimary) && !(info.protection & VM_PROT_EXECUTE)) ||
((isInFileset() && kc_type == KCKindPrimary) &&
ml_static_verify_page_protections(kext_segbase, kext_segsize, VM_PROT_EXECUTE) != KERN_SUCCESS)) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s - memory region containing module %s function "
"is not executable.",
getIdentifierCString(), whichOp);
result = kOSKextReturnBadData;
goto finish;
}
#endif
/* Verify that the kext's segments are backed by physical memory.
*/
seg = firstsegfromheader((kernel_mach_header_t *)kmod_info->address);
while (seg) {
if (!verifySegmentMapping(seg)) {
result = kOSKextReturnBadData;
goto finish;
}
seg = nextsegfromheader((kernel_mach_header_t *) kmod_info->address, seg);
}
}
result = kOSReturnSuccess;
finish:
return result;
}
/*********************************************************************
*********************************************************************/
boolean_t
OSKext::verifySegmentMapping(kernel_segment_command_t *seg)
{
mach_vm_address_t address = 0;
if (seg->vmsize > UINT32_MAX) {
return false;
}
if (!segmentShouldBeWired(seg)) {
return true;
}
for (address = seg->vmaddr;
address < round_page(seg->vmaddr + seg->vmsize);
address += PAGE_SIZE) {
if (!pmap_find_phys(kernel_pmap, (vm_offset_t)address)) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s - page %p is not backed by physical memory.",
getIdentifierCString(),
(void *)address);
return false;
}
}
return true;
}
/*********************************************************************
*********************************************************************/
static void
OSKextLogKextInfo(OSKext *aKext, uint64_t address, uint64_t size, firehose_tracepoint_code_t code)
{
uint64_t stamp = 0;
firehose_tracepoint_id_u trace_id;
struct firehose_trace_uuid_info_s uuid_info_s;
firehose_trace_uuid_info_t uuid_info = &uuid_info_s;
size_t uuid_info_len = sizeof(struct firehose_trace_uuid_info_s);
OSSharedPtr<OSData> uuid_data;
stamp = firehose_tracepoint_time(firehose_activity_flags_default);
trace_id.ftid_value = FIREHOSE_TRACE_ID_MAKE(firehose_tracepoint_namespace_metadata, _firehose_tracepoint_type_metadata_kext, (firehose_tracepoint_flags_t)0, code);
uuid_data = aKext->copyTextUUID();
if (uuid_data) {
memcpy(uuid_info->ftui_uuid, uuid_data->getBytesNoCopy(), sizeof(uuid_info->ftui_uuid));
}
uuid_info->ftui_size = size;
if (aKext->isDriverKit()) {
uuid_info->ftui_address = address;
} else {
uuid_info->ftui_address = ml_static_unslide(address);
}
firehose_trace_metadata(firehose_stream_metadata, trace_id, stamp, uuid_info, uuid_info_len);
return;
}
void
OSKext::OSKextLogDriverKitInfoLoad(OSKext *kext)
{
OSKextLogKextInfo(kext, kext->getLoadTag(), 1, firehose_tracepoint_code_load);
}
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::start(bool startDependenciesFlag)
{
OSReturn result = kOSReturnError;
kern_return_t (* startfunc)(kmod_info_t *, void *);
unsigned int i, count;
void * kmodStartData = NULL;
if (isStarted() || isInterface() || isKernelComponent()) {
result = kOSReturnSuccess;
goto finish;
}
if (!isLoaded()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Attempt to start nonloaded kext %s.",
getIdentifierCString());
result = kOSKextReturnInvalidArgument;
goto finish;
}
if (!sLoadEnabled) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext loading is disabled (attempt to start kext %s).",
getIdentifierCString());
result = kOSKextReturnDisabled;
goto finish;
}
result = validateKextMapping(/* start? */ true);
if (result != kOSReturnSuccess) {
goto finish;
}
startfunc = kmod_info->start;
count = getNumDependencies();
for (i = 0; i < count; i++) {
OSKext * dependency = OSDynamicCast(OSKext, dependencies->getObject(i));
if (dependency == NULL) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s start - internal error, dependency disappeared.",
getIdentifierCString());
goto finish;
}
if (!dependency->isStarted()) {
if (startDependenciesFlag) {
OSReturn dependencyResult =
dependency->start(startDependenciesFlag);
if (dependencyResult != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s start - dependency %s failed to start (error 0x%x).",
getIdentifierCString(),
dependency->getIdentifierCString(),
dependencyResult);
goto finish;
}
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Not starting %s - dependency %s not started yet.",
getIdentifierCString(),
dependency->getIdentifierCString());
result = kOSKextReturnStartStopError; // xxx - make new return?
goto finish;
}
}
}
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogLoadFlag,
"Kext %s calling module start function.",
getIdentifierCString());
flags.starting = 1;
// Drop a log message so logd can grab the needed information to decode this kext
OSKextLogKextInfo(this, kmod_info->address, kmod_info->size, firehose_tracepoint_code_load);
result = OSRuntimeInitializeCPP(this);
if (result == KERN_SUCCESS) {
result = startfunc(kmod_info, kmodStartData);
}
flags.starting = 0;
/* On success overlap the setting of started/starting. On failure just
* clear starting.
*/
if (result == KERN_SUCCESS) {
flags.started = 1;
// xxx - log start error from kernel?
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"Kext %s is now started.",
getIdentifierCString());
} else {
invokeOrCancelRequestCallbacks(
/* result not actually used */ kOSKextReturnStartStopError,
/* invokeFlag */ false);
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogLoadFlag,
"Kext %s did not start (return code 0x%x).",
getIdentifierCString(), result);
}
finish:
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::canUnloadKextWithIdentifier(
OSString * kextIdentifier,
bool checkClassesFlag)
{
bool result = false;
OSKext * aKext = NULL; // do not release
IORecursiveLockLock(sKextLock);
aKext = OSDynamicCast(OSKext, sKextsByID->getObject(kextIdentifier));
if (!aKext) {
goto finish; // can't unload what's not loaded
}
if (aKext->isLoaded()) {
if (aKext->getRetainCount() > kOSKextMinLoadedRetainCount) {
goto finish;
}
if (checkClassesFlag && aKext->hasOSMetaClassInstances()) {
goto finish;
}
}
result = true;
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::stop(void)
{
OSReturn result = kOSReturnError;
kern_return_t (*stopfunc)(kmod_info_t *, void *);
if (!isStarted() || isInterface()) {
result = kOSReturnSuccess;
goto finish;
}
if (!isLoaded()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Attempt to stop nonloaded kext %s.",
getIdentifierCString());
result = kOSKextReturnInvalidArgument;
goto finish;
}
/* Refuse to stop if we have clients or instances. It is up to
* the caller to make sure those aren't true.
*/
if (getRetainCount() > kOSKextMinLoadedRetainCount) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s - C++ instances; can't stop.",
getIdentifierCString());
result = kOSKextReturnInUse;
goto finish;
}
if (getRetainCount() > kOSKextMinLoadedRetainCount) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s - has references (linkage or tracking object); "
"can't stop.",
getIdentifierCString());
result = kOSKextReturnInUse;
goto finish;
}
/* Note: If validateKextMapping fails on the stop & unload path,
* we are in serious trouble and a kernel panic is likely whether
* we stop & unload the kext or not.
*/
result = validateKextMapping(/* start? */ false);
if (result != kOSReturnSuccess) {
goto finish;
}
stopfunc = kmod_info->stop;
if (stopfunc) {
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogLoadFlag,
"Kext %s calling module stop function.",
getIdentifierCString());
flags.stopping = 1;
result = stopfunc(kmod_info, /* userData */ NULL);
if (result == KERN_SUCCESS) {
result = OSRuntimeFinalizeCPP(this);
}
flags.stopping = 0;
if (result == KERN_SUCCESS) {
flags.started = 0;
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogLoadFlag,
"Kext %s is now stopped and ready to unload.",
getIdentifierCString());
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s did not stop (return code 0x%x).",
getIdentifierCString(), result);
result = kOSKextReturnStartStopError;
}
}
finish:
// Drop a log message so logd can update this kext's metadata
OSKextLogKextInfo(this, kmod_info->address, kmod_info->size, firehose_tracepoint_code_unload);
return result;
}
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::unload(void)
{
OSReturn result = kOSReturnError;
unsigned int index;
uint32_t num_kmod_refs = 0;
OSKextAccount * freeAccount;
bool in_fileset = false;
if (!sUnloadEnabled) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext unloading is disabled (%s).",
this->getIdentifierCString());
result = kOSKextReturnDisabled;
goto finish;
}
// cache this result so we don't need to access the kmod_info after
// it's been potentially free'd
in_fileset = isInFileset();
/* Refuse to unload if we have clients or instances. It is up to
* the caller to make sure those aren't true.
*/
if (getRetainCount() > kOSKextMinLoadedRetainCount) {
// xxx - Don't log under errors? this is more of an info thing
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogKextBookkeepingFlag,
"Can't unload kext %s; outstanding references (linkage or tracking object).",
getIdentifierCString());
result = kOSKextReturnInUse;
goto finish;
}
if (isDriverKit()) {
index = sLoadedKexts->getNextIndexOfObject(this, 0);
if (index != (unsigned int)-1) {
sLoadedDriverKitKexts->removeObject(index);
OSKextLogKextInfo(this, loadTag, 1, firehose_tracepoint_code_unload);
loadTag = 0;
}
}
if (!isLoaded()) {
result = kOSReturnSuccess;
goto finish;
}
if (isKernelComponent()) {
result = kOSKextReturnInvalidArgument;
goto finish;
}
if (metaClasses && !OSMetaClass::removeClasses(metaClasses.get())) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag,
"Can't unload kext %s; classes have instances:",
getIdentifierCString());
reportOSMetaClassInstances(kOSKextLogErrorLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag);
result = kOSKextReturnInUse;
goto finish;
}
/* Note that the kext is unloading before running any code that
* might be in the kext (request callbacks, module stop function).
* We will deny certain requests made against a kext in the process
* of unloading.
*/
flags.unloading = 1;
/* Update the string describing the last kext to unload in case we panic.
*/
savePanicString(/* isLoading */ false);
if (isStarted()) {
result = stop();
if (result != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s can't unload - module stop returned 0x%x.",
getIdentifierCString(), (unsigned)result);
result = kOSKextReturnStartStopError;
goto finish;
}
}
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"Kext %s unloading.",
getIdentifierCString());
{
struct list_head *p;
struct list_head *prev;
struct list_head *next;
for (p = pendingPgoHead.next; p != &pendingPgoHead; p = next) {
OSKextGrabPgoStruct *s = container_of(p, OSKextGrabPgoStruct, list_head);
s->err = OSKextGrabPgoDataLocked(this, s->metadata, instance_uuid, s->pSize, s->pBuffer, s->bufferSize);
prev = p->prev;
next = p->next;
prev->next = next;
next->prev = prev;
p->prev = p;
p->next = p;
IORecursiveLockWakeup(sKextLock, s, false);
}
}
/* Even if we don't call the stop function, we want to be sure we
* have no OSMetaClass references before unloading the kext executable
* from memory. OSMetaClasses may have pointers into the kext executable
* and that would cause a panic on OSKext::free() when metaClasses is freed.
*/
if (metaClasses) {
metaClasses->flushCollection();
}
(void) OSRuntimeFinalizeCPP(this);
/* Remove the kext from the list of loaded kexts, patch the gap
* in the kmod_info_t linked list, and reset "kmod" to point to the
* last loaded kext that isn't the fake kernel kext (sKernelKext).
*/
index = sLoadedKexts->getNextIndexOfObject(this, 0);
if (index != (unsigned int)-1) {
sLoadedKexts->removeObject(index);
OSKext * nextKext = OSDynamicCast(OSKext,
sLoadedKexts->getObject(index));
if (nextKext) {
if (index > 0) {
OSKext * gapKext = OSDynamicCast(OSKext,
sLoadedKexts->getObject(index - 1));
nextKext->kmod_info->next = gapKext->kmod_info;
} else { /* index == 0 */
nextKext->kmod_info->next = NULL;
}
}
OSKext * lastKext = OSDynamicCast(OSKext, sLoadedKexts->getLastObject());
if (lastKext && !lastKext->isKernel()) {
kmod = lastKext->kmod_info;
} else {
kmod = NULL; // clear the global kmod variable
}
}
/* Clear out the kmod references that we're keeping for compatibility
* with current panic backtrace code & kgmacros.
* xxx - will want to update those bits sometime and remove this.
*/
num_kmod_refs = getNumDependencies();
if (num_kmod_refs && kmod_info && kmod_info->reference_list) {
for (uint32_t refIndex = 0; refIndex < num_kmod_refs; refIndex++) {
kmod_reference_t * ref = &(kmod_info->reference_list[refIndex]);
ref->info->reference_count--;
}
kfree(kmod_info->reference_list,
num_kmod_refs * sizeof(kmod_reference_t));
}
#if CONFIG_DTRACE
unregisterWithDTrace();
#endif /* CONFIG_DTRACE */
notifyKextUnloadObservers(this);
freeAccount = NULL;
IOSimpleLockLock(sKextAccountsLock);
account->kext = NULL;
if (account->site.tag) {
account->site.flags |= VM_TAG_UNLOAD;
} else {
freeAccount = account;
}
IOSimpleLockUnlock(sKextAccountsLock);
if (freeAccount) {
IODelete(freeAccount, OSKextAccount, 1);
}
/* Unwire and free the linked executable.
*/
if (linkedExecutable) {
#if KASAN
kasan_unload_kext((vm_offset_t)linkedExecutable->getBytesNoCopy(), linkedExecutable->getLength());
#endif
#if VM_MAPPED_KEXTS
if (!isInterface() && (!in_fileset || flags.resetSegmentsFromVnode)) {
kernel_segment_command_t *seg = NULL;
vm_map_t kext_map = kext_get_vm_map(kmod_info);
if (!kext_map) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to free kext %s; couldn't find the kext map.",
getIdentifierCString());
result = kOSKextReturnInternalError;
goto finish;
}
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"Kext %s unwiring and unmapping linked executable.",
getIdentifierCString());
seg = firstsegfromheader((kernel_mach_header_t *)kmod_info->address);
while (seg) {
if (segmentShouldBeWired(seg)) {
vm_map_offset_t start_wire = trunc_page(seg->vmaddr);
vm_map_offset_t end_wire = round_page(seg->vmaddr + seg->vmsize);
result = vm_map_unwire(kext_map, start_wire,
end_wire, FALSE);
if (result != KERN_SUCCESS) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to unwire kext %s.",
getIdentifierCString());
result = kOSKextReturnInternalError;
goto finish;
}
}
seg = nextsegfromheader((kernel_mach_header_t *) kmod_info->address, seg);
}
#if defined(__x86_64__) || defined(__i386__)
if (in_fileset && flags.resetSegmentsFromVnode) {
IORecursiveLockLock(sKextLock);
resetKCFileSetSegments();
IORecursiveLockUnlock(sKextLock);
}
#endif // (__x86_64__) || defined(__i386__)
}
#endif /* VM_MAPPED_KEXTS */
if (flags.resetSegmentsFromImmutableCopy) {
result = resetMutableSegments();
if (result != kOSReturnSuccess) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to reset kext %s.",
getIdentifierCString());
result = kOSKextReturnInternalError;
goto finish;
}
}
if (kc_type == KCKindUnknown) {
linkedExecutable.reset();
}
}
/* An interface kext has a fake kmod_info that was allocated,
* so we have to free it.
*/
if (isInterface()) {
kfree(kmod_info, sizeof(kmod_info_t));
kmod_info = NULL;
}
if (!in_fileset) {
kmod_info = NULL;
}
flags.loaded = false;
flushDependencies();
/* save a copy of the bundle ID for us to check when deciding to
* rebuild the kernel cache file. If a kext was already in the kernel
* cache and unloaded then later loaded we do not need to rebuild the
* kernel cache. 9055303
*/
if (isPrelinked()) {
if (!_OSKextInUnloadedPrelinkedKexts(bundleID.get())) {
IORecursiveLockLock(sKextLock);
if (sUnloadedPrelinkedKexts) {
sUnloadedPrelinkedKexts->setObject(bundleID.get());
}
IORecursiveLockUnlock(sKextLock);
}
}
OSKextLog(this,
kOSKextLogProgressLevel | kOSKextLogLoadFlag,
"Kext %s unloaded.", getIdentifierCString());
queueKextNotification(kKextRequestPredicateUnloadNotification,
OSDynamicCast(OSString, bundleID.get()));
finish:
OSKext::saveLoadedKextPanicList();
OSKext::updateLoadedKextSummaries();
flags.unloading = 0;
return result;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::queueKextNotification(
const char * notificationName,
OSString * kextIdentifier)
{
OSReturn result = kOSReturnError;
OSSharedPtr<OSDictionary> loadRequest;
if (!kextIdentifier) {
result = kOSKextReturnInvalidArgument;
goto finish;
}
/* Create a new request unless one is already sitting
* in sKernelRequests for this bundle identifier
*/
result = _OSKextCreateRequest(notificationName, loadRequest);
if (result != kOSReturnSuccess) {
goto finish;
}
if (!_OSKextSetRequestArgument(loadRequest.get(),
kKextRequestArgumentBundleIdentifierKey, kextIdentifier)) {
result = kOSKextReturnNoMemory;
goto finish;
}
if (!sKernelRequests->setObject(loadRequest.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
/* We might want to only queue the notification if the IOKit daemon is active,
* but that wouldn't work for embedded. Note that we don't care if
* the ping immediately succeeds here so don't do anything with the
* result of this call.
*/
OSKext::pingIOKitDaemon();
result = kOSReturnSuccess;
finish:
return result;
}
#if CONFIG_KXLD
/*********************************************************************
*********************************************************************/
static void
_OSKextConsiderDestroyingLinkContext(
__unused thread_call_param_t p0,
__unused thread_call_param_t p1)
{
/* Take multiple locks in the correct order.
*/
IORecursiveLockLock(sKextLock);
IORecursiveLockLock(sKextInnerLock);
/* The first time we destroy the kxldContext is in the first
* OSKext::considerUnloads() call, which sets sConsiderUnloadsCalled
* before calling this function. Thereafter any call to this function
* will actually destroy the context.
*/
if (sConsiderUnloadsCalled && sKxldContext) {
kxld_destroy_context(sKxldContext);
sKxldContext = NULL;
}
/* Free the thread_call that was allocated to execute this function.
*/
if (sDestroyLinkContextThread) {
if (!thread_call_free(sDestroyLinkContextThread)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"thread_call_free() failed for kext link context.");
}
sDestroyLinkContextThread = NULL;
}
IORecursiveLockUnlock(sKextInnerLock);
IORecursiveLockUnlock(sKextLock);
return;
}
/*********************************************************************
* Destroying the kxldContext requires checking variables under both
* sKextInnerLock and sKextLock, so we do it on a separate thread
* to avoid deadlocks with IOService, with which OSKext has a reciprocal
* call relationship.
*
* This function must be invoked with sKextInnerLock held.
* Do not call any function that takes sKextLock here!
*********************************************************************/
/* static */
void
OSKext::considerDestroyingLinkContext(void)
{
IORecursiveLockLock(sKextInnerLock);
/* If we have already queued a thread to destroy the link context,
* don't bother resetting; that thread will take care of it.
*/
if (sDestroyLinkContextThread) {
goto finish;
}
/* The function to be invoked in the thread will deallocate
* this thread_call, so don't share it around.
*/
sDestroyLinkContextThread = thread_call_allocate(
&_OSKextConsiderDestroyingLinkContext, NULL);
if (!sDestroyLinkContextThread) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogLinkFlag,
"Can't create thread to destroy kext link context.");
goto finish;
}
thread_call_enter(sDestroyLinkContextThread);
finish:
IORecursiveLockUnlock(sKextInnerLock);
return;
}
#else // !CONFIG_KXLD
/* static */
void
OSKext::considerDestroyingLinkContext(void)
{
return;
}
#endif // CONFIG_KXLD
#if PRAGMA_MARK
#pragma mark Autounload
#endif
/*********************************************************************
* This is a static method because the kext will be deallocated if it
* does unload!
*********************************************************************/
/* static */
OSReturn
OSKext::autounloadKext(OSKext * aKext)
{
OSReturn result = kOSKextReturnInUse;
#if NO_KEXTD
/*
* Do not unload prelinked kexts on platforms that do not have an
* IOKit daemon as there is no way to reload the kext or restart
* matching.
*/
if (aKext->isPrelinked()) {
goto finish;
}
#endif /* defined(__x86_64__) */
/* Check for external references to this kext (usu. dependents),
* instances of defined classes (or classes derived from them),
* outstanding requests.
*/
if ((aKext->getRetainCount() > kOSKextMinLoadedRetainCount) ||
!aKext->flags.autounloadEnabled ||
aKext->isKernelComponent()) {
goto finish;
}
/* Skip a delay-autounload kext, once.
*/
if (aKext->flags.delayAutounload) {
OSKextLog(aKext,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag | kOSKextLogKextBookkeepingFlag,
"Kext %s has delayed autounload set; skipping and clearing flag.",
aKext->getIdentifierCString());
aKext->flags.delayAutounload = 0;
goto finish;
}
if (aKext->hasOSMetaClassInstances() ||
aKext->countRequestCallbacks()) {
goto finish;
}
result = OSKext::removeKext(aKext);
finish:
return result;
}
/*********************************************************************
*********************************************************************/
void
_OSKextConsiderUnloads(
__unused thread_call_param_t p0,
__unused thread_call_param_t p1)
{
bool didUnload = false;
unsigned int count, i;
/* Take multiple locks in the correct order
* (note also sKextSummaries lock further down).
*/
IORecursiveLockLock(sKextLock);
IORecursiveLockLock(sKextInnerLock);
OSKext::flushNonloadedKexts(/* flushPrelinkedKexts */ true);
/* If the system is powering down, don't try to unload anything.
*/
if (sSystemSleep) {
goto finish;
}
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel | kOSKextLogLoadFlag,
"Checking for unused kexts to autounload.");
/*****
* Remove any request callbacks marked as stale,
* and mark as stale any currently in flight.
*/
count = sRequestCallbackRecords->getCount();
if (count) {
i = count - 1;
do {
OSDictionary * callbackRecord = OSDynamicCast(OSDictionary,
sRequestCallbackRecords->getObject(i));
OSBoolean * stale = OSDynamicCast(OSBoolean,
callbackRecord->getObject(kKextRequestStaleKey));
if (stale == kOSBooleanTrue) {
OSKext::invokeRequestCallback(callbackRecord,
kOSKextReturnTimeout);
} else {
callbackRecord->setObject(kKextRequestStaleKey,
kOSBooleanTrue);
}
} while (i--);
}
/*****
* Make multiple passes through the array of loaded kexts until
* we don't unload any. This handles unwinding of dependency
* chains. We have to go *backwards* through the array because
* kexts are removed from it when unloaded, and we cannot make
* a copy or we'll mess up the retain counts we rely on to
* check whether a kext will unload. If only we could have
* nonretaining collections like CF has....
*/
do {
didUnload = false;
count = sLoadedKexts->getCount();
if (count) {
i = count - 1;
do {
OSKext * thisKext = OSDynamicCast(OSKext,
sLoadedKexts->getObject(i));
didUnload |= (kOSReturnSuccess == OSKext::autounloadKext(thisKext));
} while (i--);
}
} while (didUnload);
finish:
sConsiderUnloadsPending = false;
sConsiderUnloadsExecuted = true;
(void) OSKext::considerRebuildOfPrelinkedKernel();
IORecursiveLockUnlock(sKextInnerLock);
IORecursiveLockUnlock(sKextLock);
return;
}
/*********************************************************************
* Do not call any function that takes sKextLock here!
*********************************************************************/
void
OSKext::considerUnloads(Boolean rescheduleOnlyFlag)
{
AbsoluteTime when;
IORecursiveLockLock(sKextInnerLock);
if (!sUnloadCallout) {
sUnloadCallout = thread_call_allocate(&_OSKextConsiderUnloads, NULL);
}
/* we only reset delay value for unloading if we already have something
* pending. rescheduleOnlyFlag should not start the count down.
*/
if (rescheduleOnlyFlag && !sConsiderUnloadsPending) {
goto finish;
}
thread_call_cancel(sUnloadCallout);
if (OSKext::getAutounloadEnabled() && !sSystemSleep
#if !NO_KEXTD
&& sIOKitDaemonActive
#endif
) {
clock_interval_to_deadline(sConsiderUnloadDelay,
1000 * 1000 * 1000, &when);
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"%scheduling %sscan for unused kexts in %lu seconds.",
sConsiderUnloadsPending ? "Res" : "S",
sConsiderUnloadsCalled ? "" : "initial ",
(unsigned long)sConsiderUnloadDelay);
sConsiderUnloadsPending = true;
thread_call_enter_delayed(sUnloadCallout, when);
}
finish:
/* The kxld context should be reused throughout boot. We mark the end of
* period as the first time considerUnloads() is called, and we destroy
* the first kxld context in that function. Afterwards, it will be
* destroyed in flushNonloadedKexts.
*/
if (!sConsiderUnloadsCalled) {
sConsiderUnloadsCalled = true;
OSKext::considerDestroyingLinkContext();
}
IORecursiveLockUnlock(sKextInnerLock);
return;
}
/*********************************************************************
* Do not call any function that takes sKextLock here!
*********************************************************************/
extern "C" {
IOReturn OSKextSystemSleepOrWake(UInt32 messageType);
IOReturn
OSKextSystemSleepOrWake(UInt32 messageType)
{
IORecursiveLockLock(sKextInnerLock);
/* If the system is going to sleep, cancel the reaper thread timer,
* and note that we're in a sleep state in case it just fired but hasn't
* taken the lock yet. If we are coming back from sleep, just
* clear the sleep flag; IOService's normal operation will cause
* unloads to be considered soon enough.
*/
if (messageType == kIOMessageSystemWillSleep) {
if (sUnloadCallout) {
thread_call_cancel(sUnloadCallout);
}
sSystemSleep = true;
AbsoluteTime_to_scalar(&sLastWakeTime) = 0;
} else if (messageType == kIOMessageSystemHasPoweredOn) {
sSystemSleep = false;
clock_get_uptime(&sLastWakeTime);
}
IORecursiveLockUnlock(sKextInnerLock);
return kIOReturnSuccess;
}
};
#if PRAGMA_MARK
#pragma mark Prelinked Kernel
#endif
#ifdef CONFIG_KXLD
/*********************************************************************
* Do not access sConsiderUnloads... variables other than
* sConsiderUnloadsExecuted in this function. They are guarded by a
* different lock.
*********************************************************************/
/* static */
void
OSKext::considerRebuildOfPrelinkedKernel(void)
{
static bool requestedPrelink = false;
OSReturn checkResult = kOSReturnError;
OSSharedPtr<OSDictionary> prelinkRequest;
OSSharedPtr<OSCollectionIterator> kextIterator;
const OSSymbol * thisID = NULL; // do not release
bool doRebuild = false;
AbsoluteTime my_abstime;
UInt64 my_ns;
SInt32 delta_secs;
/* Only one auto rebuild per boot and only on boot from prelinked kernel */
if (requestedPrelink || !sPrelinkBoot) {
return;
}
/* no direct return from this point */
IORecursiveLockLock(sKextLock);
/* We need to wait for the IOKit daemon to get up and running with unloads already done
* and any new startup kexts loaded.
*/
if (!sConsiderUnloadsExecuted ||
!sDeferredLoadSucceeded) {
goto finish;
}
/* we really only care about boot / system start up related kexts so bail
* if we're here after REBUILD_MAX_TIME.
*/
if (!_OSKextInPrelinkRebuildWindow()) {
OSKextLog(/* kext */ NULL,
kOSKextLogArchiveFlag,
"%s prebuild rebuild has expired",
__FUNCTION__);
requestedPrelink = true;
goto finish;
}
/* we do not want to trigger a rebuild if we get here too close to waking
* up. (see radar 10233768)
*/
IORecursiveLockLock(sKextInnerLock);
clock_get_uptime(&my_abstime);
delta_secs = MINIMUM_WAKEUP_SECONDS + 1;
if (AbsoluteTime_to_scalar(&sLastWakeTime) != 0) {
SUB_ABSOLUTETIME(&my_abstime, &sLastWakeTime);
absolutetime_to_nanoseconds(my_abstime, &my_ns);
delta_secs = (SInt32)(my_ns / NSEC_PER_SEC);
}
IORecursiveLockUnlock(sKextInnerLock);
if (delta_secs < MINIMUM_WAKEUP_SECONDS) {
/* too close to time of last wake from sleep */
goto finish;
}
requestedPrelink = true;
/* Now it's time to see if we have a reason to rebuild. We may have done
* some loads and unloads but the kernel cache didn't actually change.
* We will rebuild if any kext is not marked prelinked AND is not in our
* list of prelinked kexts that got unloaded. (see radar 9055303)
*/
kextIterator = OSCollectionIterator::withCollection(sKextsByID.get());
if (!kextIterator) {
goto finish;
}
while ((thisID = OSDynamicCast(OSSymbol, kextIterator->getNextObject()))) {
OSKext * thisKext; // do not release
thisKext = OSDynamicCast(OSKext, sKextsByID->getObject(thisID));
if (!thisKext || thisKext->isPrelinked() || thisKext->isKernel()) {
continue;
}
if (_OSKextInUnloadedPrelinkedKexts(thisKext->bundleID.get())) {
continue;
}
/* kext is loaded and was not in current kernel cache so let's rebuild
*/
doRebuild = true;
OSKextLog(/* kext */ NULL,
kOSKextLogArchiveFlag,
"considerRebuildOfPrelinkedKernel %s triggered rebuild",
thisKext->bundleID->getCStringNoCopy());
break;
}
sUnloadedPrelinkedKexts->flushCollection();
if (!doRebuild) {
goto finish;
}
checkResult = _OSKextCreateRequest(kKextRequestPredicateRequestPrelink,
prelinkRequest);
if (checkResult != kOSReturnSuccess) {
goto finish;
}
if (!sKernelRequests->setObject(prelinkRequest.get())) {
goto finish;
}
OSKext::pingIOKitDaemon();
finish:
IORecursiveLockUnlock(sKextLock);
return;
}
#else /* !CONFIG_KXLD */
void
OSKext::considerRebuildOfPrelinkedKernel(void)
{
/* in a non-dynamic kext loading world, there is never a reason to rebuild */
return;
}
#endif /* CONFIG_KXLD */
#if PRAGMA_MARK
#pragma mark Dependencies
#endif
/*********************************************************************
*********************************************************************/
bool
OSKext::resolveDependencies(
OSArray * loopStack)
{
bool result = false;
OSSharedPtr<OSArray> localLoopStack;
bool addedToLoopStack = false;
OSDictionary * libraries = NULL; // do not release
OSSharedPtr<OSCollectionIterator> libraryIterator;
OSString * libraryID = NULL; // do not release
OSKext * libraryKext = NULL; // do not release
bool hasRawKernelDependency = false;
bool hasKernelDependency = false;
bool hasKPIDependency = false;
bool hasPrivateKPIDependency = false;
unsigned int count;
#if CONFIG_KXLD
OSString * infoString = NULL; // do not release
OSString * readableString = NULL; // do not release
#endif // CONFIG_KXLD
/* A kernel component will automatically have this flag set,
* and a loaded kext should also have it set (as should all its
* loaded dependencies).
*/
if (flags.hasAllDependencies) {
result = true;
goto finish;
}
/* Check for loops in the dependency graph.
*/
if (loopStack) {
if (loopStack->getNextIndexOfObject(this, 0) != (unsigned int)-1) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s has a dependency loop; can't resolve dependencies.",
getIdentifierCString());
goto finish;
}
} else {
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogDependenciesFlag,
"Kext %s resolving dependencies.",
getIdentifierCString());
localLoopStack = OSArray::withCapacity(6); // any small capacity will do
if (!localLoopStack) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s can't create bookkeeping stack to resolve dependencies.",
getIdentifierCString());
goto finish;
}
loopStack = localLoopStack.get();
}
if (!loopStack->setObject(this)) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - internal error resolving dependencies.",
getIdentifierCString());
goto finish;
}
addedToLoopStack = true;
/* Purge any existing kexts in the dependency list and start over.
*/
flushDependencies();
if (dependencies) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - internal error resolving dependencies.",
getIdentifierCString());
}
libraries = OSDynamicCast(OSDictionary,
getPropertyForHostArch(kOSBundleLibrariesKey));
if (libraries == NULL || libraries->getCount() == 0) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag | kOSKextLogDependenciesFlag,
"Kext %s - can't resolve dependencies; %s missing/invalid type.",
getIdentifierCString(), kOSBundleLibrariesKey);
goto finish;
}
/* Make a new array to hold the dependencies (flush freed the old one).
*/
dependencies = OSArray::withCapacity(libraries->getCount());
if (!dependencies) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - can't allocate dependencies array.",
getIdentifierCString());
goto finish;
}
// xxx - compat: We used to add an implicit dependency on kernel 6.0
// xxx - compat: if none were declared.
libraryIterator = OSCollectionIterator::withCollection(libraries);
if (!libraryIterator) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - can't allocate dependencies iterator.",
getIdentifierCString());
goto finish;
}
while ((libraryID = OSDynamicCast(OSString,
libraryIterator->getNextObject()))) {
const char * library_id = libraryID->getCStringNoCopy();
OSString * libraryVersion = OSDynamicCast(OSString,
libraries->getObject(libraryID));
if (libraryVersion == NULL) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag | kOSKextLogDependenciesFlag,
"Kext %s - illegal type in OSBundleLibraries.",
getIdentifierCString());
goto finish;
}
OSKextVersion libraryVers =
OSKextParseVersionString(libraryVersion->getCStringNoCopy());
if (libraryVers == -1) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag | kOSKextLogDependenciesFlag,
"Kext %s - invalid library version %s.",
getIdentifierCString(),
libraryVersion->getCStringNoCopy());
goto finish;
}
libraryKext = OSDynamicCast(OSKext, sKextsByID->getObject(libraryID));
if (libraryKext == NULL) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - library kext %s not found.",
getIdentifierCString(), library_id);
goto finish;
}
if (!libraryKext->isCompatibleWithVersion(libraryVers)) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - library kext %s not compatible "
"with requested version %s.",
getIdentifierCString(), library_id,
libraryVersion->getCStringNoCopy());
goto finish;
}
/* If a nonprelinked library somehow got into the mix for a
* prelinked kext, at any point in the chain, we must fail
* because the prelinked relocs for the library will be all wrong.
*/
if (this->isPrelinked() &&
libraryKext->declaresExecutable() &&
!libraryKext->isPrelinked()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s (prelinked) - library kext %s (v%s) not prelinked.",
getIdentifierCString(), library_id,
libraryVersion->getCStringNoCopy());
goto finish;
}
if (!libraryKext->resolveDependencies(loopStack)) {
goto finish;
}
/* Add the library directly only if it has an executable to link.
* Otherwise it's just used to collect other dependencies, so put
* *its* dependencies on the list for this kext.
*/
// xxx - We are losing info here; would like to make fake entries or
// xxx - keep these in the dependency graph for loaded kexts.
// xxx - I really want to make kernel components not a special case!
if (libraryKext->declaresExecutable() ||
libraryKext->isInterface()) {
if (dependencies->getNextIndexOfObject(libraryKext, 0) == (unsigned)-1) {
dependencies->setObject(libraryKext);
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogDependenciesFlag,
"Kext %s added dependency %s.",
getIdentifierCString(),
libraryKext->getIdentifierCString());
}
} else {
int numLibDependencies = libraryKext->getNumDependencies();
OSArray * libraryDependencies = libraryKext->getDependencies();
int index;
if (numLibDependencies) {
// xxx - this msg level should be 1 lower than the per-kext one
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogDependenciesFlag,
"Kext %s pulling %d dependencies from codeless library %s.",
getIdentifierCString(),
numLibDependencies,
libraryKext->getIdentifierCString());
}
for (index = 0; index < numLibDependencies; index++) {
OSKext * thisLibDependency = OSDynamicCast(OSKext,
libraryDependencies->getObject(index));
if (dependencies->getNextIndexOfObject(thisLibDependency, 0) == (unsigned)-1) {
dependencies->setObject(thisLibDependency);
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogDependenciesFlag,
"Kext %s added dependency %s from codeless library %s.",
getIdentifierCString(),
thisLibDependency->getIdentifierCString(),
libraryKext->getIdentifierCString());
}
}
}
if ((strlen(library_id) == strlen(KERNEL_LIB)) &&
0 == strncmp(library_id, KERNEL_LIB, sizeof(KERNEL_LIB) - 1)) {
hasRawKernelDependency = true;
} else if (STRING_HAS_PREFIX(library_id, KERNEL_LIB_PREFIX)) {
hasKernelDependency = true;
} else if (STRING_HAS_PREFIX(library_id, KPI_LIB_PREFIX)) {
hasKPIDependency = true;
if (!strncmp(library_id, PRIVATE_KPI, sizeof(PRIVATE_KPI) - 1)) {
hasPrivateKPIDependency = true;
}
}
}
if (hasRawKernelDependency) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag | kOSKextLogDependenciesFlag,
"Error - kext %s declares a dependency on %s, which is not permitted.",
getIdentifierCString(), KERNEL_LIB);
goto finish;
}
#if __LP64__
if (hasKernelDependency) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogValidationFlag | kOSKextLogDependenciesFlag,
"Error - kext %s declares %s dependencies. "
"Only %s* dependencies are supported for 64-bit kexts.",
getIdentifierCString(), KERNEL_LIB, KPI_LIB_PREFIX);
goto finish;
}
if (!hasKPIDependency) {
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogDependenciesFlag,
"Warning - kext %s declares no %s* dependencies. "
"If it uses any KPIs, the link may fail with undefined symbols.",
getIdentifierCString(), KPI_LIB_PREFIX);
}
#else /* __LP64__ */
// xxx - will change to flatly disallow "kernel" dependencies at some point
// xxx - is it invalid to do both "com.apple.kernel" and any
// xxx - "com.apple.kernel.*"?
if (hasKernelDependency && hasKPIDependency) {
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogDependenciesFlag,
"Warning - kext %s has immediate dependencies on both "
"%s* and %s* components; use only one style.",
getIdentifierCString(), KERNEL_LIB, KPI_LIB_PREFIX);
}
if (!hasKernelDependency && !hasKPIDependency) {
// xxx - do we want to use validation flag for these too?
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogDependenciesFlag,
"Warning - %s declares no kernel dependencies; using %s.",
getIdentifierCString(), KERNEL6_LIB);
OSKext * kernelKext = OSDynamicCast(OSKext,
sKextsByID->getObject(KERNEL6_LIB));
if (kernelKext) {
dependencies->setObject(kernelKext);
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Error - Library %s not found for %s.",
KERNEL6_LIB, getIdentifierCString());
}
}
/* If the kext doesn't have a raw kernel or KPI dependency, then add all of
* its indirect dependencies to simulate old-style linking. XXX - Should
* check for duplicates.
*/
if (!hasKPIDependency) {
unsigned int i;
flags.hasBleedthrough = true;
count = getNumDependencies();
/* We add to the dependencies array in this loop, but do not iterate
* past its original count.
*/
for (i = 0; i < count; i++) {
OSKext * dependencyKext = OSDynamicCast(OSKext,
dependencies->getObject(i));
dependencyKext->addBleedthroughDependencies(dependencies.get());
}
}
#endif /* __LP64__ */
#if CONFIG_KXLD
/*
* If we're not dynamically linking kexts, then we don't need to check
* copyright strings. The linker in user space has already done this.
*/
if (hasPrivateKPIDependency) {
bool hasApplePrefix = false;
bool infoCopyrightIsValid = false;
bool readableCopyrightIsValid = false;
hasApplePrefix = STRING_HAS_PREFIX(getIdentifierCString(),
APPLE_KEXT_PREFIX);
infoString = OSDynamicCast(OSString,
getPropertyForHostArch("CFBundleGetInfoString"));
if (infoString) {
infoCopyrightIsValid =
kxld_validate_copyright_string(infoString->getCStringNoCopy());
}
readableString = OSDynamicCast(OSString,
getPropertyForHostArch("NSHumanReadableCopyright"));
if (readableString) {
readableCopyrightIsValid =
kxld_validate_copyright_string(readableString->getCStringNoCopy());
}
if (!hasApplePrefix || (!infoCopyrightIsValid && !readableCopyrightIsValid)) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Error - kext %s declares a dependency on %s. "
"Only Apple kexts may declare a dependency on %s.",
getIdentifierCString(), PRIVATE_KPI, PRIVATE_KPI);
goto finish;
}
}
#endif // CONFIG_KXLD
result = true;
flags.hasAllDependencies = 1;
finish:
if (addedToLoopStack) {
count = loopStack->getCount();
if (count > 0 && (this == loopStack->getObject(count - 1))) {
loopStack->removeObject(count - 1);
} else {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - internal error resolving dependencies.",
getIdentifierCString());
}
}
if (result && localLoopStack) {
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogDependenciesFlag,
"Kext %s successfully resolved dependencies.",
getIdentifierCString());
}
return result;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::addBleedthroughDependencies(OSArray * anArray)
{
bool result = false;
unsigned int dependencyIndex, dependencyCount;
dependencyCount = getNumDependencies();
for (dependencyIndex = 0;
dependencyIndex < dependencyCount;
dependencyIndex++) {
OSKext * dependency = OSDynamicCast(OSKext,
dependencies->getObject(dependencyIndex));
if (!dependency) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogDependenciesFlag,
"Kext %s - internal error propagating compatibility dependencies.",
getIdentifierCString());
goto finish;
}
if (anArray->getNextIndexOfObject(dependency, 0) == (unsigned int)-1) {
anArray->setObject(dependency);
}
dependency->addBleedthroughDependencies(anArray);
}
result = true;
finish:
return result;
}
/*********************************************************************
*********************************************************************/
bool
OSKext::flushDependencies(bool forceFlag)
{
bool result = false;
/* Only clear the dependencies if the kext isn't loaded;
* we need the info for loaded kexts to track references.
*/
if (!isLoaded() || forceFlag) {
if (dependencies) {
// xxx - check level
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogDependenciesFlag,
"Kext %s flushing dependencies.",
getIdentifierCString());
dependencies.reset();
}
if (!isKernelComponent()) {
flags.hasAllDependencies = 0;
}
result = true;
}
return result;
}
/*********************************************************************
*********************************************************************/
uint32_t
OSKext::getNumDependencies(void)
{
if (!dependencies) {
return 0;
}
return dependencies->getCount();
}
/*********************************************************************
*********************************************************************/
OSArray *
OSKext::getDependencies(void)
{
return dependencies.get();
}
bool
OSKext::hasDependency(const OSSymbol * depID)
{
bool result __block;
if (depID == getIdentifier()) {
return true;
}
if (!dependencies) {
return false;
}
result = false;
dependencies->iterateObjects(^bool (OSObject * obj) {
OSKext * kext;
kext = OSDynamicCast(OSKext, obj);
if (!kext) {
return false;
}
result = (depID == kext->getIdentifier());
return result;
});
return result;
}
#if PRAGMA_MARK
#pragma mark OSMetaClass Support
#endif
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::addClass(
OSMetaClass * aClass,
uint32_t numClasses)
{
OSReturn result = kOSMetaClassNoInsKModSet;
if (!metaClasses) {
metaClasses = OSSet::withCapacity(numClasses);
if (!metaClasses) {
goto finish;
}
}
if (metaClasses->containsObject(aClass)) {
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogLoadFlag,
"Notice - kext %s has already registered class %s.",
getIdentifierCString(),
aClass->getClassName());
result = kOSReturnSuccess;
goto finish;
}
if (!metaClasses->setObject(aClass)) {
goto finish;
} else {
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogLoadFlag,
"Kext %s registered class %s.",
getIdentifierCString(),
aClass->getClassName());
}
if (!flags.autounloadEnabled) {
const OSMetaClass * metaScan = NULL; // do not release
for (metaScan = aClass; metaScan; metaScan = metaScan->getSuperClass()) {
if (metaScan == OSTypeID(IOService)) {
OSKextLog(this,
kOSKextLogProgressLevel |
kOSKextLogLoadFlag,
"Kext %s has IOService subclass %s; enabling autounload.",
getIdentifierCString(),
aClass->getClassName());
flags.autounloadEnabled = (0 == flags.unloadUnsupported);
break;
}
}
}
notifyAddClassObservers(this, aClass, flags);
result = kOSReturnSuccess;
finish:
if (result != kOSReturnSuccess) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s failed to register class %s.",
getIdentifierCString(),
aClass->getClassName());
}
return result;
}
/*********************************************************************
*********************************************************************/
OSReturn
OSKext::removeClass(
OSMetaClass * aClass)
{
OSReturn result = kOSMetaClassNoKModSet;
if (!metaClasses) {
goto finish;
}
if (!metaClasses->containsObject(aClass)) {
OSKextLog(this,
kOSKextLogWarningLevel |
kOSKextLogLoadFlag,
"Notice - kext %s asked to unregister unknown class %s.",
getIdentifierCString(),
aClass->getClassName());
result = kOSReturnSuccess;
goto finish;
}
OSKextLog(this,
kOSKextLogDetailLevel |
kOSKextLogLoadFlag,
"Kext %s unregistering class %s.",
getIdentifierCString(),
aClass->getClassName());
metaClasses->removeObject(aClass);
notifyRemoveClassObservers(this, aClass, flags);
result = kOSReturnSuccess;
finish:
if (result != kOSReturnSuccess) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Failed to unregister kext %s class %s.",
getIdentifierCString(),
aClass->getClassName());
}
return result;
}
/*********************************************************************
*********************************************************************/
OSSet *
OSKext::getMetaClasses(void)
{
return metaClasses.get();
}
/*********************************************************************
*********************************************************************/
bool
OSKext::hasOSMetaClassInstances(void)
{
bool result = false;
OSSharedPtr<OSCollectionIterator> classIterator;
OSMetaClass * checkClass = NULL; // do not release
if (!metaClasses) {
goto finish;
}
classIterator = OSCollectionIterator::withCollection(metaClasses.get());
if (!classIterator) {
// xxx - log alloc failure?
goto finish;
}
while ((checkClass = (OSMetaClass *)classIterator->getNextObject())) {
if (checkClass->getInstanceCount()) {
result = true;
goto finish;
}
}
finish:
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::reportOSMetaClassInstances(
const char * kextIdentifier,
OSKextLogSpec msgLogSpec)
{
OSSharedPtr<OSKext> theKext;
theKext = OSKext::lookupKextWithIdentifier(kextIdentifier);
if (!theKext) {
goto finish;
}
theKext->reportOSMetaClassInstances(msgLogSpec);
finish:
return;
}
/*********************************************************************
*********************************************************************/
void
OSKext::reportOSMetaClassInstances(OSKextLogSpec msgLogSpec)
{
OSSharedPtr<OSCollectionIterator> classIterator;
OSMetaClass * checkClass = NULL; // do not release
if (!metaClasses) {
goto finish;
}
classIterator = OSCollectionIterator::withCollection(metaClasses.get());
if (!classIterator) {
goto finish;
}
while ((checkClass = (OSMetaClass *)classIterator->getNextObject())) {
if (checkClass->getInstanceCount()) {
OSKextLog(this,
msgLogSpec,
" Kext %s class %s has %d instance%s.",
getIdentifierCString(),
checkClass->getClassName(),
checkClass->getInstanceCount(),
checkClass->getInstanceCount() == 1 ? "" : "s");
}
}
finish:
return;
}
#if PRAGMA_MARK
#pragma mark User-Space Requests
#endif
static kern_return_t
patchDextLaunchRequests(task_t calling_task, OSArray *requests)
{
OSReturn result = kOSReturnSuccess;
for (uint32_t requestIndex = 0; requestIndex < requests->getCount(); requestIndex++) {
OSDictionary * request = NULL; //do not release
IOUserServerCheckInToken * token = NULL; //do not release
OSString * requestPredicate = NULL; //do not release
OSSharedPtr<OSNumber> portNameNumber;
mach_port_name_t portName = 0;
request = OSDynamicCast(OSDictionary, requests->getObject(requestIndex));
if (!request) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Elements of request should be of type OSDictionary");
result = kOSKextReturnInternalError;
goto finish;
}
requestPredicate = _OSKextGetRequestPredicate(request);
if (!requestPredicate) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Failed to get request predicate");
result = kOSKextReturnInternalError;
goto finish;
}
// is this a dext launch?
if (requestPredicate->isEqualTo(kKextRequestPredicateRequestDaemonLaunch)) {
token = OSDynamicCast(IOUserServerCheckInToken, _OSKextGetRequestArgument(request, kKextRequestArgumentCheckInToken));
if (!token) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Could not find a IOUserServerCheckInToken in daemon launch request.");
result = kOSKextReturnInternalError;
goto finish;
}
portName = iokit_make_send_right(calling_task, token, IKOT_IOKIT_IDENT);
if (portName == 0 || portName == MACH_PORT_DEAD) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Could not create send right for object.");
result = kOSKextReturnInternalError;
goto finish;
}
// Store the mach port name as a OSNumber
portNameNumber = OSNumber::withNumber(portName, CHAR_BIT * sizeof(portName));
if (!portNameNumber) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Could not create OSNumber object.");
result = kOSKextReturnNoMemory;
goto finish;
}
if (!_OSKextSetRequestArgument(request, kKextRequestArgumentCheckInToken, portNameNumber.get())) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Could not set OSNumber object as request " kKextRequestArgumentCheckInToken);
result = kOSKextReturnNoMemory;
goto finish;
}
}
finish:
if (result != kOSReturnSuccess) {
break;
}
}
return result;
}
/*********************************************************************
* XXX - this function is a big ugly mess
*********************************************************************/
/* static */
OSReturn
OSKext::handleRequest(
host_priv_t hostPriv,
OSKextLogSpec clientLogFilter,
char * requestBuffer,
uint32_t requestLength,
char ** responseOut,
uint32_t * responseLengthOut,
char ** logInfoOut,
uint32_t * logInfoLengthOut)
{
OSReturn result = kOSReturnError;
kern_return_t kmem_result = KERN_FAILURE;
char * response = NULL; // returned by reference
uint32_t responseLength = 0;
bool taskCanManageAllKCs = false;
bool taskOnlyManagesBootKC = false;
OSSharedPtr<OSObject> parsedXML;
OSDictionary * requestDict = NULL; // do not release
OSSharedPtr<OSString> errorString;
OSSharedPtr<OSObject> responseObject;
OSSharedPtr<OSSerialize> serializer;
OSSharedPtr<OSArray> logInfoArray;
OSString * predicate = NULL; // do not release
OSString * kextIdentifier = NULL; // do not release
OSArray * kextIdentifiers = NULL; // do not release
OSKext * theKext = NULL; // do not release
OSBoolean * boolArg = NULL; // do not release
IORecursiveLockLock(sKextLock);
if (responseOut) {
*responseOut = NULL;
*responseLengthOut = 0;
}
if (logInfoOut) {
*logInfoOut = NULL;
*logInfoLengthOut = 0;
}
OSKext::setUserSpaceLogFilter(clientLogFilter, logInfoOut ? true : false);
/* XML must be nul-terminated.
*/
if (requestBuffer[requestLength - 1] != '\0') {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid request from user space (not nul-terminated).");
result = kOSKextReturnBadData;
goto finish;
}
parsedXML = OSUnserializeXML((const char *)requestBuffer, errorString);
if (parsedXML) {
requestDict = OSDynamicCast(OSDictionary, parsedXML.get());
}
if (!requestDict) {
const char * errorCString = "(unknown error)";
if (errorString && errorString->getCStringNoCopy()) {
errorCString = errorString->getCStringNoCopy();
} else if (parsedXML) {
errorCString = "not a dictionary";
}
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Error unserializing request from user space: %s.",
errorCString);
result = kOSKextReturnSerialization;
goto finish;
}
predicate = _OSKextGetRequestPredicate(requestDict);
if (!predicate) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Recieved kext request from user space with no predicate.");
result = kOSKextReturnInvalidArgument;
goto finish;
}
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogIPCFlag,
"Received '%s' request from user space.",
predicate->getCStringNoCopy());
/*
* All management of file sets requires an entitlement
*/
result = kOSKextReturnNotPrivileged;
if (predicate->isEqualTo(kKextRequestPredicateUnload) ||
predicate->isEqualTo(kKextRequestPredicateStart) ||
predicate->isEqualTo(kKextRequestPredicateStop) ||
predicate->isEqualTo(kKextRequestPredicateGetKernelRequests) ||
predicate->isEqualTo(kKextRequestPredicateSendResource) ||
predicate->isEqualTo(kKextRequestPredicateLoadFileSetKC) ||
predicate->isEqualTo(kKextRequestPredicateLoadCodeless) ||
predicate->isEqualTo(kKextRequestPredicateLoadFromKC) ||
predicate->isEqualTo(kKextRequestPredicateMissingAuxKCBundles) ||
predicate->isEqualTo(kKextRequestPredicateAuxKCBundleAvailable) ||
predicate->isEqualTo(kKextRequestPredicateDaemonReady)) {
if (hostPriv == HOST_PRIV_NULL) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Access Failure - must be root user.");
goto finish;
}
taskCanManageAllKCs = IOTaskHasEntitlement(current_task(), kOSKextCollectionManagementEntitlement) == TRUE;
taskOnlyManagesBootKC = IOTaskHasEntitlement(current_task(), kOSKextOnlyBootKCManagementEntitlement) == TRUE;
if (!taskCanManageAllKCs && !taskOnlyManagesBootKC) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Access Failure - client not entitled to manage file sets.");
goto finish;
}
/*
* The OnlyBootKC entitlement restricts the
* collection-management entitlement to only managing kexts in
* the BootKC. All other predicates that alter global state or
* add new KCs are disallowed.
*/
if (taskOnlyManagesBootKC &&
(predicate->isEqualTo(kKextRequestPredicateGetKernelRequests) ||
predicate->isEqualTo(kKextRequestPredicateSendResource) ||
predicate->isEqualTo(kKextRequestPredicateLoadFileSetKC) ||
predicate->isEqualTo(kKextRequestPredicateLoadCodeless) ||
predicate->isEqualTo(kKextRequestPredicateMissingAuxKCBundles) ||
predicate->isEqualTo(kKextRequestPredicateAuxKCBundleAvailable) ||
predicate->isEqualTo(kKextRequestPredicateDaemonReady))) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Access Failure - client not entitled to manage non-primary KCs");
goto finish;
}
/*
* If we get here, then the process either has the full KC
* management entitlement, or it has the BootKC-only
* entitlement and the request is about the BootKC.
*/
}
/* Get common args in anticipation of use.
*/
kextIdentifier = OSDynamicCast(OSString, _OSKextGetRequestArgument(
requestDict, kKextRequestArgumentBundleIdentifierKey));
kextIdentifiers = OSDynamicCast(OSArray, _OSKextGetRequestArgument(
requestDict, kKextRequestArgumentBundleIdentifierKey));
if (kextIdentifier) {
theKext = OSDynamicCast(OSKext, sKextsByID->getObject(kextIdentifier));
}
boolArg = OSDynamicCast(OSBoolean, _OSKextGetRequestArgument(
requestDict, kKextRequestArgumentValueKey));
if (taskOnlyManagesBootKC &&
theKext &&
theKext->isInFileset() &&
theKext->kc_type != KCKindPrimary) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Access Failure - client not entitled to manage kext in non-primary KC");
result = kOSKextReturnNotPrivileged;
goto finish;
}
result = kOSKextReturnInvalidArgument;
if (predicate->isEqualTo(kKextRequestPredicateStart)) {
if (!kextIdentifier) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to kext start request.");
} else if (!theKext) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Kext %s not found for start request.",
kextIdentifier->getCStringNoCopy());
result = kOSKextReturnNotFound;
} else {
result = theKext->start();
}
} else if (predicate->isEqualTo(kKextRequestPredicateStop)) {
if (!kextIdentifier) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to kext stop request.");
} else if (!theKext) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Kext %s not found for stop request.",
kextIdentifier->getCStringNoCopy());
result = kOSKextReturnNotFound;
} else {
result = theKext->stop();
}
} else if (predicate->isEqualTo(kKextRequestPredicateMissingAuxKCBundles)) {
result = OSKext::setMissingAuxKCBundles(requestDict);
} else if (predicate->isEqualTo(kKextRequestPredicateAuxKCBundleAvailable)) {
if (!kextIdentifier) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to AuxKC Bundle Available request.");
} else {
result = OSKext::setAuxKCBundleAvailable(kextIdentifier, requestDict);
}
} else if (predicate->isEqualTo(kKextRequestPredicateLoadFromKC)) {
if (!kextIdentifier) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to kext load from KC request.");
} else if (!theKext) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Kext %s not found for load from KC request.",
kextIdentifier->getCStringNoCopy());
result = kOSKextReturnNotFound;
} else if (!theKext->isInFileset()) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Kext %s does not exist in a KC: refusing to load.",
kextIdentifier->getCStringNoCopy());
result = kOSKextReturnNotLoadable;
} else {
result = OSKext::loadKextFromKC(theKext, requestDict);
}
} else if (predicate->isEqualTo(kKextRequestPredicateLoadCodeless)) {
if (!kextIdentifier) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to codeless kext load interface (missing identifier).");
} else {
result = OSKext::loadCodelessKext(kextIdentifier, requestDict);
}
} else if (predicate->isEqualTo(kKextRequestPredicateUnload)) {
if (!kextIdentifier) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to kext unload request.");
} else if (!theKext) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Kext %s not found for unload request.",
kextIdentifier->getCStringNoCopy());
result = kOSKextReturnNotFound;
} else {
OSBoolean * terminateFlag = OSDynamicCast(OSBoolean,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentTerminateIOServicesKey));
result = OSKext::removeKext(theKext, terminateFlag == kOSBooleanTrue);
}
} else if (predicate->isEqualTo(kKextRequestPredicateSendResource)) {
result = OSKext::dispatchResource(requestDict);
} else if (predicate->isEqualTo(kKextRequestPredicateGetUUIDByAddress)) {
OSNumber *lookupNum = NULL;
lookupNum = OSDynamicCast(OSNumber,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentLookupAddressKey));
responseObject = OSKext::copyKextUUIDForAddress(lookupNum);
if (responseObject) {
result = kOSReturnSuccess;
} else {
goto finish;
}
} else if (predicate->isEqualTo(kKextRequestPredicateGetLoaded) ||
predicate->isEqualTo(kKextRequestPredicateGetLoadedByUUID) ||
predicate->isEqualTo(kKextRequestPredicateGetKextsInCollection)) {
OSBoolean * delayAutounloadBool = NULL;
OSObject * infoKeysRaw = NULL;
OSArray * infoKeys = NULL;
uint32_t infoKeysCount = 0;
delayAutounloadBool = OSDynamicCast(OSBoolean,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentDelayAutounloadKey));
/* If asked to delay autounload, reset the timer if it's currently set.
* (That is, don't schedule an unload if one isn't already pending.
*/
if (delayAutounloadBool == kOSBooleanTrue) {
OSKext::considerUnloads(/* rescheduleOnly? */ true);
}
infoKeysRaw = _OSKextGetRequestArgument(requestDict,
kKextRequestArgumentInfoKeysKey);
infoKeys = OSDynamicCast(OSArray, infoKeysRaw);
if (infoKeysRaw && !infoKeys) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to kext info request.");
goto finish;
}
if (infoKeys) {
infoKeysCount = infoKeys->getCount();
for (uint32_t i = 0; i < infoKeysCount; i++) {
if (!OSDynamicCast(OSString, infoKeys->getObject(i))) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid arguments to kext info request.");
goto finish;
}
}
}
if (predicate->isEqualTo(kKextRequestPredicateGetLoaded)) {
responseObject = OSKext::copyLoadedKextInfo(kextIdentifiers, infoKeys);
} else if (predicate->isEqualTo(kKextRequestPredicateGetLoadedByUUID)) {
responseObject = OSKext::copyLoadedKextInfoByUUID(kextIdentifiers, infoKeys);
} else if (predicate->isEqualTo(kKextRequestPredicateGetKextsInCollection)) {
responseObject = OSKext::copyKextCollectionInfo(requestDict, infoKeys);
}
if (!responseObject) {
result = kOSKextReturnInternalError;
} else {
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogIPCFlag,
"Returning loaded kext info.");
result = kOSReturnSuccess;
}
} else if (predicate->isEqualTo(kKextRequestPredicateGetKernelRequests)) {
/* Hand the current sKernelRequests array to the caller
* (who must release it), and make a new one.
*/
responseObject = os::move(sKernelRequests);
sKernelRequests = OSArray::withCapacity(0);
sPostedKextLoadIdentifiers->flushCollection();
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogIPCFlag,
"Returning kernel requests.");
result = kOSReturnSuccess;
} else if (predicate->isEqualTo(kKextRequestPredicateGetAllLoadRequests)) {
/* Return the set of all requested bundle identifiers */
responseObject = sAllKextLoadIdentifiers;
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogIPCFlag,
"Returning load requests.");
result = kOSReturnSuccess;
} else if (predicate->isEqualTo(kKextRequestPredicateLoadFileSetKC)) {
printf("KextLog: Loading FileSet KC(s)\n");
result = OSKext::loadFileSetKexts(requestDict);
} else if (predicate->isEqualTo(kKextRequestPredicateDaemonReady)) {
printf("KextLog: " kIOKitDaemonName " is %s\n", sIOKitDaemonActive ? "active" : "not active");
result = (sIOKitDaemonActive && !sOSKextWasResetAfterUserspaceReboot) ? kOSReturnSuccess : kIOReturnNotReady;
} else {
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogIPCFlag,
"Received '%s' invalid request from user space.",
predicate->getCStringNoCopy());
goto finish;
}
/**********
* Now we have handle the request, or not. Gather up the response & logging
* info to ship to user space.
*********/
/* Note: Nothing in OSKext is supposed to retain requestDict,
* but you never know....
*/
if (requestDict->getRetainCount() > 1) {
OSKextLog(/* kext */ NULL,
kOSKextLogWarningLevel |
kOSKextLogIPCFlag,
"Request from user space still retained by a kext; "
"probable memory leak.");
}
if (responseOut && responseObject) {
serializer = OSSerialize::withCapacity(0);
if (!serializer) {
result = kOSKextReturnNoMemory;
goto finish;
}
/*
* Before serializing the kernel requests, patch the dext launch requests so
* that the value for kKextRequestArgumentCheckInToken is a mach port name for the
* IOUserServerCheckInToken kernel object.
*/
if (predicate->isEqualTo(kKextRequestPredicateGetKernelRequests)) {
OSArray * requests = OSDynamicCast(OSArray, responseObject.get());
task_t calling_task = current_task();
if (!requests) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"responseObject should be an OSArray if predicate is " kKextRequestPredicateGetKernelRequests);
result = kOSKextReturnInternalError;
goto finish;
}
result = patchDextLaunchRequests(calling_task, requests);
if (result != kOSReturnSuccess) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Failed to patch dext launch requests.");
goto finish;
}
}
if (!responseObject->serialize(serializer.get())) {
OSKextLog(/* kext */ NULL,
kOSKextLogGeneralFlag | kOSKextLogErrorLevel,
"Failed to serialize response to request from user space.");
result = kOSKextReturnSerialization;
goto finish;
}
response = (char *)serializer->text();
responseLength = serializer->getLength();
}
if (responseOut && response) {
char * buffer;
/* This kmem_alloc sets the return value of the function.
*/
kmem_result = kmem_alloc(kernel_map, (vm_offset_t *)&buffer,
round_page(responseLength), VM_KERN_MEMORY_OSKEXT);
if (kmem_result != KERN_SUCCESS) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Failed to copy response to request from user space.");
result = kmem_result;
goto finish;
} else {
/* 11981737 - clear uninitialized data in last page */
bzero((void *)(buffer + responseLength),
(round_page(responseLength) - responseLength));
memcpy(buffer, response, responseLength);
*responseOut = buffer;
*responseLengthOut = responseLength;
}
}
finish:
/* Gather up the collected log messages for user space. Any messages
* messages past this call will not make it up as log messages but
* will be in the system log. Note that we ignore the return of the
* serialize; it has no bearing on the operation at hand even if we
* fail to get the log messages.
*/
logInfoArray = OSKext::clearUserSpaceLogFilter();
if (logInfoArray && logInfoOut && logInfoLengthOut) {
(void)OSKext::serializeLogInfo(logInfoArray.get(),
logInfoOut, logInfoLengthOut);
}
IORecursiveLockUnlock(sKextLock);
return result;
}
#if PRAGMA_MARK
#pragma mark Linked Kext Collection Support
#endif
static int
__whereIsAddr(vm_offset_t theAddr, unsigned long *segSizes, vm_offset_t *segAddrs, int segCount)
{
for (int i = 0; i < segCount; i++) {
vm_offset_t segStart = segAddrs[i];
vm_offset_t segEnd = segStart + (vm_offset_t)segSizes[i];
if (theAddr >= segStart && theAddr < segEnd) {
return i;
}
}
return -1;
}
static void
__slideOldKaslrOffsets(kernel_mach_header_t *mh,
kernel_segment_command_t *kextTextSeg,
OSData *kaslrOffsets)
{
static const char *plk_segNames[] = {
"__TEXT",
"__TEXT_EXEC",
"__DATA",
"__DATA_CONST",
"__LINKEDIT",
"__PRELINK_TEXT",
"__PLK_TEXT_EXEC",
"__PRELINK_DATA",
"__PLK_DATA_CONST",
"__PLK_LLVM_COV",
"__PLK_LINKEDIT",
"__PRELINK_INFO"
};
static const size_t num_plk_seg = (size_t)(sizeof(plk_segNames) / sizeof(plk_segNames[0]));
unsigned long plk_segSizes[num_plk_seg];
vm_offset_t plk_segAddrs[num_plk_seg];
for (size_t i = 0; i < num_plk_seg; i++) {
plk_segSizes[i] = 0;
plk_segAddrs[i] = (vm_offset_t)getsegdatafromheader(mh, plk_segNames[i], &plk_segSizes[i]);
}
uint64_t kextTextStart = (uint64_t)kextTextSeg->vmaddr;
int slidKextAddrCount = 0;
int badSlideAddr = 0;
int badSlideTarget = 0;
struct kaslrPackedOffsets {
uint32_t count; /* number of offsets */
uint32_t offsetsArray[]; /* offsets to slide */
};
const struct kaslrPackedOffsets *myOffsets = NULL;
myOffsets = (const struct kaslrPackedOffsets *)kaslrOffsets->getBytesNoCopy();
for (uint32_t j = 0; j < myOffsets->count; j++) {
uint64_t slideOffset = (uint64_t)myOffsets->offsetsArray[j];
vm_offset_t *slideAddr = (vm_offset_t *)((uint64_t)kextTextStart + slideOffset);
int slideAddrSegIndex = -1;
int addrToSlideSegIndex = -1;
slideAddrSegIndex = __whereIsAddr((vm_offset_t)slideAddr, &plk_segSizes[0], &plk_segAddrs[0], num_plk_seg);
if (slideAddrSegIndex >= 0) {
addrToSlideSegIndex = __whereIsAddr(ml_static_slide(*slideAddr), &plk_segSizes[0], &plk_segAddrs[0], num_plk_seg);
if (addrToSlideSegIndex < 0) {
badSlideTarget++;
continue;
}
} else {
badSlideAddr++;
continue;
}
slidKextAddrCount++;
*slideAddr = ml_static_slide(*slideAddr);
} // for ...
}
/********************************************************************
* addKextsFromKextCollection
*
* Input: MachO header of kext collection. The MachO is assumed to
* have a section named 'info_seg_name,info_sect_name' that
* contains a serialized XML info dictionary. This dictionary
* contains a UUID, possibly a set of relocations (for older
* kxld-built binaries), and an array of kext personalities.
*
********************************************************************/
bool
OSKext::addKextsFromKextCollection(kernel_mach_header_t *mh,
OSDictionary *infoDict, const char *text_seg_name,
OSData **kcUUID, kc_kind_t type)
{
bool result = false;
OSArray *kextArray = NULL; // do not release
OSData *infoDictKCUUID = NULL; // do not release
OSData *kaslrOffsets = NULL; // do not release
IORegistryEntry *registryRoot = NULL; // do not release
OSSharedPtr<OSNumber> kcKextCount;
/* extract the KC UUID from the dictionary */
infoDictKCUUID = OSDynamicCast(OSData, infoDict->getObject(kPrelinkInfoKCIDKey));
if (infoDictKCUUID) {
if (infoDictKCUUID->getLength() != sizeof(uuid_t)) {
panic("kcUUID length is %d, expected %lu",
infoDictKCUUID->getLength(), sizeof(uuid_t));
}
}
/* locate the array of kext dictionaries */
kextArray = OSDynamicCast(OSArray, infoDict->getObject(kPrelinkInfoDictionaryKey));
if (!kextArray) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"The given KC has no kext info dictionaries");
goto finish;
}
/*
* old-style KASLR offsets may be present in the info dictionary. If
* we find them, use them and eventually slide them.
*/
kaslrOffsets = OSDynamicCast(OSData, infoDict->getObject(kPrelinkLinkKASLROffsetsKey));
/*
* Before processing any kexts, locate the special kext bundle which
* contains a list of kexts that we are to prevent from loading.
*/
createExcludeListFromPrelinkInfo(kextArray);
/*
* Create OSKext objects for each kext we find in the array of kext
* info plist dictionaries.
*/
for (int i = 0; i < (int)kextArray->getCount(); ++i) {
OSDictionary *kextDict = NULL;
kextDict = OSDynamicCast(OSDictionary, kextArray->getObject(i));
if (!kextDict) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogDirectoryScanFlag | kOSKextLogArchiveFlag,
"Kext info dictionary for kext #%d isn't a dictionary?", i);
continue;
}
/*
* Create the kext for the entry, then release it, because the
* kext system keeps a reference around until the kext is
* explicitly removed. Any creation/registration failures are
* already logged for us.
*/
withPrelinkedInfoDict(kextDict, (kaslrOffsets ? TRUE : FALSE), type);
}
/*
* slide old-style kxld relocations
* NOTE: this is still used on embedded KCs built with kcgen
* TODO: Remove this once we use the new kext linker everywhere!
*/
if (kaslrOffsets && vm_kernel_slide > 0) {
kernel_segment_command_t *text_segment = NULL;
text_segment = getsegbynamefromheader(mh, text_seg_name);
if (!text_segment) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Can't find a TEXT segment named '%s' in macho header", text_seg_name);
goto finish;
}
__slideOldKaslrOffsets(mh, text_segment, kaslrOffsets);
/* All kexts covered by the old-style kaslr relocation list are now slid, set VM protections for them */
setAllVMAttributes();
}
/* Store the number of prelinked kexts in the registry so we can tell
* when the system has been started from a prelinked kernel.
*/
registryRoot = IORegistryEntry::getRegistryRoot();
assert(registryRoot);
kcKextCount = OSNumber::withNumber((unsigned long long)infoDict->getCount(), 8 * sizeof(uint32_t));
assert(kcKextCount);
if (kcKextCount) {
OSSharedPtr<OSObject> prop = registryRoot->copyProperty(kOSPrelinkKextCountKey);
OSNumber *num;
num = OSDynamicCast(OSNumber, prop.get());
if (num) {
kcKextCount->addValue(num->unsigned64BitValue());
}
registryRoot->setProperty(kOSPrelinkKextCountKey, kcKextCount.get());
}
OSKextLog(/* kext */ NULL,
kOSKextLogProgressLevel |
kOSKextLogGeneralFlag | kOSKextLogKextBookkeepingFlag |
kOSKextLogDirectoryScanFlag | kOSKextLogArchiveFlag,
"%u prelinked kexts", infoDict->getCount());
if (kcUUID && infoDictKCUUID) {
*kcUUID = OSData::withData(infoDictKCUUID).detach();
}
result = true;
finish:
return result;
}
bool
OSKext::addKextsFromKextCollection(kernel_mach_header_t *mh,
OSDictionary *infoDict, const char *text_seg_name,
OSSharedPtr<OSData> &kcUUID, kc_kind_t type)
{
OSData *result = NULL;
bool success = addKextsFromKextCollection(mh,
infoDict,
text_seg_name,
&result,
type);
if (success) {
kcUUID.reset(result, OSNoRetain);
}
return success;
}
static OSSharedPtr<OSObject> deferredAuxKCXML;
bool
OSKext::registerDeferredKextCollection(kernel_mach_header_t *mh,
OSSharedPtr<OSObject> &parsedXML, kc_kind_t type)
{
if (type != KCKindAuxiliary) {
return false;
}
kernel_mach_header_t *_mh;
_mh = (kernel_mach_header_t*)PE_get_kc_header(type);
if (!_mh || _mh != mh) {
return false;
}
if (deferredAuxKCXML) {
/* only allow this to be called once */
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"An Aux KC has already been registered for deferred processing.");
return false;
}
OSDictionary *infoDict = OSDynamicCast(OSDictionary, parsedXML.get());
if (!infoDict) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"The Aux KC has info dictionary");
return false;
}
OSData *kcUUID = OSDynamicCast(OSData, infoDict->getObject(kPrelinkInfoKCIDKey));
if (!kcUUID || kcUUID->getLength() != sizeof(uuid_t)) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"The Aux KC has no UUID in %s", kPrelinkInfoKCIDKey);
return false;
}
/*
* Copy the AuxKC UUID to make sure that the kern.auxiliaryfilesetuuid
* sysctl can return the UUID to user space which will check this
* value for errors.
*/
memcpy((void *)&auxkc_uuid, (const void *)kcUUID->getBytesNoCopy(),
kcUUID->getLength());
uuid_unparse_upper(auxkc_uuid, auxkc_uuid_string);
auxkc_uuid_valid = TRUE;
deferredAuxKCXML = parsedXML;
return true;
}
OSSharedPtr<OSObject>
OSKext::consumeDeferredKextCollection(kc_kind_t type)
{
if (type != KCKindAuxiliary || !deferredAuxKCXML) {
return NULL;
}
return os::move(deferredAuxKCXML);
}
#if PRAGMA_MARK
#pragma mark Profile-Guided-Optimization Support
#endif
// #include <InstrProfiling.h>
extern "C" {
uint64_t __llvm_profile_get_size_for_buffer_internal(const char *DataBegin,
const char *DataEnd,
const char *CountersBegin,
const char *CountersEnd,
const char *NamesBegin,
const char *NamesEnd);
int __llvm_profile_write_buffer_internal(char *Buffer,
const char *DataBegin,
const char *DataEnd,
const char *CountersBegin,
const char *CountersEnd,
const char *NamesBegin,
const char *NamesEnd);
}
static
void
OSKextPgoMetadataPut(char *pBuffer,
size_t *position,
size_t bufferSize,
uint32_t *num_pairs,
const char *key,
const char *value)
{
size_t strlen_key = strlen(key);
size_t strlen_value = strlen(value);
size_t len = strlen(key) + 1 + strlen(value) + 1;
char *pos = pBuffer + *position;
*position += len;
if (pBuffer && bufferSize && *position <= bufferSize) {
memcpy(pos, key, strlen_key); pos += strlen_key;
*(pos++) = '=';
memcpy(pos, value, strlen_value); pos += strlen_value;
*(pos++) = 0;
if (num_pairs) {
(*num_pairs)++;
}
}
}
static
void
OSKextPgoMetadataPutMax(size_t *position, const char *key, size_t value_max)
{
*position += strlen(key) + 1 + value_max + 1;
}
static
void
OSKextPgoMetadataPutAll(OSKext *kext,
uuid_t instance_uuid,
char *pBuffer,
size_t *position,
size_t bufferSize,
uint32_t *num_pairs)
{
_static_assert_1_arg(sizeof(clock_sec_t) % 2 == 0);
//log_10 2^16 ≈ 4.82
const size_t max_secs_string_size = 5 * sizeof(clock_sec_t) / 2;
const size_t max_timestamp_string_size = max_secs_string_size + 1 + 6;
if (!pBuffer) {
OSKextPgoMetadataPutMax(position, "INSTANCE", 36);
OSKextPgoMetadataPutMax(position, "UUID", 36);
OSKextPgoMetadataPutMax(position, "TIMESTAMP", max_timestamp_string_size);
} else {
uuid_string_t instance_uuid_string;
uuid_unparse(instance_uuid, instance_uuid_string);
OSKextPgoMetadataPut(pBuffer, position, bufferSize, num_pairs,
"INSTANCE", instance_uuid_string);
OSSharedPtr<OSData> uuid_data;
uuid_t uuid;
uuid_string_t uuid_string;
uuid_data = kext->copyUUID();
if (uuid_data) {
memcpy(uuid, uuid_data->getBytesNoCopy(), sizeof(uuid));
uuid_unparse(uuid, uuid_string);
OSKextPgoMetadataPut(pBuffer, position, bufferSize, num_pairs,
"UUID", uuid_string);
}
clock_sec_t secs;
clock_usec_t usecs;
clock_get_calendar_microtime(&secs, &usecs);
assert(usecs < 1000000);
char timestamp[max_timestamp_string_size + 1];
_static_assert_1_arg(sizeof(long) >= sizeof(clock_sec_t));
snprintf(timestamp, sizeof(timestamp), "%lu.%06d", (unsigned long)secs, (int)usecs);
OSKextPgoMetadataPut(pBuffer, position, bufferSize, num_pairs,
"TIMESTAMP", timestamp);
}
OSKextPgoMetadataPut(pBuffer, position, bufferSize, num_pairs,
"NAME", kext->getIdentifierCString());
char versionCString[kOSKextVersionMaxLength];
OSKextVersionGetString(kext->getVersion(), versionCString, kOSKextVersionMaxLength);
OSKextPgoMetadataPut(pBuffer, position, bufferSize, num_pairs,
"VERSION", versionCString);
}
static
size_t
OSKextPgoMetadataSize(OSKext *kext)
{
size_t position = 0;
uuid_t fakeuuid = {};
OSKextPgoMetadataPutAll(kext, fakeuuid, NULL, &position, 0, NULL);
return position;
}
int
OSKextGrabPgoDataLocked(OSKext *kext,
bool metadata,
uuid_t instance_uuid,
uint64_t *pSize,
char *pBuffer,
uint64_t bufferSize)
{
int err = 0;
kernel_section_t *sect_prf_data = NULL;
kernel_section_t *sect_prf_name = NULL;
kernel_section_t *sect_prf_cnts = NULL;
uint64_t size;
size_t metadata_size = 0;
size_t offset_to_pairs = 0;
sect_prf_data = kext->lookupSection("__DATA", "__llvm_prf_data");
sect_prf_name = kext->lookupSection("__DATA", "__llvm_prf_names");
if (!sect_prf_name) {
// kextcache sometimes truncates the section name to 15 chars
// <rdar://problem/52080551> 16 character section name is truncated to 15 characters by kextcache
sect_prf_name = kext->lookupSection("__DATA", "__llvm_prf_name");
}
sect_prf_cnts = kext->lookupSection("__DATA", "__llvm_prf_cnts");
if (!sect_prf_data || !sect_prf_name || !sect_prf_cnts) {
err = ENOTSUP;
goto out;
}
size = __llvm_profile_get_size_for_buffer_internal(
(const char*) sect_prf_data->addr, (const char*) sect_prf_data->addr + sect_prf_data->size,
(const char*) sect_prf_cnts->addr, (const char*) sect_prf_cnts->addr + sect_prf_cnts->size,
(const char*) sect_prf_name->addr, (const char*) sect_prf_name->addr + sect_prf_name->size);
if (metadata) {
metadata_size = OSKextPgoMetadataSize(kext);
size += metadata_size;
size += sizeof(pgo_metadata_footer);
}
if (pSize) {
*pSize = size;
}
if (pBuffer && bufferSize) {
if (bufferSize < size) {
err = ERANGE;
goto out;
}
err = __llvm_profile_write_buffer_internal(
pBuffer,
(const char*) sect_prf_data->addr, (const char*) sect_prf_data->addr + sect_prf_data->size,
(const char*) sect_prf_cnts->addr, (const char*) sect_prf_cnts->addr + sect_prf_cnts->size,
(const char*) sect_prf_name->addr, (const char*) sect_prf_name->addr + sect_prf_name->size);
if (err) {
err = EIO;
goto out;
}
if (metadata) {
offset_to_pairs = sizeof(struct pgo_metadata_footer) + metadata_size;
if (offset_to_pairs > UINT32_MAX) {
err = E2BIG;
goto out;
}
char *end_of_buffer = pBuffer + size;
struct pgo_metadata_footer *footerp = (struct pgo_metadata_footer *) (end_of_buffer - sizeof(struct pgo_metadata_footer));
char *metadata_buffer = end_of_buffer - (sizeof(struct pgo_metadata_footer) + metadata_size);
size_t metadata_position = 0;
uint32_t num_pairs = 0;
OSKextPgoMetadataPutAll(kext, instance_uuid, metadata_buffer, &metadata_position, metadata_size, &num_pairs);
while (metadata_position < metadata_size) {
metadata_buffer[metadata_position++] = 0;
}
struct pgo_metadata_footer footer;
footer.magic = htonl(0x6d657461);
footer.number_of_pairs = htonl( num_pairs );
footer.offset_to_pairs = htonl((uint32_t)offset_to_pairs );
memcpy(footerp, &footer, sizeof(footer));
}
}
out:
return err;
}
int
OSKextGrabPgoData(uuid_t uuid,
uint64_t *pSize,
char *pBuffer,
uint64_t bufferSize,
int wait_for_unload,
int metadata)
{
int err = 0;
OSSharedPtr<OSKext> kext;
IORecursiveLockLock(sKextLock);
kext = OSKext::lookupKextWithUUID(uuid);
if (!kext) {
err = ENOENT;
goto out;
}
if (wait_for_unload) {
OSKextGrabPgoStruct s;
s.metadata = metadata;
s.pSize = pSize;
s.pBuffer = pBuffer;
s.bufferSize = bufferSize;
s.err = EINTR;
struct list_head *prev = &kext->pendingPgoHead;
struct list_head *next = kext->pendingPgoHead.next;
s.list_head.prev = prev;
s.list_head.next = next;
prev->next = &s.list_head;
next->prev = &s.list_head;
kext.reset();
IORecursiveLockSleep(sKextLock, &s, THREAD_ABORTSAFE);
prev = s.list_head.prev;
next = s.list_head.next;
prev->next = next;
next->prev = prev;
err = s.err;
} else {
err = OSKextGrabPgoDataLocked(kext.get(), metadata, kext->instance_uuid, pSize, pBuffer, bufferSize);
}
out:
IORecursiveLockUnlock(sKextLock);
return err;
}
void
OSKextResetPgoCountersLock()
{
IORecursiveLockLock(sKextLock);
}
void
OSKextResetPgoCountersUnlock()
{
IORecursiveLockUnlock(sKextLock);
}
extern unsigned int not_in_kdp;
void
OSKextResetPgoCounters()
{
assert(!not_in_kdp);
uint32_t count = sLoadedKexts->getCount();
for (uint32_t i = 0; i < count; i++) {
OSKext *kext = OSDynamicCast(OSKext, sLoadedKexts->getObject(i));
kernel_section_t *sect_prf_cnts = kext->lookupSection("__DATA", "__llvm_prf_cnts");
if (!sect_prf_cnts) {
continue;
}
memset((void*)sect_prf_cnts->addr, 0, sect_prf_cnts->size);
}
}
OSSharedPtr<OSDictionary>
OSKext::copyLoadedKextInfoByUUID(
OSArray * kextIdentifiers,
OSArray * infoKeys)
{
OSSharedPtr<OSDictionary> result;
OSSharedPtr<OSDictionary> kextInfo;
uint32_t max_count, i, j;
uint32_t idCount = 0;
uint32_t idIndex = 0;
IORecursiveLockLock(sKextLock);
OSArray *list[2] = {sLoadedKexts.get(), sLoadedDriverKitKexts.get()};
uint32_t count[2] = {sLoadedKexts->getCount(), sLoadedDriverKitKexts->getCount()};
#if CONFIG_MACF
/* Is the calling process allowed to query kext info? */
if (current_task() != kernel_task) {
int macCheckResult = 0;
kauth_cred_t cred = NULL;
cred = kauth_cred_get_with_ref();
macCheckResult = mac_kext_check_query(cred);
kauth_cred_unref(&cred);
if (macCheckResult != 0) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Failed to query kext info (MAC policy error 0x%x).",
macCheckResult);
goto finish;
}
}
#endif
/* Empty list of UUIDs is equivalent to no list (get all).
*/
if (kextIdentifiers && !kextIdentifiers->getCount()) {
kextIdentifiers = NULL;
} else if (kextIdentifiers) {
idCount = kextIdentifiers->getCount();
}
/* Same for keys.
*/
if (infoKeys && !infoKeys->getCount()) {
infoKeys = NULL;
}
max_count = count[0] + count[1];
result = OSDictionary::withCapacity(max_count);
if (!result) {
goto finish;
}
for (j = 0; j < (sizeof(list) / sizeof(list[0])); j++) {
for (i = 0; i < count[j]; i++) {
OSKext *thisKext = NULL; // do not release
Boolean includeThis = true;
uuid_t thisKextUUID;
uuid_t thisKextTextUUID;
OSSharedPtr<OSData> uuid_data;
uuid_string_t uuid_key;
thisKext = OSDynamicCast(OSKext, list[j]->getObject(i));
if (!thisKext) {
continue;
}
uuid_data = thisKext->copyUUID();
if (!uuid_data) {
continue;
}
memcpy(&thisKextUUID, uuid_data->getBytesNoCopy(), sizeof(thisKextUUID));
uuid_unparse(thisKextUUID, uuid_key);
uuid_data = thisKext->copyTextUUID();
if (!uuid_data) {
continue;
}
memcpy(&thisKextTextUUID, uuid_data->getBytesNoCopy(), sizeof(thisKextTextUUID));
/* Skip current kext if we have a list of UUIDs and
* it isn't in the list.
*/
if (kextIdentifiers) {
includeThis = false;
for (idIndex = 0; idIndex < idCount; idIndex++) {
const OSString* wantedUUID = OSDynamicCast(OSString,
kextIdentifiers->getObject(idIndex));
uuid_t uuid;
uuid_parse(wantedUUID->getCStringNoCopy(), uuid);
if ((0 == uuid_compare(uuid, thisKextUUID))
|| (0 == uuid_compare(uuid, thisKextTextUUID))) {
includeThis = true;
/* Only need to find the first kext if multiple match,
* ie. asking for the kernel uuid does not need to find
* interface kexts or builtin static kexts.
*/
kextIdentifiers->removeObject(idIndex);
uuid_unparse(uuid, uuid_key);
break;
}
}
}
if (!includeThis) {
continue;
}
kextInfo = thisKext->copyInfo(infoKeys);
if (kextInfo) {
result->setObject(uuid_key, kextInfo.get());
}
if (kextIdentifiers && !kextIdentifiers->getCount()) {
goto finish;
}
}
}
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
OSSharedPtr<OSDictionary>
OSKext::copyKextCollectionInfo(
OSDictionary *requestDict,
OSArray *infoKeys)
{
OSSharedPtr<OSDictionary> result;
OSString *collectionType = NULL;
OSObject *rawLoadedState = NULL;
OSString *loadedState = NULL;
kc_kind_t kc_request_kind = KCKindUnknown;
bool onlyLoaded = false;
bool onlyUnloaded = false;
#if CONFIG_MACF
/* Is the calling process allowed to query kext info? */
if (current_task() != kernel_task) {
int macCheckResult = 0;
kauth_cred_t cred = NULL;
cred = kauth_cred_get_with_ref();
macCheckResult = mac_kext_check_query(cred);
kauth_cred_unref(&cred);
if (macCheckResult != 0) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Failed to query kext info (MAC policy error 0x%x).",
macCheckResult);
goto finish;
}
}
#endif
if (infoKeys && !infoKeys->getCount()) {
infoKeys = NULL;
}
collectionType = OSDynamicCast(OSString,
_OSKextGetRequestArgument(requestDict,
kKextRequestArgumentCollectionTypeKey));
if (!collectionType) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid '%s' argument to kext collection info request.",
kKextRequestArgumentCollectionTypeKey);
goto finish;
}
if (collectionType->isEqualTo(kKCTypePrimary)) {
kc_request_kind = KCKindPrimary;
} else if (collectionType->isEqualTo(kKCTypeSystem)) {
kc_request_kind = KCKindPageable;
} else if (collectionType->isEqualTo(kKCTypeAuxiliary)) {
kc_request_kind = KCKindAuxiliary;
} else if (collectionType->isEqualTo(kKCTypeCodeless)) {
kc_request_kind = KCKindNone;
} else if (!collectionType->isEqualTo(kKCTypeAny)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid '%s' argument value '%s' to kext collection info request.",
kKextRequestArgumentCollectionTypeKey,
collectionType->getCStringNoCopy());
goto finish;
}
rawLoadedState = _OSKextGetRequestArgument(requestDict,
kKextRequestArgumentLoadedStateKey);
if (rawLoadedState) {
loadedState = OSDynamicCast(OSString, rawLoadedState);
if (!loadedState) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogIPCFlag,
"Invalid '%s' argument to kext collection info request.",
kKextRequestArgumentLoadedStateKey);
goto finish;
}
}
if (loadedState) {
if (loadedState->isEqualTo("Loaded")) {
onlyLoaded = true;
} else if (loadedState->isEqualTo("Unloaded")) {
onlyUnloaded = true;
} else if (!loadedState->isEqualTo("Any")) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Invalid '%s' argument value '%s' for '%s' collection info",
kKextRequestArgumentLoadedStateKey,
loadedState->getCStringNoCopy(),
collectionType->getCStringNoCopy());
goto finish;
}
}
result = OSDictionary::withCapacity(sKextsByID->getCount());
if (!result) {
goto finish;
}
IORecursiveLockLock(sKextLock);
{ // start block scope
sKextsByID->iterateObjects(^bool (const OSSymbol *thisKextID, OSObject *obj)
{
OSKext *thisKext = NULL; // do not release
OSSharedPtr<OSDictionary> kextInfo;
(void)thisKextID;
thisKext = OSDynamicCast(OSKext, obj);
if (!thisKext) {
return false;;
}
/*
* skip the kext if it came from the wrong collection type
* (and the caller requested a specific type)
*/
if ((kc_request_kind != KCKindUnknown) && (thisKext->kc_type != kc_request_kind)) {
return false;
}
/*
* respect the caller's desire to find only loaded or
* unloaded kexts
*/
if (onlyLoaded && (-1U == sLoadedKexts->getNextIndexOfObject(thisKext, 0))) {
return false;
}
if (onlyUnloaded && (-1U != sLoadedKexts->getNextIndexOfObject(thisKext, 0))) {
return false;
}
kextInfo = thisKext->copyInfo(infoKeys);
if (kextInfo) {
result->setObject(thisKext->getIdentifier(), kextInfo.get());
}
return false;
});
} // end block scope
IORecursiveLockUnlock(sKextLock);
finish:
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
OSSharedPtr<OSDictionary>
OSKext::copyLoadedKextInfo(
OSArray * kextIdentifiers,
OSArray * infoKeys)
{
OSSharedPtr<OSDictionary> result;
uint32_t idCount = 0;
bool onlyLoaded;
IORecursiveLockLock(sKextLock);
#if CONFIG_MACF
/* Is the calling process allowed to query kext info? */
if (current_task() != kernel_task) {
int macCheckResult = 0;
kauth_cred_t cred = NULL;
cred = kauth_cred_get_with_ref();
macCheckResult = mac_kext_check_query(cred);
kauth_cred_unref(&cred);
if (macCheckResult != 0) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Failed to query kext info (MAC policy error 0x%x).",
macCheckResult);
goto finish;
}
}
#endif
/* Empty list of bundle ids is equivalent to no list (get all).
*/
if (kextIdentifiers && !kextIdentifiers->getCount()) {
kextIdentifiers = NULL;
} else if (kextIdentifiers) {
idCount = kextIdentifiers->getCount();
}
/* Same for keys.
*/
if (infoKeys && !infoKeys->getCount()) {
infoKeys = NULL;
}
onlyLoaded = (!infoKeys || !_OSArrayContainsCString(infoKeys, kOSBundleAllPrelinkedKey));
result = OSDictionary::withCapacity(128);
if (!result) {
goto finish;
}
#if 0
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"kaslr: vm_kernel_slide 0x%lx \n",
vm_kernel_slide);
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"kaslr: vm_kernel_stext 0x%lx vm_kernel_etext 0x%lx \n",
vm_kernel_stext, vm_kernel_etext);
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"kaslr: vm_kernel_base 0x%lx vm_kernel_top 0x%lx \n",
vm_kernel_base, vm_kernel_top);
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"kaslr: vm_kext_base 0x%lx vm_kext_top 0x%lx \n",
vm_kext_base, vm_kext_top);
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"kaslr: vm_prelink_stext 0x%lx vm_prelink_etext 0x%lx \n",
vm_prelink_stext, vm_prelink_etext);
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"kaslr: vm_prelink_sinfo 0x%lx vm_prelink_einfo 0x%lx \n",
vm_prelink_sinfo, vm_prelink_einfo);
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"kaslr: vm_slinkedit 0x%lx vm_elinkedit 0x%lx \n",
vm_slinkedit, vm_elinkedit);
#endif
{ // start block scope
sKextsByID->iterateObjects(^bool (const OSSymbol * thisKextID, OSObject * obj)
{
OSKext * thisKext = NULL; // do not release
Boolean includeThis = true;
OSSharedPtr<OSDictionary> kextInfo;
thisKext = OSDynamicCast(OSKext, obj);
if (!thisKext) {
return false;;
}
/* Skip current kext if not yet started and caller didn't request all.
*/
if (onlyLoaded && (-1U == sLoadedKexts->getNextIndexOfObject(thisKext, 0))) {
return false;;
}
/* Skip current kext if we have a list of bundle IDs and
* it isn't in the list.
*/
if (kextIdentifiers) {
includeThis = false;
for (uint32_t idIndex = 0; idIndex < idCount; idIndex++) {
const OSString * thisRequestID = OSDynamicCast(OSString,
kextIdentifiers->getObject(idIndex));
if (thisKextID->isEqualTo(thisRequestID)) {
includeThis = true;
break;
}
}
}
if (!includeThis) {
return false;
}
kextInfo = thisKext->copyInfo(infoKeys);
if (kextInfo) {
result->setObject(thisKext->getIdentifier(), kextInfo.get());
}
return false;
});
} // end block scope
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
* Any info that needs to do allocations must goto finish on alloc
* failure. Info that is just a lookup should just not set the object
* if the info does not exist.
*********************************************************************/
#define _OSKextLoadInfoDictCapacity (12)
OSSharedPtr<OSDictionary>
OSKext::copyInfo(OSArray * infoKeys)
{
OSSharedPtr<OSDictionary> result;
bool success = false;
OSSharedPtr<OSData> headerData;
OSSharedPtr<OSData> logData;
OSSharedPtr<OSNumber> cpuTypeNumber;
OSSharedPtr<OSNumber> cpuSubtypeNumber;
OSString * versionString = NULL; // do not release
OSString * bundleType = NULL; // do not release
uint32_t executablePathCStringSize = 0;
char * executablePathCString = NULL; // must kfree
OSSharedPtr<OSString> executablePathString;
OSSharedPtr<OSData> uuid;
OSSharedPtr<OSArray> dependencyLoadTags;
OSSharedPtr<OSCollectionIterator> metaClassIterator;
OSSharedPtr<OSArray> metaClassInfo;
OSSharedPtr<OSDictionary> metaClassDict;
OSMetaClass * thisMetaClass = NULL; // do not release
OSSharedPtr<OSString> metaClassName;
OSSharedPtr<OSString> superclassName;
kc_format_t kcformat;
uint32_t count, i;
result = OSDictionary::withCapacity(_OSKextLoadInfoDictCapacity);
if (!result) {
goto finish;
}
/* Empty keys means no keys, but NULL is quicker to check.
*/
if (infoKeys && !infoKeys->getCount()) {
infoKeys = NULL;
}
if (!PE_get_primary_kc_format(&kcformat)) {
goto finish;
}
/* Headers, CPU type, and CPU subtype.
*/
if (!infoKeys ||
_OSArrayContainsCString(infoKeys, kOSBundleMachOHeadersKey) ||
_OSArrayContainsCString(infoKeys, kOSBundleLogStringsKey) ||
_OSArrayContainsCString(infoKeys, kOSBundleCPUTypeKey) ||
_OSArrayContainsCString(infoKeys, kOSBundleCPUSubtypeKey)) {
if (linkedExecutable && !isInterface()) {
kernel_mach_header_t *kext_mach_hdr = (kernel_mach_header_t *)
linkedExecutable->getBytesNoCopy();
#if !SECURE_KERNEL || XNU_TARGET_OS_OSX
// do not return macho header info on shipping embedded - 19095897
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleMachOHeadersKey)) {
kernel_mach_header_t * temp_kext_mach_hdr;
struct load_command * lcp;
headerData = OSData::withBytes(kext_mach_hdr,
(u_int) (sizeof(*kext_mach_hdr) + kext_mach_hdr->sizeofcmds));
if (!headerData) {
goto finish;
}
// unslide any vmaddrs we return to userspace - 10726716
temp_kext_mach_hdr = (kernel_mach_header_t *)
headerData->getBytesNoCopy();
if (temp_kext_mach_hdr == NULL) {
goto finish;
}
lcp = (struct load_command *) (temp_kext_mach_hdr + 1);
for (i = 0; i < temp_kext_mach_hdr->ncmds; i++) {
if (lcp->cmd == LC_SEGMENT_KERNEL) {
kernel_segment_command_t * segp;
kernel_section_t * secp;
segp = (kernel_segment_command_t *) lcp;
// 10543468 - if we jettisoned __LINKEDIT clear size info
if (flags.jettisonLinkeditSeg) {
if (strncmp(segp->segname, SEG_LINKEDIT, sizeof(segp->segname)) == 0) {
segp->vmsize = 0;
segp->fileoff = 0;
segp->filesize = 0;
}
}
#if __arm__ || __arm64__
// iBoot disregards zero-size segments, just set their addresses to gVirtBase
// and unslide them to avoid vm assertion failures / kernel logging breakage.
if (segp->vmsize == 0 && segp->vmaddr < gVirtBase) {
segp->vmaddr = gVirtBase;
for (secp = firstsect(segp); secp != NULL; secp = nextsect(segp, secp)) {
secp->size = 0; // paranoia :)
secp->addr = gVirtBase;
}
}
#endif
#if 0
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"%s: LC_SEGMENT_KERNEL segname '%s' vmaddr 0x%llX 0x%lX vmsize %llu nsects %u",
__FUNCTION__, segp->segname, segp->vmaddr,
VM_KERNEL_UNSLIDE(segp->vmaddr),
segp->vmsize, segp->nsects);
if ((VM_KERNEL_IS_SLID(segp->vmaddr) == false) &&
(VM_KERNEL_IS_KEXT(segp->vmaddr) == false) &&
(VM_KERNEL_IS_PRELINKTEXT(segp->vmaddr) == false) &&
(VM_KERNEL_IS_PRELINKINFO(segp->vmaddr) == false) &&
(VM_KERNEL_IS_KEXT_LINKEDIT(segp->vmaddr) == false)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"%s: not in kext range - vmaddr 0x%llX vm_kext_base 0x%lX vm_kext_top 0x%lX",
__FUNCTION__, segp->vmaddr, vm_kext_base, vm_kext_top);
}
#endif
segp->vmaddr = ml_static_unslide(segp->vmaddr);
for (secp = firstsect(segp); secp != NULL; secp = nextsect(segp, secp)) {
secp->addr = ml_static_unslide(secp->addr);
}
}
lcp = (struct load_command *)((caddr_t)lcp + lcp->cmdsize);
}
result->setObject(kOSBundleMachOHeadersKey, headerData.get());
}
#endif // !SECURE_KERNEL || XNU_TARGET_OS_OSX
if (_OSArrayContainsCString(infoKeys, kOSBundleLogStringsKey)) {
osLogDataHeaderRef *header;
char headerBytes[offsetof(osLogDataHeaderRef, sections) + NUM_OS_LOG_SECTIONS * sizeof(header->sections[0])];
void *os_log_data = NULL;
void *cstring_data = NULL;
unsigned long os_log_size = 0;
unsigned long cstring_size = 0;
uint32_t os_log_offset = 0;
uint32_t cstring_offset = 0;
bool res;
os_log_data = getsectdatafromheader(kext_mach_hdr, "__TEXT", "__os_log", &os_log_size);
os_log_offset = (uintptr_t)os_log_data - (uintptr_t)kext_mach_hdr;
cstring_data = getsectdatafromheader(kext_mach_hdr, "__TEXT", "__cstring", &cstring_size);
cstring_offset = (uintptr_t)cstring_data - (uintptr_t)kext_mach_hdr;
header = (osLogDataHeaderRef *) headerBytes;
header->version = OS_LOG_HDR_VERSION;
header->sect_count = NUM_OS_LOG_SECTIONS;
header->sections[OS_LOG_SECT_IDX].sect_offset = os_log_offset;
header->sections[OS_LOG_SECT_IDX].sect_size = (uint32_t) os_log_size;
header->sections[CSTRING_SECT_IDX].sect_offset = cstring_offset;
header->sections[CSTRING_SECT_IDX].sect_size = (uint32_t) cstring_size;
logData = OSData::withBytes(header, (u_int) (sizeof(osLogDataHeaderRef)));
if (!logData) {
goto finish;
}
res = logData->appendBytes(&(header->sections[0]), (u_int)(header->sect_count * sizeof(header->sections[0])));
if (!res) {
goto finish;
}
if (os_log_data) {
res = logData->appendBytes(os_log_data, (u_int)header->sections[OS_LOG_SECT_IDX].sect_size);
if (!res) {
goto finish;
}
}
if (cstring_data) {
res = logData->appendBytes(cstring_data, (u_int)header->sections[CSTRING_SECT_IDX].sect_size);
if (!res) {
goto finish;
}
}
result->setObject(kOSBundleLogStringsKey, logData.get());
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleCPUTypeKey)) {
cpuTypeNumber = OSNumber::withNumber(
(uint64_t) kext_mach_hdr->cputype,
8 * sizeof(kext_mach_hdr->cputype));
if (!cpuTypeNumber) {
goto finish;
}
result->setObject(kOSBundleCPUTypeKey, cpuTypeNumber.get());
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleCPUSubtypeKey)) {
cpuSubtypeNumber = OSNumber::withNumber(
(uint64_t) kext_mach_hdr->cpusubtype,
8 * sizeof(kext_mach_hdr->cpusubtype));
if (!cpuSubtypeNumber) {
goto finish;
}
result->setObject(kOSBundleCPUSubtypeKey, cpuSubtypeNumber.get());
}
} else {
if (isDriverKit() && _OSArrayContainsCString(infoKeys, kOSBundleLogStringsKey)) {
osLogDataHeaderRef *header;
char headerBytes[offsetof(osLogDataHeaderRef, sections) + NUM_OS_LOG_SECTIONS * sizeof(header->sections[0])];
bool res;
header = (osLogDataHeaderRef *) headerBytes;
header->version = OS_LOG_HDR_VERSION;
header->sect_count = NUM_OS_LOG_SECTIONS;
header->sections[OS_LOG_SECT_IDX].sect_offset = 0;
header->sections[OS_LOG_SECT_IDX].sect_size = (uint32_t) 0;
header->sections[CSTRING_SECT_IDX].sect_offset = 0;
header->sections[CSTRING_SECT_IDX].sect_size = (uint32_t) 0;
logData = OSData::withBytes(header, (u_int) (sizeof(osLogDataHeaderRef)));
if (!logData) {
goto finish;
}
res = logData->appendBytes(&(header->sections[0]), (u_int)(header->sect_count * sizeof(header->sections[0])));
if (!res) {
goto finish;
}
result->setObject(kOSBundleLogStringsKey, logData.get());
}
}
}
/* CFBundleIdentifier. We set this regardless because it's just stupid not to.
*/
result->setObject(kCFBundleIdentifierKey, bundleID.get());
/* CFBundlePackageType
*/
bundleType = infoDict ? OSDynamicCast(OSString, infoDict->getObject(kCFBundlePackageTypeKey)): NULL;
if (bundleType) {
result->setObject(kCFBundlePackageTypeKey, bundleType);
}
/* CFBundleVersion.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kCFBundleVersionKey)) {
versionString = OSDynamicCast(OSString,
getPropertyForHostArch(kCFBundleVersionKey));
if (versionString) {
result->setObject(kCFBundleVersionKey, versionString);
}
}
/* OSBundleCompatibleVersion.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleCompatibleVersionKey)) {
versionString = OSDynamicCast(OSString,
getPropertyForHostArch(kOSBundleCompatibleVersionKey));
if (versionString) {
result->setObject(kOSBundleCompatibleVersionKey, versionString);
}
}
/* Path.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundlePathKey)) {
if (path) {
result->setObject(kOSBundlePathKey, path.get());
}
}
/* OSBundleExecutablePath.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleExecutablePathKey)) {
if (path && executableRelPath) {
uint32_t pathLength = path->getLength(); // gets incremented below
// +1 for slash, +1 for \0
executablePathCStringSize = pathLength + executableRelPath->getLength() + 2;
executablePathCString = (char *)kheap_alloc_tag(KHEAP_TEMP,
executablePathCStringSize, Z_WAITOK, VM_KERN_MEMORY_OSKEXT); // +1 for \0
if (!executablePathCString) {
goto finish;
}
strlcpy(executablePathCString, path->getCStringNoCopy(),
executablePathCStringSize);
executablePathCString[pathLength++] = '/';
executablePathCString[pathLength++] = '\0';
strlcat(executablePathCString, executableRelPath->getCStringNoCopy(),
executablePathCStringSize);
executablePathString = OSString::withCString(executablePathCString);
if (!executablePathString) {
goto finish;
}
result->setObject(kOSBundleExecutablePathKey, executablePathString.get());
} else if (flags.builtin) {
result->setObject(kOSBundleExecutablePathKey, bundleID.get());
} else if (isDriverKit()) {
if (path) {
// +1 for slash, +1 for \0
uint32_t pathLength = path->getLength();
executablePathCStringSize = pathLength + 2;
executablePathCString = (char *)kheap_alloc_tag(KHEAP_TEMP,
executablePathCStringSize, Z_WAITOK, VM_KERN_MEMORY_OSKEXT);
if (!executablePathCString) {
goto finish;
}
strlcpy(executablePathCString, path->getCStringNoCopy(), executablePathCStringSize);
executablePathCString[pathLength++] = '/';
executablePathCString[pathLength++] = '\0';
executablePathString = OSString::withCString(executablePathCString);
if (!executablePathString) {
goto finish;
}
result->setObject(kOSBundleExecutablePathKey, executablePathString.get());
}
}
}
/* UUID, if the kext has one.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleUUIDKey)) {
uuid = copyUUID();
if (uuid) {
result->setObject(kOSBundleUUIDKey, uuid.get());
}
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleTextUUIDKey)) {
uuid = copyTextUUID();
if (uuid) {
result->setObject(kOSBundleTextUUIDKey, uuid.get());
}
}
/*
* Info.plist digest
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSKextInfoPlistDigestKey)) {
OSData *digest;
digest = infoDict ? OSDynamicCast(OSData, infoDict->getObject(kOSKextInfoPlistDigestKey)) : NULL;
if (digest) {
result->setObject(kOSKextInfoPlistDigestKey, digest);
}
}
/*
* Collection type
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSKextBundleCollectionTypeKey)) {
result->setObject(kOSKextBundleCollectionTypeKey, OSString::withCString(getKCTypeString()));
}
/*
* Collection availability
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSKextAuxKCAvailabilityKey)) {
result->setObject(kOSKextAuxKCAvailabilityKey,
isLoadable() ? kOSBooleanTrue : kOSBooleanFalse);
}
/*
* Allows user load
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleAllowUserLoadKey)) {
OSBoolean *allowUserLoad = OSDynamicCast(OSBoolean, getPropertyForHostArch(kOSBundleAllowUserLoadKey));
if (allowUserLoad) {
result->setObject(kOSBundleAllowUserLoadKey, allowUserLoad);
}
}
/*
* Bundle Dependencies (OSBundleLibraries)
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleLibrariesKey)) {
OSDictionary *libraries = OSDynamicCast(OSDictionary, getPropertyForHostArch(kOSBundleLibrariesKey));
if (libraries) {
result->setObject(kOSBundleLibrariesKey, libraries);
}
}
/*****
* OSKernelResource, OSBundleIsInterface, OSBundlePrelinked, OSBundleStarted.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSKernelResourceKey)) {
result->setObject(kOSKernelResourceKey,
isKernelComponent() ? kOSBooleanTrue : kOSBooleanFalse);
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleIsInterfaceKey)) {
result->setObject(kOSBundleIsInterfaceKey,
isInterface() ? kOSBooleanTrue : kOSBooleanFalse);
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundlePrelinkedKey)) {
result->setObject(kOSBundlePrelinkedKey,
isPrelinked() ? kOSBooleanTrue : kOSBooleanFalse);
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleStartedKey)) {
result->setObject(kOSBundleStartedKey,
isStarted() ? kOSBooleanTrue : kOSBooleanFalse);
}
/* LoadTag (Index).
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleLoadTagKey)) {
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber((unsigned long long)loadTag,
/* numBits */ 8 * sizeof(loadTag));
if (!scratchNumber) {
goto finish;
}
result->setObject(kOSBundleLoadTagKey, scratchNumber.get());
}
/* LoadAddress, LoadSize.
*/
if (!infoKeys ||
_OSArrayContainsCString(infoKeys, kOSBundleLoadAddressKey) ||
_OSArrayContainsCString(infoKeys, kOSBundleLoadSizeKey) ||
_OSArrayContainsCString(infoKeys, kOSBundleExecLoadAddressKey) ||
_OSArrayContainsCString(infoKeys, kOSBundleExecLoadSizeKey) ||
_OSArrayContainsCString(infoKeys, kOSBundleWiredSizeKey)) {
bool is_dext = isDriverKit();
if (isInterface() || flags.builtin || linkedExecutable || is_dext) {
/* These go to userspace via serialization, so we don't want any doubts
* about their size.
*/
uint64_t loadAddress = 0;
uint32_t loadSize = 0;
uint32_t wiredSize = 0;
uint64_t execLoadAddress = 0;
uint32_t execLoadSize = 0;
/* Interfaces always report 0 load address & size.
* Just the way they roll.
*
* xxx - leaving in # when we have a linkedExecutable...a kernelcomp
* xxx - shouldn't have one!
*/
if (flags.builtin || linkedExecutable) {
kernel_mach_header_t *mh = NULL;
kernel_segment_command_t *seg = NULL;
if (flags.builtin) {
loadAddress = kmod_info->address;
loadSize = (uint32_t)kmod_info->size;
} else {
loadAddress = (uint64_t)linkedExecutable->getBytesNoCopy();
loadSize = linkedExecutable->getLength();
}
mh = (kernel_mach_header_t *)loadAddress;
loadAddress = ml_static_unslide(loadAddress);
/* Walk through the kext, looking for the first executable
* segment in case we were asked for its size/address.
*/
for (seg = firstsegfromheader(mh); seg != NULL; seg = nextsegfromheader(mh, seg)) {
if (seg->initprot & VM_PROT_EXECUTE) {
execLoadAddress = ml_static_unslide(seg->vmaddr);
execLoadSize = (uint32_t)seg->vmsize;
break;
}
}
/* If we have a kmod_info struct, calculated the wired size
* from that. Otherwise it's the full load size.
*/
if (kmod_info) {
wiredSize = loadSize - (uint32_t)kmod_info->hdr_size;
} else {
wiredSize = loadSize;
}
} else if (is_dext) {
/*
* DriverKit userspace executables do not have a kernel linkedExecutable,
* so we "fake" their address range with the LoadTag.
*/
if (loadTag) {
loadAddress = execLoadAddress = loadTag;
loadSize = execLoadSize = 1;
}
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleLoadAddressKey)) {
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(unsigned long long)(loadAddress),
/* numBits */ 8 * sizeof(loadAddress));
if (!scratchNumber) {
goto finish;
}
result->setObject(kOSBundleLoadAddressKey, scratchNumber.get());
}
if (kcformat == KCFormatStatic || kcformat == KCFormatKCGEN) {
if ((!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleCacheLoadAddressKey))
&& loadAddress && loadSize) {
void *baseAddress = PE_get_kc_baseaddress(KCKindPrimary);
if (!baseAddress) {
goto finish;
}
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(unsigned long long)ml_static_unslide((vm_offset_t)baseAddress),
/* numBits */ 8 * sizeof(loadAddress));
if (!scratchNumber) {
goto finish;
}
result->setObject(kOSBundleCacheLoadAddressKey, scratchNumber.get());
}
if ((!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleKextsInKernelTextKey))
&& (this == sKernelKext) && gBuiltinKmodsCount) {
result->setObject(kOSBundleKextsInKernelTextKey, kOSBooleanTrue);
}
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleExecLoadAddressKey)) {
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(unsigned long long)(execLoadAddress),
/* numBits */ 8 * sizeof(execLoadAddress));
if (!scratchNumber) {
goto finish;
}
result->setObject(kOSBundleExecLoadAddressKey, scratchNumber.get());
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleLoadSizeKey)) {
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(unsigned long long)(loadSize),
/* numBits */ 8 * sizeof(loadSize));
if (!scratchNumber) {
goto finish;
}
result->setObject(kOSBundleLoadSizeKey, scratchNumber.get());
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleExecLoadSizeKey)) {
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(unsigned long long)(execLoadSize),
/* numBits */ 8 * sizeof(execLoadSize));
if (!scratchNumber) {
goto finish;
}
result->setObject(kOSBundleExecLoadSizeKey, scratchNumber.get());
}
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleWiredSizeKey)) {
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(unsigned long long)(wiredSize),
/* numBits */ 8 * sizeof(wiredSize));
if (!scratchNumber) {
goto finish;
}
result->setObject(kOSBundleWiredSizeKey, scratchNumber.get());
}
}
}
/* OSBundleDependencies. In descending order for
* easy compatibility with kextstat(8).
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleDependenciesKey)) {
if ((count = getNumDependencies())) {
dependencyLoadTags = OSArray::withCapacity(count);
result->setObject(kOSBundleDependenciesKey, dependencyLoadTags.get());
i = count - 1;
do {
OSKext * dependency = OSDynamicCast(OSKext,
dependencies->getObject(i));
if (!dependency) {
continue;
}
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(unsigned long long)dependency->getLoadTag(),
/* numBits*/ 8 * sizeof(loadTag));
if (!scratchNumber) {
goto finish;
}
dependencyLoadTags->setObject(scratchNumber.get());
} while (i--);
}
}
/* OSBundleMetaClasses.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleClassesKey)) {
if (metaClasses && metaClasses->getCount()) {
metaClassIterator = OSCollectionIterator::withCollection(metaClasses.get());
metaClassInfo = OSArray::withCapacity(metaClasses->getCount());
if (!metaClassIterator || !metaClassInfo) {
goto finish;
}
result->setObject(kOSBundleClassesKey, metaClassInfo.get());
while ((thisMetaClass = OSDynamicCast(OSMetaClass,
metaClassIterator->getNextObject()))) {
metaClassDict = OSDictionary::withCapacity(3);
if (!metaClassDict) {
goto finish;
}
metaClassName = OSString::withCString(thisMetaClass->getClassName());
if (thisMetaClass->getSuperClass()) {
superclassName = OSString::withCString(
thisMetaClass->getSuperClass()->getClassName());
}
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(thisMetaClass->getInstanceCount(),
8 * sizeof(unsigned int));
/* Bail if any of the essentials is missing. The root class lacks a superclass,
* of course.
*/
if (!metaClassDict || !metaClassName || !scratchNumber) {
goto finish;
}
metaClassInfo->setObject(metaClassDict.get());
metaClassDict->setObject(kOSMetaClassNameKey, metaClassName.get());
if (superclassName) {
metaClassDict->setObject(kOSMetaClassSuperclassNameKey, superclassName.get());
}
metaClassDict->setObject(kOSMetaClassTrackingCountKey, scratchNumber.get());
}
}
}
/* OSBundleRetainCount.
*/
if (!infoKeys || _OSArrayContainsCString(infoKeys, kOSBundleRetainCountKey)) {
{
int kextRetainCount = getRetainCount() - 1;
if (isLoaded()) {
kextRetainCount--;
}
OSSharedPtr<OSNumber> scratchNumber = OSNumber::withNumber(
(int)kextRetainCount,
/* numBits*/ 8 * sizeof(int));
if (scratchNumber) {
result->setObject(kOSBundleRetainCountKey, scratchNumber.get());
}
}
}
success = true;
finish:
if (executablePathCString) {
kheap_free(KHEAP_TEMP, executablePathCString, executablePathCStringSize);
}
if (!success) {
result.reset();
}
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
bool
OSKext::copyUserExecutablePath(const OSSymbol * bundleID, char * pathResult, size_t pathSize)
{
bool ok;
OSSharedPtr<OSKext> kext;
IORecursiveLockLock(sKextLock);
kext.reset(OSDynamicCast(OSKext, sKextsByID->getObject(bundleID)), OSRetain);
IORecursiveLockUnlock(sKextLock);
if (!kext || !kext->path || !kext->userExecutableRelPath) {
return false;
}
snprintf(pathResult, pathSize, "%s/Contents/MacOS/%s",
kext->path->getCStringNoCopy(),
kext->userExecutableRelPath->getCStringNoCopy());
ok = true;
return ok;
}
/*********************************************************************
*********************************************************************/
/* static */
OSReturn
OSKext::requestResource(
const char * kextIdentifierCString,
const char * resourceNameCString,
OSKextRequestResourceCallback callback,
void * context,
OSKextRequestTag * requestTagOut)
{
OSReturn result = kOSReturnError;
OSSharedPtr<OSKext> callbackKext; // looked up
OSKextRequestTag requestTag = -1;
OSSharedPtr<OSNumber> requestTagNum;
OSSharedPtr<OSDictionary> requestDict;
OSSharedPtr<OSString> kextIdentifier;
OSSharedPtr<OSString> resourceName;
OSSharedPtr<OSDictionary> callbackRecord;
OSSharedPtr<OSData> callbackWrapper;
OSSharedPtr<OSData> contextWrapper;
IORecursiveLockLock(sKextLock);
if (requestTagOut) {
*requestTagOut = kOSKextRequestTagInvalid;
}
/* If requests to user space are disabled, don't go any further */
if (!sKernelRequestsEnabled) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Can't request resource %s for %s - requests to user space are disabled.",
resourceNameCString,
kextIdentifierCString);
result = kOSKextReturnDisabled;
goto finish;
}
if (!kextIdentifierCString || !resourceNameCString || !callback) {
result = kOSKextReturnInvalidArgument;
goto finish;
}
callbackKext = OSKext::lookupKextWithAddress((vm_address_t)callback);
if (!callbackKext) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Resource request has bad callback address.");
result = kOSKextReturnInvalidArgument;
goto finish;
}
if (!callbackKext->flags.starting && !callbackKext->flags.started) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Resource request callback is in a kext that is not started.");
result = kOSKextReturnInvalidArgument;
goto finish;
}
/* Do not allow any new requests to be made on a kext that is unloading.
*/
if (callbackKext->flags.stopping) {
result = kOSKextReturnStopping;
goto finish;
}
/* If we're wrapped the next available request tag around to the negative
* numbers, we can't service any more requests.
*/
if (sNextRequestTag == kOSKextRequestTagInvalid) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"No more request tags available; restart required.");
result = kOSKextReturnNoResources;
goto finish;
}
requestTag = sNextRequestTag++;
result = _OSKextCreateRequest(kKextRequestPredicateRequestResource,
requestDict);
if (result != kOSReturnSuccess) {
goto finish;
}
kextIdentifier = OSString::withCString(kextIdentifierCString);
resourceName = OSString::withCString(resourceNameCString);
requestTagNum = OSNumber::withNumber((long long unsigned int)requestTag,
8 * sizeof(requestTag));
if (!kextIdentifier ||
!resourceName ||
!requestTagNum ||
!_OSKextSetRequestArgument(requestDict.get(),
kKextRequestArgumentBundleIdentifierKey, kextIdentifier.get()) ||
!_OSKextSetRequestArgument(requestDict.get(),
kKextRequestArgumentNameKey, resourceName.get()) ||
!_OSKextSetRequestArgument(requestDict.get(),
kKextRequestArgumentRequestTagKey, requestTagNum.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
callbackRecord = OSDynamicPtrCast<OSDictionary>(requestDict->copyCollection());
if (!callbackRecord) {
result = kOSKextReturnNoMemory;
goto finish;
}
// we validate callback address at call time
callbackWrapper = OSData::withBytes((void *)&callback, sizeof(void *));
if (context) {
contextWrapper = OSData::withBytes((void *)&context, sizeof(void *));
}
if (!callbackWrapper || !_OSKextSetRequestArgument(callbackRecord.get(),
kKextRequestArgumentCallbackKey, callbackWrapper.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
if (context) {
if (!contextWrapper || !_OSKextSetRequestArgument(callbackRecord.get(),
kKextRequestArgumentContextKey, contextWrapper.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
}
/* Only post the requests after all the other potential failure points
* have been passed.
*/
if (!sKernelRequests->setObject(requestDict.get()) ||
!sRequestCallbackRecords->setObject(callbackRecord.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
OSKext::pingIOKitDaemon();
result = kOSReturnSuccess;
if (requestTagOut) {
*requestTagOut = requestTag;
}
finish:
/* If we didn't succeed, yank the request & callback
* from their holding arrays.
*/
if (result != kOSReturnSuccess) {
unsigned int index;
index = sKernelRequests->getNextIndexOfObject(requestDict.get(), 0);
if (index != (unsigned int)-1) {
sKernelRequests->removeObject(index);
}
index = sRequestCallbackRecords->getNextIndexOfObject(callbackRecord.get(), 0);
if (index != (unsigned int)-1) {
sRequestCallbackRecords->removeObject(index);
}
}
OSKext::considerUnloads(/* rescheduleOnly? */ true);
IORecursiveLockUnlock(sKextLock);
return result;
}
OSReturn
OSKext::requestDaemonLaunch(
OSString *kextIdentifier,
OSString *serverName,
OSNumber *serverTag,
OSSharedPtr<IOUserServerCheckInToken> &checkInToken)
{
OSReturn result;
IOUserServerCheckInToken * checkInTokenRaw = NULL;
result = requestDaemonLaunch(kextIdentifier, serverName,
serverTag, &checkInTokenRaw);
if (kOSReturnSuccess == result) {
checkInToken.reset(checkInTokenRaw, OSNoRetain);
}
return result;
}
OSReturn
OSKext::requestDaemonLaunch(
OSString *kextIdentifier,
OSString *serverName,
OSNumber *serverTag,
IOUserServerCheckInToken ** checkInToken)
{
OSReturn result = kOSReturnError;
OSSharedPtr<OSDictionary> requestDict;
OSSharedPtr<IOUserServerCheckInToken> token;
if (!kextIdentifier || !serverName || !serverTag) {
result = kOSKextReturnInvalidArgument;
goto finish;
}
IORecursiveLockLock(sKextLock);
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogGeneralFlag,
"Requesting daemon launch for %s with serverName %s and tag %llu",
kextIdentifier->getCStringNoCopy(),
serverName->getCStringNoCopy(),
serverTag->unsigned64BitValue()
);
result = _OSKextCreateRequest(kKextRequestPredicateRequestDaemonLaunch, requestDict);
if (result != kOSReturnSuccess) {
goto finish;
}
token.reset(IOUserServerCheckInToken::create(), OSNoRetain);
if (!token) {
result = kOSKextReturnNoMemory;
goto finish;
}
if (!_OSKextSetRequestArgument(requestDict.get(),
kKextRequestArgumentBundleIdentifierKey, kextIdentifier) ||
!_OSKextSetRequestArgument(requestDict.get(),
kKextRequestArgumentDriverExtensionServerName, serverName) ||
!_OSKextSetRequestArgument(requestDict.get(),
kKextRequestArgumentDriverExtensionServerTag, serverTag) ||
!_OSKextSetRequestArgument(requestDict.get(),
kKextRequestArgumentCheckInToken, token.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
/* Only post the requests after all the other potential failure points
* have been passed.
*/
if (!sKernelRequests->setObject(requestDict.get())) {
result = kOSKextReturnNoMemory;
goto finish;
}
*checkInToken = token.detach();
OSKext::pingIOKitDaemon();
result = kOSReturnSuccess;
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::dequeueCallbackForRequestTag(
OSKextRequestTag requestTag,
OSSharedPtr<OSDictionary> &callbackRecordOut)
{
OSDictionary * callbackRecordOutRaw = NULL;
OSReturn result;
result = dequeueCallbackForRequestTag(requestTag,
&callbackRecordOutRaw);
if (kOSReturnSuccess == result) {
callbackRecordOut.reset(callbackRecordOutRaw, OSNoRetain);
}
return result;
}
OSReturn
OSKext::dequeueCallbackForRequestTag(
OSKextRequestTag requestTag,
OSDictionary ** callbackRecordOut)
{
OSReturn result = kOSReturnError;
OSSharedPtr<OSNumber> requestTagNum;
requestTagNum = OSNumber::withNumber((long long unsigned int)requestTag,
8 * sizeof(requestTag));
if (!requestTagNum) {
goto finish;
}
result = OSKext::dequeueCallbackForRequestTag(requestTagNum.get(),
callbackRecordOut);
finish:
return result;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::dequeueCallbackForRequestTag(
OSNumber * requestTagNum,
OSSharedPtr<OSDictionary> &callbackRecordOut)
{
OSDictionary * callbackRecordOutRaw = NULL;
OSReturn result;
result = dequeueCallbackForRequestTag(requestTagNum,
&callbackRecordOutRaw);
if (kOSReturnSuccess == result) {
callbackRecordOut.reset(callbackRecordOutRaw, OSNoRetain);
}
return result;
}
OSReturn
OSKext::dequeueCallbackForRequestTag(
OSNumber * requestTagNum,
OSDictionary ** callbackRecordOut)
{
OSReturn result = kOSKextReturnInvalidArgument;
OSDictionary * callbackRecord = NULL; // retain if matched!
OSNumber * callbackTagNum = NULL; // do not release
unsigned int count, i;
result = kOSReturnError;
count = sRequestCallbackRecords->getCount();
for (i = 0; i < count; i++) {
callbackRecord = OSDynamicCast(OSDictionary,
sRequestCallbackRecords->getObject(i));
if (!callbackRecord) {
goto finish;
}
/* If we don't find a tag, we basically have a leak here. Maybe
* we should just remove it.
*/
callbackTagNum = OSDynamicCast(OSNumber, _OSKextGetRequestArgument(
callbackRecord, kKextRequestArgumentRequestTagKey));
if (!callbackTagNum) {
goto finish;
}
/* We could be even more paranoid and check that all the incoming
* args match what's in the callback record.
*/
if (callbackTagNum->isEqualTo(requestTagNum)) {
if (callbackRecordOut) {
*callbackRecordOut = callbackRecord;
callbackRecord->retain();
}
sRequestCallbackRecords->removeObject(i);
result = kOSReturnSuccess;
goto finish;
}
}
result = kOSKextReturnNotFound;
finish:
return result;
}
/*********************************************************************
* Busy timeout triage
*********************************************************************/
/* static */
bool
OSKext::pendingIOKitDaemonRequests(void)
{
return sRequestCallbackRecords && sRequestCallbackRecords->getCount();
}
/*********************************************************************
* Acquires and releases sKextLock
*
* This function is designed to be called exactly once on boot by
* the IOKit management daemon, kernelmanagerd. It gathers all codeless
* kext and dext personalities, and then attempts to map a System
* (pageable) KC and an Auxiliary (aux) KC.
*
* Even if the pageable or aux KC fail to load - this function will
* not allow a second call. This avoids security issues where
* kernelmanagerd has been compromised or the pageable kc has been
* tampered with and the attacker attempts to re-load a malicious
* variant.
*
* Return: if a KC fails to load the return value will contain:
* kOSKextReturnKCLoadFailure. If the pageable KC fails,
* the return value will contain kOSKextReturnKCLoadFailureSystemKC.
* Similarly, if the aux kc load fails, the return value will
* contain kOSKextReturnKCLoadFailureAuxKC. The two values
* compose with each other and with kOSKextReturnKCLoadFailure.
*********************************************************************/
/* static */
OSReturn
OSKext::loadFileSetKexts(OSDictionary * requestDict __unused)
{
static bool daemon_ready = false;
OSReturn ret = kOSKextReturnInvalidArgument;
OSReturn kcerr = 0;
bool start_matching = false;
bool allow_fileset_load = !daemon_ready;
#if !(defined(__x86_64__) || defined(__i386__))
/* never allow KCs full of kexts on non-x86 machines */
allow_fileset_load = false;
#endif
/*
* Change with 70582300
*/
#if 0 || !defined(VM_MAPPED_KEXTS)
/*
* On platforms that don't support the SystemKC or a file-backed
* AuxKC, the kext receipt for 3rd party kexts loaded by the booter
* needs to be queried before we load any codeless kexts or release
* any 3rd party kexts to run. On platforms that support a file-backed
* AuxKC, this process is done via the kext audit mechanism.
*/
printf("KextLog: waiting for kext receipt to be queried.\n");
while (!IOServiceWaitForMatchingResource(kOSKextReceiptQueried, UINT64_MAX)) {
IOSleep(30);
}
#endif /* !VM_MAPPED_KEXTS */
/*
* Get the args from the request. Right now we need the file
* name for the pageable and the aux kext collection file sets.
*/
OSDictionary * requestArgs = NULL; // do not release
OSString * pageable_filepath = NULL; // do not release
OSString * aux_filepath = NULL; // do not release
OSArray * codeless_kexts = NULL; // do not release
kernel_mach_header_t *akc_mh = NULL;
requestArgs = OSDynamicCast(OSDictionary,
requestDict->getObject(kKextRequestArgumentsKey));
if (requestArgs == NULL) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"KextLog: No arguments in plist for loading fileset kext\n");
printf("KextLog: No arguments in plist for loading fileset kext\n");
return ret;
}
ret = kOSKextReturnDisabled;
IORecursiveLockLock(sKextLock);
if (!sLoadEnabled) {
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"KextLog: Kext loading is disabled (attempt to load KCs).");
IORecursiveLockUnlock(sKextLock);
return ret;
}
pageable_filepath = OSDynamicCast(OSString,
requestArgs->getObject(kKextRequestArgumentPageableKCFilename));
if (allow_fileset_load && pageable_filepath != NULL) {
printf("KextLog: Loading Pageable KC from file %s\n", pageable_filepath->getCStringNoCopy());
ret = OSKext::loadKCFileSet(pageable_filepath->getCStringNoCopy(), KCKindPageable);
if (ret) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"KextLog: loadKCFileSet for Pageable KC returned %d\n", ret);
printf("KextLog: loadKCFileSet for Pageable KC returned %d\n", ret);
ret = kOSKextReturnKCLoadFailure;
kcerr |= kOSKextReturnKCLoadFailureSystemKC;
goto try_auxkc;
}
/*
* Even if the AuxKC fails to load, we still want to send
* the System KC personalities to the catalog for matching
*/
start_matching = true;
} else if (pageable_filepath != NULL) {
OSKextLog(/* kext */ NULL, kOSKextLogBasicLevel | kOSKextLogIPCFlag,
"KextLog: ignoring Pageable KC load from %s\n", pageable_filepath->getCStringNoCopy());
ret = kOSKextReturnUnsupported;
}
try_auxkc:
akc_mh = (kernel_mach_header_t*)PE_get_kc_header(KCKindAuxiliary);
if (akc_mh) {
/*
* If we try to load a deferred AuxKC, then don't ever attempt
* a filesystem map of a file
*/
allow_fileset_load = false;
/*
* This function is only called once per boot, so we haven't
* yet loaded an AuxKC. If we have registered the AuxKC mach
* header, that means that the kext collection has been placed
* in memory for us by the booter, and is waiting for us to
* process it. Grab the deferred XML plist of info
* dictionaries and add all the kexts.
*/
OSSharedPtr<OSObject> parsedXML;
OSSharedPtr<OSData> loaded_kcUUID;
OSDictionary *infoDict;
parsedXML = consumeDeferredKextCollection(KCKindAuxiliary);
infoDict = OSDynamicCast(OSDictionary, parsedXML.get());
#if !defined(VM_MAPPED_KEXTS)
/*
* On platforms where we don't dynamically wire-down / page-in
* kext memory, we need to maintain the invariant that if the
* AuxKC in memory does not contain a kext receipt, then we
* should not load any of the kexts.
*/
size_t receipt_sz = 0;
if (getsectdatafromheader(akc_mh, kReceiptInfoSegment, kAuxKCReceiptSection, &receipt_sz) == NULL || receipt_sz == 0) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"KextLog: WARNING: Failed to load AuxKC from memory: missing receipt");
ret = kOSKextReturnKCLoadFailure;
goto try_codeless;
}
#endif
if (infoDict) {
bool added;
printf("KextLog: Adding kexts from in-memory AuxKC\n");
added = OSKext::addKextsFromKextCollection(akc_mh, infoDict,
kPrelinkTextSegment, loaded_kcUUID, KCKindAuxiliary);
if (!loaded_kcUUID) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"KextLog: WARNING: did not find UUID in deferred Aux KC!");
} else if (!added) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"KextLog: WARNING: Failed to load AuxKC from memory.");
}
/* only return success if the pageable load (above) was successful */
if (ret != kOSKextReturnKCLoadFailure) {
ret = kOSReturnSuccess;
}
/* the registration of the AuxKC parsed out the KC's UUID already */
} else {
if (daemon_ready) {
/*
* Complain, but don't return an error if this isn't the first time the
* IOKit daemon is checking in. If the daemon ever restarts, we will
* hit this case because we've already consumed the deferred personalities.
* We return success here so that a call to this function from a restarted
* daemon with no codeless kexts will succeed.
*/
OSKextLog(/* kext */ NULL, kOSKextLogBasicLevel | kOSKextLogIPCFlag,
"KextLog: can't re-parse deferred AuxKC personalities on IOKit daemon restart");
if (ret != kOSKextReturnKCLoadFailure) {
ret = kOSReturnSuccess;
}
} else {
/* this is a real error case */
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"KextLog: ERROR loading deferred AuxKC: PRELINK_INFO wasn't an OSDictionary");
printf("KextLog: ERROR loading deferred AuxKC: PRELINK_INFO wasn't an OSDictionary\n");
ret = kOSKextReturnKCLoadFailure;
kcerr |= kOSKextReturnKCLoadFailureAuxKC;
}
}
}
aux_filepath = OSDynamicCast(OSString,
requestArgs->getObject(kKextRequestArgumentAuxKCFilename));
if (allow_fileset_load && aux_filepath != NULL) {
printf("KextLog: Loading Aux KC from file %s\n", aux_filepath->getCStringNoCopy());
ret = OSKext::loadKCFileSet(aux_filepath->getCStringNoCopy(), KCKindAuxiliary);
if (ret) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"KextLog: loadKCFileSet for Aux KC returned %d\n", ret);
printf("KextLog: loadKCFileSet for Aux KC returned %d\n", ret);
ret = kOSKextReturnKCLoadFailure;
kcerr |= kOSKextReturnKCLoadFailureAuxKC;
goto try_codeless;
}
start_matching = true;
} else if (aux_filepath != NULL) {
OSKextLog(/* kext */ NULL, kOSKextLogBasicLevel | kOSKextLogIPCFlag,
"KextLog: Ignoring AuxKC load from %s\n", aux_filepath->getCStringNoCopy());
if (ret != kOSKextReturnKCLoadFailure) {
ret = kOSKextReturnUnsupported;
}
}
try_codeless:
/*
* Load codeless kexts last so that there is no possibilty of a
* codeless kext bundle ID preventing a kext in the system KC from
* loading
*/
codeless_kexts = OSDynamicCast(OSArray,
requestArgs->getObject(kKextRequestArgumentCodelessPersonalities));
if (codeless_kexts != NULL) {
uint32_t count = codeless_kexts->getCount();
OSKextLog(NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"KextLog: loading %d codeless kexts/dexts", count);
for (uint32_t i = 0; i < count; i++) {
OSDictionary *infoDict;
infoDict = OSDynamicCast(OSDictionary,
codeless_kexts->getObject(i));
if (!infoDict) {
continue;
}
// instantiate a new kext, and don't hold a reference
// (the kext subsystem will hold one implicitly)
OSKext::withCodelessInfo(infoDict);
}
/* ignore errors that are not KC load failures */
if (ret != kOSKextReturnKCLoadFailure) {
ret = kOSReturnSuccess;
}
start_matching = true;
}
/* send personalities to the IOCatalog once */
if (ret == kOSReturnSuccess || start_matching || sOSKextWasResetAfterUserspaceReboot) {
OSKext::sendAllKextPersonalitiesToCatalog(true);
/*
* This request necessarily came from the IOKit daemon (kernelmanagerd), so mark
* things as active and start all the delayed matching: the
* dext and codeless kext personalities should have all been
* delivered via this one call.
*/
if (!daemon_ready) {
OSKext::setIOKitDaemonActive();
OSKext::setDeferredLoadSucceeded(TRUE);
IOService::iokitDaemonLaunched();
}
if (sOSKextWasResetAfterUserspaceReboot) {
sOSKextWasResetAfterUserspaceReboot = false;
OSKext::setIOKitDaemonActive();
IOService::startDeferredMatches();
}
}
if (ret == kOSKextReturnKCLoadFailure) {
ret |= kcerr;
}
/*
* Only allow this function to attempt to load the pageable and
* aux KCs once per boot.
*/
daemon_ready = true;
IORecursiveLockUnlock(sKextLock);
return ret;
}
OSReturn
OSKext::resetMutableSegments(void)
{
kernel_segment_command_t *seg = NULL;
kernel_mach_header_t *k_mh = (kernel_mach_header_t *)kmod_info->address;
u_int index = 0;
OSKextSavedMutableSegment *savedSegment = NULL;
uintptr_t kext_slide = PE_get_kc_slide(kc_type);
OSReturn err;
if (!savedMutableSegments) {
OSKextLog(this, kOSKextLogErrorLevel | kOSKextLogLoadFlag,
"Kext %s cannot be reset, mutable segments were not saved.", getIdentifierCString());
err = kOSKextReturnInternalError;
goto finish;
}
for (seg = firstsegfromheader(k_mh), index = 0; seg; seg = nextsegfromheader(k_mh, seg)) {
if (!segmentIsMutable(seg)) {
continue;
}
uint64_t unslid_vmaddr = seg->vmaddr - kext_slide;
uint64_t vmsize = seg->vmsize;
err = kOSKextReturnInternalError;
for (index = 0; index < savedMutableSegments->getCount(); index++) {
savedSegment = OSDynamicCast(OSKextSavedMutableSegment, savedMutableSegments->getObject(index));
assert(savedSegment);
if (savedSegment->getVMAddr() == seg->vmaddr && savedSegment->getVMSize() == seg->vmsize) {
OSKextLog(this, kOSKextLogDebugLevel | kOSKextLogLoadFlag,
"Resetting kext %s, mutable segment %.*s %llx->%llx.", getIdentifierCString(), (int)strnlen(seg->segname, sizeof(seg->segname)), seg->segname, unslid_vmaddr, unslid_vmaddr + vmsize - 1);
err = savedSegment->restoreContents(seg);
if (err != kOSReturnSuccess) {
panic("Kext %s cannot be reset, mutable segment %llx->%llx could not be restored.", getIdentifierCString(), unslid_vmaddr, unslid_vmaddr + vmsize - 1);
}
}
}
if (err != kOSReturnSuccess) {
panic("Kext %s cannot be reset, could not find saved mutable segment for %llx->%llx.", getIdentifierCString(), unslid_vmaddr, unslid_vmaddr + vmsize - 1);
}
}
err = kOSReturnSuccess;
finish:
return err;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::loadKCFileSet(
const char *filepath,
kc_kind_t type)
{
#if VM_MAPPED_KEXTS
/* we only need to load filesets on systems that support VM_MAPPED kexts */
OSReturn err;
struct vnode *vp = NULL;
void *fileset_control;
off_t fsize;
bool pageable = (type == KCKindPageable);
if ((pageable && pageableKCloaded) ||
(!pageable && auxKCloaded)) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"KC FileSet of type %s is already loaded", (pageable ? "Pageable" : "Aux"));
return kOSKextReturnInvalidArgument;
}
/* Do not allow AuxKC to load if Pageable KC is not loaded */
if (!pageable && !pageableKCloaded) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"Trying to load the Aux KC without loading the Pageable KC");
return kOSKextReturnInvalidArgument;
}
fileset_control = ubc_getobject_from_filename(filepath, &vp, &fsize);
if (fileset_control == NULL) {
printf("Could not get memory control object for file %s", filepath);
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"Could not get memory control object for file %s", filepath);
return kOSKextReturnInvalidArgument;
}
if (vp == NULL) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"Could not find vnode for file %s", filepath);
return kOSKextReturnInvalidArgument;
}
kernel_mach_header_t *mh = NULL;
uintptr_t slide = 0;
#if CONFIG_CSR
/*
* When SIP is enabled, the KC we map must be SIP-protected
*/
if (csr_check(CSR_ALLOW_UNRESTRICTED_FS) != 0) {
struct vnode_attr va;
int error;
VATTR_INIT(&va);
VATTR_WANTED(&va, va_flags);
error = vnode_getattr(vp, &va, vfs_context_current());
if (error) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"vnode_getattr(%s) failed (error=%d)", filepath, error);
err = kOSKextReturnInternalError;
goto finish;
}
if (!(va.va_flags & SF_RESTRICTED)) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"Path to KC '%s' is not SIP-protected", filepath);
err = kOSKextReturnInvalidArgument;
goto finish;
}
}
#endif
err = OSKext::mapKCFileSet(fileset_control, (vm_size_t)fsize, &mh, 0, &slide, pageable, NULL);
if (err) {
printf("KextLog: mapKCFileSet returned %d\n", err);
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"mapKCFileSet returned %d\n", err);
err = kOSKextReturnInvalidArgument;
}
#if CONFIG_CSR
finish:
#endif
/* Drop the vnode ref returned by ubc_getobject_from_filename if mapKCFileSet failed */
assert(vp != NULL);
if (err == kOSReturnSuccess) {
PE_set_kc_vp(type, vp);
if (pageable) {
pageableKCloaded = true;
} else {
auxKCloaded = true;
}
} else {
vnode_put(vp);
}
return err;
#else
(void)filepath;
(void)type;
return kOSKextReturnUnsupported;
#endif // VM_MAPPED_KEXTS
}
#if defined(__x86_64__) || defined(__i386__)
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::mapKCFileSet(
void *control,
vm_size_t fsize,
kernel_mach_header_t **mhp,
off_t file_offset,
uintptr_t *slidep,
bool pageable,
void *map_entry_list)
{
bool fileset_load = false;
kern_return_t ret;
OSReturn err;
kernel_section_t *infoPlistSection = NULL;
OSDictionary *infoDict = NULL;
OSSharedPtr<OSObject> parsedXML;
OSSharedPtr<OSString> errorString;
OSSharedPtr<OSData> loaded_kcUUID;
/* Check if initial load for file set */
if (*mhp == NULL) {
fileset_load = true;
/* Get a page aligned address from kext map to map the file */
vm_map_offset_t pagealigned_addr = get_address_from_kext_map(fsize);
if (pagealigned_addr == 0) {
return kOSKextReturnNoMemory;
}
*mhp = (kernel_mach_header_t *)pagealigned_addr;
/* Allocate memory for bailout mechanism */
map_entry_list = allocate_kcfileset_map_entry_list();
if (map_entry_list == NULL) {
return kOSKextReturnNoMemory;
}
}
uintptr_t *slideptr = fileset_load ? slidep : NULL;
err = mapKCTextSegment(control, mhp, file_offset, slideptr, map_entry_list);
/* mhp and slideptr are updated by mapKCTextSegment */
if (err) {
if (fileset_load) {
deallocate_kcfileset_map_entry_list_and_unmap_entries(map_entry_list, TRUE, pageable);
}
return err;
}
/* Initialize the kc header globals */
if (fileset_load) {
if (pageable) {
PE_set_kc_header(KCKindPageable, *mhp, *slidep);
} else {
PE_set_kc_header(KCKindAuxiliary, *mhp, *slidep);
}
}
/* Iterate through all the segments and map necessary segments */
struct load_command *lcp = (struct load_command *) (*mhp + 1);
for (unsigned int i = 0; i < (*mhp)->ncmds; i++, lcp = (struct load_command *)((uintptr_t)lcp + lcp->cmdsize)) {
vm_map_offset_t start;
kernel_mach_header_t *k_mh = NULL;
kernel_segment_command_t * seg = NULL;
struct fileset_entry_command *fse = NULL;
if (lcp->cmd == LC_SEGMENT_KERNEL) {
seg = (kernel_segment_command_t *)lcp;
start = ((uintptr_t)(seg->vmaddr)) + *slidep;
} else if (lcp->cmd == LC_FILESET_ENTRY) {
fse = (struct fileset_entry_command *)lcp;
k_mh = (kernel_mach_header_t *)(((uintptr_t)(fse->vmaddr)) + *slidep);
/* Map the segments of the mach-o binary */
err = OSKext::mapKCFileSet(control, 0, &k_mh, fse->fileoff, slidep, pageable, map_entry_list);
if (err) {
deallocate_kcfileset_map_entry_list_and_unmap_entries(map_entry_list, TRUE, pageable);
return kOSKextReturnInvalidArgument;
}
continue;
} else if (lcp->cmd == LC_DYLD_CHAINED_FIXUPS) {
/* Check if the Aux KC is built pageable style */
if (!pageable && !fileset_load && !auxKCloaded) {
resetAuxKCSegmentOnUnload = true;
}
continue;
} else {
continue;
}
if (fileset_load) {
if (seg->vmsize == 0) {
continue;
}
/* Only map __PRELINK_INFO, __BRANCH_STUBS, __BRANCH_GOTS and __LINKEDIT sections */
if (strncmp(seg->segname, kPrelinkInfoSegment, sizeof(seg->segname)) != 0 &&
strncmp(seg->segname, kKCBranchStubs, sizeof(seg->segname)) != 0 &&
strncmp(seg->segname, kKCBranchGots, sizeof(seg->segname)) != 0 &&
strncmp(seg->segname, SEG_LINKEDIT, sizeof(seg->segname)) != 0) {
continue;
}
} else {
if (seg->vmsize == 0) {
continue;
}
/* Skip the __LINKEDIT, __LINKINFO and __TEXT segments */
if (strncmp(seg->segname, SEG_LINKEDIT, sizeof(seg->segname)) == 0 ||
strncmp(seg->segname, SEG_LINKINFO, sizeof(seg->segname)) == 0 ||
strncmp(seg->segname, SEG_TEXT, sizeof(seg->segname)) == 0) {
continue;
}
}
ret = vm_map_kcfileset_segment(
&start, seg->vmsize,
(memory_object_control_t)control, seg->fileoff, seg->maxprot);
if (ret != KERN_SUCCESS) {
if (fileset_load) {
deallocate_kcfileset_map_entry_list_and_unmap_entries(map_entry_list, TRUE, pageable);
}
return kOSKextReturnInvalidArgument;
}
add_kcfileset_map_entry(map_entry_list, start, seg->vmsize);
}
/* Return if regular mach-o */
if (!fileset_load) {
return 0;
}
/*
* Fixup for the Pageable KC and the Aux KC is done by
* i386_slide_kext_collection_mh_addrs, but it differs in
* following ways:
*
* PageableKC: Fixup only __BRANCH_STUBS segment and top level load commands.
* The fixup of kext segments and kext load commands are done at kext
* load time by calling i386_slide_individual_kext.
*
* AuxKC old style: Fixup all the segments and all the load commands.
*
* AuxKC pageable style: Same as the Pageable KC.
*/
bool adjust_mach_header = (pageable ? true : ((resetAuxKCSegmentOnUnload) ? true : false));
ret = i386_slide_kext_collection_mh_addrs(*mhp, *slidep, adjust_mach_header);
if (ret != KERN_SUCCESS) {
deallocate_kcfileset_map_entry_list_and_unmap_entries(map_entry_list, TRUE, pageable);
return kOSKextReturnInvalidArgument;
}
/* Get the prelink info dictionary */
infoPlistSection = getsectbynamefromheader(*mhp, kPrelinkInfoSegment, kPrelinkInfoSection);
parsedXML = OSUnserializeXML((const char *)infoPlistSection->addr, errorString);
if (parsedXML) {
infoDict = OSDynamicCast(OSDictionary, parsedXML.get());
}
if (!infoDict) {
const char *errorCString = "(unknown error)";
if (errorString && errorString->getCStringNoCopy()) {
errorCString = errorString->getCStringNoCopy();
} else if (parsedXML) {
errorCString = "not a dictionary";
}
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"Error unserializing kext info plist section: %s.", errorCString);
deallocate_kcfileset_map_entry_list_and_unmap_entries(map_entry_list, TRUE, pageable);
return kOSKextReturnInvalidArgument;
}
/* Validate that the Kext Collection is prelinked to the loaded KC */
err = OSKext::validateKCFileSetUUID(infoDict, pageable ? KCKindPageable : KCKindAuxiliary);
if (err) {
deallocate_kcfileset_map_entry_list_and_unmap_entries(map_entry_list, TRUE, pageable);
return kOSKextReturnInvalidArgument;
}
/* Set Protection of Segments */
OSKext::protectKCFileSet(*mhp, pageable ? KCKindPageable : KCKindAuxiliary);
OSKext::addKextsFromKextCollection(*mhp,
infoDict, kPrelinkTextSegment,
loaded_kcUUID, pageable ? KCKindPageable : KCKindAuxiliary);
/* Copy in the KC UUID */
if (!loaded_kcUUID) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"WARNING: did not find UUID in prelinked %s KC!", pageable ? "Pageable" : "Aux");
} else if (pageable) {
pageablekc_uuid_valid = TRUE;
memcpy((void *)&pageablekc_uuid, (const void *)loaded_kcUUID->getBytesNoCopy(), loaded_kcUUID->getLength());
uuid_unparse_upper(pageablekc_uuid, pageablekc_uuid_string);
} else {
auxkc_uuid_valid = TRUE;
memcpy((void *)&auxkc_uuid, (const void *)loaded_kcUUID->getBytesNoCopy(), loaded_kcUUID->getLength());
uuid_unparse_upper(auxkc_uuid, auxkc_uuid_string);
}
deallocate_kcfileset_map_entry_list_and_unmap_entries(map_entry_list, FALSE, pageable);
return 0;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::mapKCTextSegment(
void *control,
kernel_mach_header_t **mhp,
off_t file_offset,
uintptr_t *slidep,
void *map_entry_list)
{
kern_return_t ret;
vm_map_offset_t mach_header_map_size = vm_map_round_page(sizeof(kernel_mach_header_t),
PAGE_MASK);
vm_map_offset_t load_command_map_size = 0;
kernel_mach_header_t *base_mh = *mhp;
/* Map the mach header at start of fileset for now (vmaddr = 0) */
ret = vm_map_kcfileset_segment(
(vm_map_offset_t *)&base_mh, mach_header_map_size,
(memory_object_control_t)control, file_offset, (VM_PROT_READ | VM_PROT_WRITE));
if (ret != KERN_SUCCESS) {
printf("Kext Log: mapKCTextSegment failed to map mach header of fileset %x", ret);
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"Failed to map mach header of kc fileset with error %d", ret);
return kOSKextReturnInvalidArgument;
}
if (slidep) {
/* Verify that it's an MH_FILESET */
if (base_mh->filetype != MH_FILESET) {
printf("Kext Log: mapKCTextSegment mach header filetype"
" is not an MH_FILESET, it is %x", base_mh->filetype);
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"mapKCTextSegment mach header filetype is not an MH_FILESET, it is %x", base_mh->filetype);
/* Unmap the mach header */
vm_unmap_kcfileset_segment((vm_map_offset_t *)&base_mh, mach_header_map_size);
return kOSKextReturnInvalidArgument;
}
}
/* Map the remaining pages of load commands */
if (base_mh->sizeofcmds > mach_header_map_size) {
vm_map_offset_t load_command_addr = ((vm_map_offset_t)base_mh) + mach_header_map_size;
load_command_map_size = base_mh->sizeofcmds - mach_header_map_size;
/* Map the load commands */
ret = vm_map_kcfileset_segment(
&load_command_addr, load_command_map_size,
(memory_object_control_t)control, file_offset + mach_header_map_size,
(VM_PROT_READ | VM_PROT_WRITE));
if (ret != KERN_SUCCESS) {
printf("KextLog: mapKCTextSegment failed to map load commands of fileset %x", ret);
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"Failed to map load commands of kc fileset with error %d", ret);
/* Unmap the mach header */
vm_unmap_kcfileset_segment((vm_map_offset_t *)&base_mh, mach_header_map_size);
return kOSKextReturnInvalidArgument;
}
}
kernel_segment_command_t *text_seg;
text_seg = getsegbynamefromheader((kernel_mach_header_t *)base_mh, SEG_TEXT);
/* Calculate the slide and vm addr of mach header */
if (slidep) {
*mhp = (kernel_mach_header_t *)((uintptr_t)base_mh + text_seg->vmaddr);
*slidep = ((uintptr_t)*mhp) - text_seg->vmaddr;
}
/* Cache the text segment size and file offset before unmapping */
vm_map_offset_t text_segment_size = text_seg->vmsize;
vm_object_offset_t text_segment_fileoff = text_seg->fileoff;
vm_prot_t text_maxprot = text_seg->maxprot;
/* Unmap the first page and loadcommands and map the text segment */
ret = vm_unmap_kcfileset_segment((vm_map_offset_t *)&base_mh, mach_header_map_size);
assert(ret == KERN_SUCCESS);
if (load_command_map_size) {
vm_map_offset_t load_command_addr = ((vm_map_offset_t)base_mh) + mach_header_map_size;
ret = vm_unmap_kcfileset_segment(&load_command_addr, load_command_map_size);
assert(ret == KERN_SUCCESS);
}
/* Map the text segment at actual vm addr specified in fileset */
ret = vm_map_kcfileset_segment((vm_map_offset_t *)mhp, text_segment_size,
(memory_object_control_t)control, text_segment_fileoff, text_maxprot);
if (ret != KERN_SUCCESS) {
OSKextLog(/* kext */ NULL, kOSKextLogDebugLevel | kOSKextLogIPCFlag,
"Failed to map Text segment of kc fileset with error %d", ret);
return kOSKextReturnInvalidArgument;
}
add_kcfileset_map_entry(map_entry_list, (vm_map_offset_t)*mhp, text_segment_size);
return 0;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::protectKCFileSet(
kernel_mach_header_t *mh,
kc_kind_t type)
{
vm_map_t kext_map = g_kext_map;
kernel_segment_command_t * seg = NULL;
vm_map_offset_t start = 0;
vm_map_offset_t end = 0;
OSReturn ret = 0;
/* Set VM permissions */
seg = firstsegfromheader((kernel_mach_header_t *)mh);
while (seg) {
start = round_page(seg->vmaddr);
end = trunc_page(seg->vmaddr + seg->vmsize);
/*
* Wire down and protect __TEXT, __BRANCH_STUBS and __BRANCH_GOTS
* for the Pageable KC and the Aux KC, wire down and protect __LINKEDIT
* for the Aux KC as well.
*/
if (strncmp(seg->segname, kKCBranchGots, sizeof(seg->segname)) == 0 ||
strncmp(seg->segname, kKCBranchStubs, sizeof(seg->segname)) == 0 ||
strncmp(seg->segname, SEG_TEXT, sizeof(seg->segname)) == 0 ||
(type == KCKindAuxiliary && !resetAuxKCSegmentOnUnload &&
strncmp(seg->segname, SEG_LINKEDIT, sizeof(seg->segname)) == 0)) {
ret = OSKext_protect((kernel_mach_header_t *)mh,
kext_map, start, end, seg->maxprot, TRUE, type);
if (ret != KERN_SUCCESS) {
printf("OSKext protect failed with error %d", ret);
return kOSKextReturnInvalidArgument;
}
ret = OSKext_protect((kernel_mach_header_t *)mh,
kext_map, start, end, seg->initprot, FALSE, type);
if (ret != KERN_SUCCESS) {
printf("OSKext protect failed with error %d", ret);
return kOSKextReturnInvalidArgument;
}
ret = OSKext_wire((kernel_mach_header_t *)mh,
kext_map, start, end, seg->initprot, FALSE, type);
if (ret != KERN_SUCCESS) {
printf("OSKext wire failed with error %d", ret);
return kOSKextReturnInvalidArgument;
}
}
seg = nextsegfromheader((kernel_mach_header_t *) mh, seg);
}
return 0;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
void
OSKext::freeKCFileSetcontrol(void)
{
PE_reset_all_kc_vp();
}
/*********************************************************************
* Assumes sKextLock is held.
*
* resetKCFileSetSegments: Kext start function expects data segment to
* be pristine on every load, unmap the dirty segments on unload and
* remap them from FileSet on disk. Remap all segments of kext since
* fixups are done per kext and not per segment.
*********************************************************************/
OSReturn
OSKext::resetKCFileSetSegments(void)
{
kernel_segment_command_t *seg = NULL;
kernel_segment_command_t *text_seg;
uint32_t text_fileoff;
kernel_mach_header_t *k_mh = NULL;
uintptr_t slide;
struct vnode *vp = NULL;
void *fileset_control = NULL;
bool pageable = (kc_type == KCKindPageable);
OSReturn err;
kern_return_t kr;
/* Check the vnode reference is still available */
vp = (struct vnode *)PE_get_kc_vp(kc_type);
if (vp == NULL) {
OSKextLog(this, kOSKextLogProgressLevel | kOSKextLogLoadFlag,
"Kext %s could not be reset, since reboot released the vnode ref", getIdentifierCString());
return kOSKextReturnInternalError;
}
fileset_control = ubc_getobject(vp, 0);
assert(fileset_control != NULL);
OSKextLog(this, kOSKextLogProgressLevel | kOSKextLogLoadFlag,
"Kext %s resetting all segments", getIdentifierCString());
k_mh = (kernel_mach_header_t *)kmod_info->address;
text_seg = getsegbynamefromheader((kernel_mach_header_t *)kmod_info->address, SEG_TEXT);
text_fileoff = text_seg->fileoff;
slide = PE_get_kc_slide(kc_type);
seg = firstsegfromheader((kernel_mach_header_t *)k_mh);
while (seg) {
if (seg->vmsize == 0) {
seg = nextsegfromheader((kernel_mach_header_t *) k_mh, seg);
continue;
}
/* Skip the __LINKEDIT, __LINKINFO and __TEXT segments */
if (strncmp(seg->segname, SEG_LINKEDIT, sizeof(seg->segname)) == 0 ||
strncmp(seg->segname, SEG_LINKINFO, sizeof(seg->segname)) == 0 ||
strncmp(seg->segname, SEG_TEXT, sizeof(seg->segname)) == 0) {
seg = nextsegfromheader((kernel_mach_header_t *) k_mh, seg);
continue;
}
kr = vm_unmap_kcfileset_segment(&seg->vmaddr, seg->vmsize);
assert(kr == KERN_SUCCESS);
seg = nextsegfromheader((kernel_mach_header_t *) k_mh, seg);
}
/* Unmap the text segment */
kr = vm_unmap_kcfileset_segment(&text_seg->vmaddr, text_seg->vmsize);
assert(kr == KERN_SUCCESS);
/* Map all the segments of the kext */
err = OSKext::mapKCFileSet(fileset_control, 0, &k_mh, text_fileoff, &slide, pageable, NULL);
if (err) {
panic("Could not reset segments of a mapped kext, error %x", err);
}
/* Update address in kmod_info, since it has been reset */
if (kmod_info->address) {
kmod_info->address = (((uintptr_t)(kmod_info->address)) + slide);
}
return 0;
}
/*********************************************************************
* Mechanism to track all segment mapping while mapping KC fileset.
*********************************************************************/
struct kcfileset_map_entry {
vm_map_offset_t me_start;
vm_map_offset_t me_size;
};
struct kcfileset_map_entry_list {
int kme_list_count;
int kme_list_index;
struct kcfileset_map_entry kme_list[];
};
#define KCFILESET_MAP_ENTRY_MAX (16380)
static void *
allocate_kcfileset_map_entry_list(void)
{
struct kcfileset_map_entry_list *entry_list;
entry_list = (struct kcfileset_map_entry_list *)kalloc(sizeof(struct kcfileset_map_entry_list) +
(sizeof(struct kcfileset_map_entry) * KCFILESET_MAP_ENTRY_MAX));
entry_list->kme_list_count = KCFILESET_MAP_ENTRY_MAX;
entry_list->kme_list_index = 0;
return entry_list;
}
static void
add_kcfileset_map_entry(
void *map_entry_list,
vm_map_offset_t start,
vm_map_offset_t size)
{
if (map_entry_list == NULL) {
return;
}
struct kcfileset_map_entry_list *entry_list = (struct kcfileset_map_entry_list *)map_entry_list;
if (entry_list->kme_list_index >= entry_list->kme_list_count) {
panic("Ran out of map kc fileset list\n");
}
entry_list->kme_list[entry_list->kme_list_index].me_start = start;
entry_list->kme_list[entry_list->kme_list_index].me_size = size;
entry_list->kme_list_index++;
}
static void
deallocate_kcfileset_map_entry_list_and_unmap_entries(
void *map_entry_list,
boolean_t unmap_entries,
bool pageable)
{
struct kcfileset_map_entry_list *entry_list = (struct kcfileset_map_entry_list *)map_entry_list;
if (unmap_entries) {
for (int i = 0; i < entry_list->kme_list_index; i++) {
kern_return_t ret;
ret = vm_unmap_kcfileset_segment(
&(entry_list->kme_list[i].me_start),
entry_list->kme_list[i].me_size);
assert(ret == KERN_SUCCESS);
}
PE_reset_kc_header(pageable ? KCKindPageable : KCKindAuxiliary);
}
kfree(entry_list, sizeof(struct kcfileset_map_entry_list) +
(sizeof(struct kcfileset_map_entry) * KCFILESET_MAP_ENTRY_MAX));
}
/*********************************************************************
* Mechanism to map kext segment.
*********************************************************************/
kern_return_t
vm_map_kcfileset_segment(
vm_map_offset_t *start,
vm_map_offset_t size,
void *control,
vm_object_offset_t fileoffset,
vm_prot_t max_prot)
{
vm_map_kernel_flags_t vmk_flags;
vmk_flags.vmkf_no_copy_on_read = 1;
vmk_flags.vmkf_cs_enforcement = 0;
vmk_flags.vmkf_cs_enforcement_override = 1;
kern_return_t ret;
/* Add Write to max prot to allow fixups */
max_prot = max_prot | VM_PROT_WRITE;
/*
* Map the segments from file as COPY mappings to
* make sure changes on disk to the file does not affect
* mapped segments.
*/
ret = vm_map_enter_mem_object_control(
g_kext_map,
start,
size,
(mach_vm_offset_t)0,
VM_FLAGS_FIXED,
vmk_flags,
VM_KERN_MEMORY_OSKEXT,
(memory_object_control_t)control,
fileoffset,
TRUE, /* copy */
(VM_PROT_READ | VM_PROT_WRITE), max_prot,
VM_INHERIT_NONE);
return ret;
}
kern_return_t
vm_unmap_kcfileset_segment(
vm_map_offset_t *start,
vm_map_offset_t size)
{
return mach_vm_deallocate(g_kext_map, *start, size);
}
#endif //(__x86_64__) || defined(__i386__)
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::validateKCFileSetUUID(
OSDictionary *infoDict,
kc_kind_t type)
{
OSReturn ret = kOSReturnSuccess;
if (!kernelcache_uuid_valid) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"validateKCFileSetUUID Boot KC UUID was not set at boot.");
ret = kOSKextReturnInvalidArgument;
goto finish;
}
ret = OSKext::validateKCUUIDfromPrelinkInfo(&kernelcache_uuid, type, infoDict, kPrelinkInfoBootKCIDKey);
if (ret != 0) {
goto finish;
}
#if defined(__x86_64__) || defined(__i386__)
/* Check if the Aux KC is prelinked to correct Pageable KC */
if (type == KCKindAuxiliary) {
if (!pageablekc_uuid_valid) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"validateKCFileSetUUID Pageable KC UUID was not set while loading Pageable KC.");
ret = kOSKextReturnInvalidArgument;
goto finish;
}
ret = OSKext::validateKCUUIDfromPrelinkInfo(&pageablekc_uuid, type, infoDict, kPrelinkInfoPageableKCIDKey);
if (ret != 0) {
goto finish;
}
}
#endif //(__x86_64__) || defined(__i386__)
printf("KextLog: Collection UUID matches with loaded KCs.\n");
finish:
return ret;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::validateKCUUIDfromPrelinkInfo(
uuid_t *loaded_kcuuid,
kc_kind_t type,
OSDictionary *infoDict,
const char *uuid_key)
{
/* extract the UUID from the dictionary */
OSData *prelinkinfoKCUUID = OSDynamicCast(OSData, infoDict->getObject(uuid_key));
if (!prelinkinfoKCUUID) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"validateKCUUID Info plist does not contain %s KC UUID key.", uuid_key);
return kOSKextReturnInvalidArgument;
}
if (prelinkinfoKCUUID->getLength() != sizeof(uuid_t)) {
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"validateKCUUID %s KC UUID has wrong length: %d.", uuid_key, prelinkinfoKCUUID->getLength());
return kOSKextReturnInvalidArgument;
}
if (memcmp((void *)loaded_kcuuid, (const void *)prelinkinfoKCUUID->getBytesNoCopy(),
prelinkinfoKCUUID->getLength())) {
OSData *info_dict_uuid;
uuid_string_t info_dict_uuid_str = {};
uuid_string_t expected_uuid_str = {};
uuid_string_t given_uuid_str = {};
uuid_t given_uuid;
/* extract the KC UUID from the dictionary */
info_dict_uuid = OSDynamicCast(OSData, infoDict->getObject(kPrelinkInfoKCIDKey));
if (info_dict_uuid && info_dict_uuid->getLength() == sizeof(uuid_t)) {
uuid_t tmp_uuid;
memcpy(tmp_uuid, (const void *)info_dict_uuid->getBytesNoCopy(), sizeof(tmp_uuid));
uuid_unparse(tmp_uuid, info_dict_uuid_str);
}
uuid_unparse(*loaded_kcuuid, expected_uuid_str);
memcpy(given_uuid, (const void *)prelinkinfoKCUUID->getBytesNoCopy(), sizeof(given_uuid));
uuid_unparse(given_uuid, given_uuid_str);
printf("KextLog: ERROR: UUID from key:%s %s != expected %s (KC UUID: %s)\n", uuid_key,
given_uuid_str, expected_uuid_str, info_dict_uuid_str);
OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
"KextLog: ERROR: UUID from key:%s %s != expected %s (KC UUID: %s)\n", uuid_key,
given_uuid_str, expected_uuid_str, info_dict_uuid_str);
if (type == KCKindPageable && sPanicOnKCMismatch) {
panic("System KC UUID %s linked against %s, but %s is loaded",
info_dict_uuid_str, given_uuid_str, expected_uuid_str);
}
return kOSKextReturnInvalidArgument;
}
return 0;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::dispatchResource(OSDictionary * requestDict)
{
OSReturn result = kOSReturnError;
OSSharedPtr<OSDictionary> callbackRecord;
OSNumber * requestTag = NULL; // do not release
OSNumber * requestResult = NULL; // do not release
OSData * dataObj = NULL; // do not release
uint32_t dataLength = 0;
const void * dataPtr = NULL; // do not free
OSData * callbackWrapper = NULL; // do not release
OSKextRequestResourceCallback callback = NULL;
OSData * contextWrapper = NULL; // do not release
void * context = NULL; // do not free
OSSharedPtr<OSKext> callbackKext;
/* Get the args from the request. Right now we need the tag
* to look up the callback record, and the result for invoking the callback.
*/
requestTag = OSDynamicCast(OSNumber, _OSKextGetRequestArgument(requestDict,
kKextRequestArgumentRequestTagKey));
requestResult = OSDynamicCast(OSNumber, _OSKextGetRequestArgument(requestDict,
kKextRequestArgumentResultKey));
if (!requestTag || !requestResult) {
result = kOSKextReturnInvalidArgument;
goto finish;
}
/* Look for a callback record matching this request's tag.
*/
result = dequeueCallbackForRequestTag(requestTag, callbackRecord);
if (result != kOSReturnSuccess) {
goto finish;
}
/*****
* Get the context pointer of the callback record (if there is one).
*/
contextWrapper = OSDynamicCast(OSData, _OSKextGetRequestArgument(callbackRecord.get(),
kKextRequestArgumentContextKey));
context = _OSKextExtractPointer(contextWrapper);
if (contextWrapper && !context) {
goto finish;
}
callbackWrapper = OSDynamicCast(OSData,
_OSKextGetRequestArgument(callbackRecord.get(),
kKextRequestArgumentCallbackKey));
callback = _OSKextExtractCallbackPointer(callbackWrapper);
if (!callback) {
goto finish;
}
/* Check for a data obj. We might not have one and that's ok, that means
* we didn't find the requested resource, and we still have to tell the
* caller that via the callback.
*/
dataObj = OSDynamicCast(OSData, _OSKextGetRequestArgument(requestDict,
kKextRequestArgumentValueKey));
if (dataObj) {
dataPtr = dataObj->getBytesNoCopy();
dataLength = dataObj->getLength();
}
callbackKext = OSKext::lookupKextWithAddress((vm_address_t)callback);
if (!callbackKext) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Can't invoke callback for resource request; ");
goto finish;
}
if (!callbackKext->flags.starting && !callbackKext->flags.started) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Can't invoke kext resource callback; ");
goto finish;
}
(void)callback(requestTag->unsigned32BitValue(),
(OSReturn)requestResult->unsigned32BitValue(),
dataPtr, dataLength, context);
result = kOSReturnSuccess;
finish:
return result;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::setMissingAuxKCBundles(OSDictionary * requestDict)
{
OSSharedPtr<OSDictionary> missingIDs;
OSArray *bundleIDList = NULL; // do not release
bundleIDList = OSDynamicCast(OSArray, _OSKextGetRequestArgument(
requestDict, kKextRequestArgumentMissingBundleIDs));
if (!bundleIDList) {
return kOSKextReturnInvalidArgument;
}
missingIDs = OSDictionary::withCapacity(bundleIDList->getCount());
if (!missingIDs) {
return kOSKextReturnNoMemory;
}
uint32_t count, i;
count = bundleIDList->getCount();
for (i = 0; i < count; i++) {
OSString *thisID = OSDynamicCast(OSString, bundleIDList->getObject(i));
if (thisID) {
missingIDs->setObject(thisID, kOSBooleanFalse);
}
}
sNonLoadableKextsByID.reset(missingIDs.get(), OSRetain);
return kOSReturnSuccess;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::setAuxKCBundleAvailable(OSString *kextIdentifier, OSDictionary *requestDict)
{
bool loadable = true;
if (!kextIdentifier) {
return kOSKextReturnInvalidArgument;
}
if (requestDict) {
OSBoolean *loadableArg;
loadableArg = OSDynamicCast(OSBoolean, _OSKextGetRequestArgument(
requestDict, kKextRequestArgumentBundleAvailability));
/* If we find the "Bundle Available" arg, and it's false, then
* mark the bundle ID as _not_ loadable
*/
if (loadableArg && !loadableArg->getValue()) {
loadable = false;
}
}
if (!sNonLoadableKextsByID) {
sNonLoadableKextsByID = OSDictionary::withCapacity(1);
}
sNonLoadableKextsByID->setObject(kextIdentifier, OSBoolean::withBoolean(loadable));
OSKextLog(/* kext */ NULL,
kOSKextLogBasicLevel | kOSKextLogIPCFlag,
"KextLog: AuxKC bundle %s marked as %s",
kextIdentifier->getCStringNoCopy(),
(loadable ? "loadable" : "NOT loadable"));
return kOSReturnSuccess;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::invokeRequestCallback(
OSDictionary * callbackRecord,
OSReturn callbackResult)
{
OSString * predicate = _OSKextGetRequestPredicate(callbackRecord);
OSSharedPtr<OSNumber> resultNum;
if (!predicate) {
goto finish;
}
resultNum = OSNumber::withNumber((long long unsigned int)callbackResult,
8 * sizeof(callbackResult));
if (!resultNum) {
goto finish;
}
/* Insert the result into the callback record and dispatch it as if it
* were the reply coming down from user space.
*/
_OSKextSetRequestArgument(callbackRecord, kKextRequestArgumentResultKey,
resultNum.get());
if (predicate->isEqualTo(kKextRequestPredicateRequestResource)) {
/* This removes the pending callback record.
*/
OSKext::dispatchResource(callbackRecord);
}
finish:
return;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
OSReturn
OSKext::cancelRequest(
OSKextRequestTag requestTag,
void ** contextOut)
{
OSReturn result = kOSKextReturnNoMemory;
OSSharedPtr<OSDictionary> callbackRecord;
OSData * contextWrapper = NULL; // do not release
IORecursiveLockLock(sKextLock);
result = OSKext::dequeueCallbackForRequestTag(requestTag,
callbackRecord);
IORecursiveLockUnlock(sKextLock);
if (result == kOSReturnSuccess && contextOut) {
contextWrapper = OSDynamicCast(OSData,
_OSKextGetRequestArgument(callbackRecord.get(),
kKextRequestArgumentContextKey));
*contextOut = _OSKextExtractPointer(contextWrapper);
}
return result;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
void
OSKext::invokeOrCancelRequestCallbacks(
OSReturn callbackResult,
bool invokeFlag)
{
unsigned int count, i;
count = sRequestCallbackRecords->getCount();
if (!count) {
goto finish;
}
i = count - 1;
do {
OSDictionary * request = OSDynamicCast(OSDictionary,
sRequestCallbackRecords->getObject(i));
if (!request) {
continue;
}
OSData * callbackWrapper = OSDynamicCast(OSData,
_OSKextGetRequestArgument(request,
kKextRequestArgumentCallbackKey));
if (!callbackWrapper) {
sRequestCallbackRecords->removeObject(i);
continue;
}
vm_address_t callbackAddress = (vm_address_t)
ptrauth_strip(_OSKextExtractPointer(callbackWrapper), ptrauth_key_function_pointer);
if ((kmod_info->address <= callbackAddress) &&
(callbackAddress < (kmod_info->address + kmod_info->size))) {
if (invokeFlag) {
/* This removes the callback record.
*/
invokeRequestCallback(request, callbackResult);
} else {
sRequestCallbackRecords->removeObject(i);
}
}
} while (i--);
finish:
return;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
uint32_t
OSKext::countRequestCallbacks(void)
{
uint32_t result = 0;
unsigned int count, i;
count = sRequestCallbackRecords->getCount();
if (!count) {
goto finish;
}
i = count - 1;
do {
OSDictionary * request = OSDynamicCast(OSDictionary,
sRequestCallbackRecords->getObject(i));
if (!request) {
continue;
}
OSData * callbackWrapper = OSDynamicCast(OSData,
_OSKextGetRequestArgument(request,
kKextRequestArgumentCallbackKey));
if (!callbackWrapper) {
continue;
}
vm_address_t callbackAddress = (vm_address_t)
ptrauth_strip(_OSKextExtractPointer(callbackWrapper), ptrauth_key_function_pointer);
if ((kmod_info->address <= callbackAddress) &&
(callbackAddress < (kmod_info->address + kmod_info->size))) {
result++;
}
} while (i--);
finish:
return result;
}
/*********************************************************************
*********************************************************************/
static OSReturn
_OSKextCreateRequest(
const char * predicate,
OSSharedPtr<OSDictionary> & requestR)
{
OSReturn result = kOSKextReturnNoMemory;
OSSharedPtr<OSDictionary> request;
request = OSDictionary::withCapacity(2);
if (!request) {
goto finish;
}
result = _OSDictionarySetCStringValue(request.get(),
kKextRequestPredicateKey, predicate);
if (result != kOSReturnSuccess) {
goto finish;
}
result = kOSReturnSuccess;
finish:
if (result == kOSReturnSuccess) {
requestR = os::move(request);
}
return result;
}
/*********************************************************************
*********************************************************************/
static OSString *
_OSKextGetRequestPredicate(OSDictionary * requestDict)
{
return OSDynamicCast(OSString,
requestDict->getObject(kKextRequestPredicateKey));
}
/*********************************************************************
*********************************************************************/
static OSObject *
_OSKextGetRequestArgument(
OSDictionary * requestDict,
const char * argName)
{
OSDictionary * args = OSDynamicCast(OSDictionary,
requestDict->getObject(kKextRequestArgumentsKey));
if (args) {
return args->getObject(argName);
}
return NULL;
}
/*********************************************************************
*********************************************************************/
static bool
_OSKextSetRequestArgument(
OSDictionary * requestDict,
const char * argName,
OSObject * value)
{
OSDictionary * args = OSDynamicCast(OSDictionary,
requestDict->getObject(kKextRequestArgumentsKey));
OSSharedPtr<OSDictionary> newArgs;
if (!args) {
newArgs = OSDictionary::withCapacity(2);
args = newArgs.get();
if (!args) {
goto finish;
}
requestDict->setObject(kKextRequestArgumentsKey, args);
}
if (args) {
return args->setObject(argName, value);
}
finish:
return false;
}
/*********************************************************************
*********************************************************************/
static void *
_OSKextExtractPointer(OSData * wrapper)
{
void * result = NULL;
const void * resultPtr = NULL;
if (!wrapper) {
goto finish;
}
resultPtr = wrapper->getBytesNoCopy();
result = *(void **)resultPtr;
finish:
return result;
}
/*********************************************************************
*********************************************************************/
static OSKextRequestResourceCallback
_OSKextExtractCallbackPointer(OSData * wrapper)
{
OSKextRequestResourceCallback result = NULL;
const void * resultPtr = NULL;
if (!wrapper) {
goto finish;
}
resultPtr = wrapper->getBytesNoCopy();
result = *(OSKextRequestResourceCallback *)resultPtr;
finish:
return result;
}
/*********************************************************************
*********************************************************************/
static OSReturn
_OSDictionarySetCStringValue(
OSDictionary * dict,
const char * cKey,
const char * cValue)
{
OSReturn result = kOSKextReturnNoMemory;
OSSharedPtr<const OSSymbol> key;
OSSharedPtr<OSString> value;
key = OSSymbol::withCString(cKey);
value = OSString::withCString(cValue);
if (!key || !value) {
goto finish;
}
if (dict->setObject(key.get(), value.get())) {
result = kOSReturnSuccess;
}
finish:
return result;
}
/*********************************************************************
*********************************************************************/
static bool
_OSArrayContainsCString(
OSArray * array,
const char * cString)
{
bool result = false;
OSSharedPtr<const OSSymbol> symbol;
uint32_t count, i;
if (!array || !cString) {
goto finish;
}
symbol = OSSymbol::withCStringNoCopy(cString);
if (!symbol) {
goto finish;
}
count = array->getCount();
for (i = 0; i < count; i++) {
OSObject * thisObject = array->getObject(i);
if (symbol->isEqualTo(thisObject)) {
result = true;
goto finish;
}
}
finish:
return result;
}
#if CONFIG_KXLD
/*********************************************************************
* We really only care about boot / system start up related kexts.
* We return true if we're less than REBUILD_MAX_TIME since start up,
* otherwise return false.
*********************************************************************/
bool
_OSKextInPrelinkRebuildWindow(void)
{
static bool outside_the_window = false;
AbsoluteTime my_abstime;
UInt64 my_ns;
SInt32 my_secs;
if (outside_the_window) {
return false;
}
clock_get_uptime(&my_abstime);
absolutetime_to_nanoseconds(my_abstime, &my_ns);
my_secs = (SInt32)(my_ns / NSEC_PER_SEC);
if (my_secs > REBUILD_MAX_TIME) {
outside_the_window = true;
return false;
}
return true;
}
#endif /* CONFIG_KXLD */
/*********************************************************************
*********************************************************************/
bool
_OSKextInUnloadedPrelinkedKexts( const OSSymbol * theBundleID )
{
int unLoadedCount, i;
bool result = false;
IORecursiveLockLock(sKextLock);
if (sUnloadedPrelinkedKexts == NULL) {
goto finish;
}
unLoadedCount = sUnloadedPrelinkedKexts->getCount();
if (unLoadedCount == 0) {
goto finish;
}
for (i = 0; i < unLoadedCount; i++) {
const OSSymbol * myBundleID; // do not release
myBundleID = OSDynamicCast(OSSymbol, sUnloadedPrelinkedKexts->getObject(i));
if (!myBundleID) {
continue;
}
if (theBundleID->isEqualTo(myBundleID->getCStringNoCopy())) {
result = true;
break;
}
}
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
#if PRAGMA_MARK
#pragma mark Personalities (IOKit Drivers)
#endif
/*********************************************************************
*********************************************************************/
/* static */
OSSharedPtr<OSArray>
OSKext::copyAllKextPersonalities(bool filterSafeBootFlag)
{
OSSharedPtr<OSArray> result;
OSSharedPtr<OSCollectionIterator> kextIterator;
OSSharedPtr<OSArray> personalities;
OSString * kextID = NULL; // do not release
OSKext * theKext = NULL; // do not release
IORecursiveLockLock(sKextLock);
/* Let's conservatively guess that any given kext has around 3
* personalities for now.
*/
result = OSArray::withCapacity(sKextsByID->getCount() * 3);
if (!result) {
goto finish;
}
kextIterator = OSCollectionIterator::withCollection(sKextsByID.get());
if (!kextIterator) {
goto finish;
}
while ((kextID = OSDynamicCast(OSString, kextIterator->getNextObject()))) {
theKext = OSDynamicCast(OSKext, sKextsByID->getObject(kextID));
if (theKext->flags.requireExplicitLoad) {
OSKextLog(theKext,
kOSKextLogDebugLevel |
kOSKextLogLoadFlag,
"Kext %s requires an explicit kextload; "
"omitting its personalities.",
theKext->getIdentifierCString());
} else if (!sSafeBoot || !filterSafeBootFlag || theKext->isLoadableInSafeBoot()) {
personalities = theKext->copyPersonalitiesArray();
if (!personalities) {
continue;
}
result->merge(personalities.get());
} else {
// xxx - check for better place to put this log msg
OSKextLog(theKext,
kOSKextLogWarningLevel |
kOSKextLogLoadFlag,
"Kext %s is not loadable during safe boot; "
"omitting its personalities.",
theKext->getIdentifierCString());
}
}
finish:
IORecursiveLockUnlock(sKextLock);
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::sendAllKextPersonalitiesToCatalog(bool startMatching)
{
int numPersonalities = 0;
OSKextLog(/* kext */ NULL,
kOSKextLogStepLevel |
kOSKextLogLoadFlag,
"Sending all eligible registered kexts' personalities "
"to the IOCatalogue %s.",
startMatching ? "and starting matching" : "but not starting matching");
OSSharedPtr<OSArray> personalities = OSKext::copyAllKextPersonalities(
/* filterSafeBootFlag */ true);
if (personalities) {
gIOCatalogue->addDrivers(personalities.get(), startMatching);
numPersonalities = personalities->getCount();
}
OSKextLog(/* kext */ NULL,
kOSKextLogStepLevel |
kOSKextLogLoadFlag,
"%d kext personalit%s sent to the IOCatalogue; %s.",
numPersonalities, numPersonalities > 0 ? "ies" : "y",
startMatching ? "matching started" : "matching not started");
return;
}
/*********************************************************************
* Do not make a deep copy, just convert the IOKitPersonalities dict
* to an array for sending to the IOCatalogue.
*********************************************************************/
OSSharedPtr<OSArray>
OSKext::copyPersonalitiesArray(void)
{
OSSharedPtr<OSArray> result;
OSDictionary * personalities = NULL; // do not release
OSSharedPtr<OSCollectionIterator> personalitiesIterator;
OSString * personalityName = NULL; // do not release
OSString * personalityBundleIdentifier = NULL; // do not release
personalities = OSDynamicCast(OSDictionary,
getPropertyForHostArch(kIOKitPersonalitiesKey));
if (!personalities) {
goto finish;
}
result = OSArray::withCapacity(personalities->getCount());
if (!result) {
goto finish;
}
personalitiesIterator =
OSCollectionIterator::withCollection(personalities);
if (!personalitiesIterator) {
goto finish;
}
while ((personalityName = OSDynamicCast(OSString,
personalitiesIterator->getNextObject()))) {
OSDictionary * personality = OSDynamicCast(OSDictionary,
personalities->getObject(personalityName));
/******
* If the personality doesn't have a CFBundleIdentifier, or if it
* differs from the kext's, insert the kext's ID so we can find it.
* The publisher ID is used to remove personalities from bundles
* correctly.
*/
personalityBundleIdentifier = OSDynamicCast(OSString,
personality->getObject(kCFBundleIdentifierKey));
if (!personalityBundleIdentifier) {
personality->setObject(kCFBundleIdentifierKey, bundleID.get());
} else if (!personalityBundleIdentifier->isEqualTo(bundleID.get())) {
personality->setObject(kIOPersonalityPublisherKey, bundleID.get());
}
result->setObject(personality);
}
finish:
return result;
}
/*********************************************************************
* Might want to change this to a bool return?
*********************************************************************/
OSReturn
OSKext::sendPersonalitiesToCatalog(
bool startMatching,
OSArray * personalityNames)
{
OSReturn result = kOSReturnSuccess;
OSSharedPtr<OSArray> personalitiesToSend;
OSDictionary * kextPersonalities = NULL; // do not release
int count, i;
if (!sLoadEnabled) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext loading is disabled (attempt to start matching for kext %s).",
getIdentifierCString());
result = kOSKextReturnDisabled;
goto finish;
}
if (sSafeBoot && !isLoadableInSafeBoot()) {
OSKextLog(this,
kOSKextLogErrorLevel |
kOSKextLogLoadFlag,
"Kext %s is not loadable during safe boot; "
"not sending personalities to the IOCatalogue.",
getIdentifierCString());
result = kOSKextReturnNotLoadable;
goto finish;
}
if (!personalityNames || !personalityNames->getCount()) {
personalitiesToSend = copyPersonalitiesArray();
} else {
kextPersonalities = OSDynamicCast(OSDictionary,
getPropertyForHostArch(kIOKitPersonalitiesKey));
if (!kextPersonalities || !kextPersonalities->getCount()) {
// not an error
goto finish;
}
personalitiesToSend = OSArray::withCapacity(0);
if (!personalitiesToSend) {
result = kOSKextReturnNoMemory;
goto finish;
}
count = personalityNames->getCount();
for (i = 0; i < count; i++) {
OSString * name = OSDynamicCast(OSString,
personalityNames->getObject(i));
if (!name) {
continue;
}
OSDictionary * personality = OSDynamicCast(OSDictionary,
kextPersonalities->getObject(name));
if (personality) {
personalitiesToSend->setObject(personality);
}
}
}
if (personalitiesToSend) {
unsigned numPersonalities = personalitiesToSend->getCount();
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogLoadFlag,
"Kext %s sending %d personalit%s to the IOCatalogue%s.",
getIdentifierCString(),
numPersonalities,
numPersonalities > 1 ? "ies" : "y",
startMatching ? " and starting matching" : " but not starting matching");
gIOCatalogue->addDrivers(personalitiesToSend.get(), startMatching);
}
finish:
return result;
}
/*********************************************************************
* xxx - We should allow removing the kext's declared personalities,
* xxx - even with other bundle identifiers.
*********************************************************************/
void
OSKext::removePersonalitiesFromCatalog(void)
{
OSSharedPtr<OSDictionary> personality;
personality = OSDictionary::withCapacity(1);
if (!personality) {
goto finish;
}
personality->setObject(kCFBundleIdentifierKey, getIdentifier());
OSKextLog(this,
kOSKextLogStepLevel |
kOSKextLogLoadFlag,
"Kext %s removing all personalities naming it from the IOCatalogue.",
getIdentifierCString());
/* Have the IOCatalog remove all personalities matching this kext's
* bundle ID and trigger matching anew.
*/
gIOCatalogue->removeDrivers(personality.get(), /* startMatching */ true);
finish:
return;
}
#if PRAGMA_MARK
#pragma mark Logging
#endif
/*********************************************************************
* Do not call any function that takes sKextLock here!
*********************************************************************/
/* static */
OSKextLogSpec
OSKext::setUserSpaceLogFilter(
OSKextLogSpec newUserLogFilter,
bool captureFlag)
{
OSKextLogSpec result;
bool allocError = false;
/* Do not call any function that takes sKextLoggingLock during
* this critical block. That means do logging after.
*/
IOLockLock(sKextLoggingLock);
result = sUserSpaceKextLogFilter;
sUserSpaceKextLogFilter = newUserLogFilter;
if (newUserLogFilter && captureFlag &&
!sUserSpaceLogSpecArray && !sUserSpaceLogMessageArray) {
// xxx - do some measurements for a good initial capacity?
sUserSpaceLogSpecArray = OSArray::withCapacity(0);
sUserSpaceLogMessageArray = OSArray::withCapacity(0);
if (!sUserSpaceLogSpecArray || !sUserSpaceLogMessageArray) {
allocError = true;
}
}
IOLockUnlock(sKextLoggingLock);
/* If the config flag itself is changing, log the state change
* going both ways, before setting up the user-space log arrays,
* so that this is only logged in the kernel.
*/
if (result != newUserLogFilter) {
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogGeneralFlag,
"User-space log flags changed from 0x%x to 0x%x.",
result, newUserLogFilter);
}
if (allocError) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel |
kOSKextLogGeneralFlag,
"Failed to allocate user-space log message arrays.");
}
return result;
}
/*********************************************************************
* Do not call any function that takes sKextLock here!
*********************************************************************/
/* static */
OSSharedPtr<OSArray>
OSKext::clearUserSpaceLogFilter(void)
{
OSSharedPtr<OSArray> result;
OSKextLogSpec oldLogFilter;
OSKextLogSpec newLogFilter = kOSKextLogSilentFilter;
/* Do not call any function that takes sKextLoggingLock during
* this critical block. That means do logging after.
*/
IOLockLock(sKextLoggingLock);
result = OSArray::withCapacity(2);
if (result) {
result->setObject(sUserSpaceLogSpecArray.get());
result->setObject(sUserSpaceLogMessageArray.get());
}
sUserSpaceLogSpecArray.reset();
sUserSpaceLogMessageArray.reset();
oldLogFilter = sUserSpaceKextLogFilter;
sUserSpaceKextLogFilter = newLogFilter;
IOLockUnlock(sKextLoggingLock);
/* If the config flag itself is changing, log the state change
* going both ways, after tearing down the user-space log
* arrays, so this is only logged within the kernel.
*/
if (oldLogFilter != newLogFilter) {
OSKextLog(/* kext */ NULL,
kOSKextLogDebugLevel |
kOSKextLogGeneralFlag,
"User-space log flags changed from 0x%x to 0x%x.",
oldLogFilter, newLogFilter);
}
return result;
}
/*********************************************************************
* Do not call any function that takes sKextLock here!
*********************************************************************/
/* static */
OSKextLogSpec
OSKext::getUserSpaceLogFilter(void)
{
OSKextLogSpec result;
IOLockLock(sKextLoggingLock);
result = sUserSpaceKextLogFilter;
IOLockUnlock(sKextLoggingLock);
return result;
}
/*********************************************************************
* This function is called by OSMetaClass during kernel C++ setup.
* Be careful what you access here; assume only OSKext::initialize()
* has been called.
*
* Do not call any function that takes sKextLock here!
*********************************************************************/
#define VTRESET "\033[0m"
#define VTBOLD "\033[1m"
#define VTUNDER "\033[4m"
#define VTRED "\033[31m"
#define VTGREEN "\033[32m"
#define VTYELLOW "\033[33m"
#define VTBLUE "\033[34m"
#define VTMAGENTA "\033[35m"
#define VTCYAN "\033[36m"
inline const char *
colorForFlags(OSKextLogSpec flags)
{
OSKextLogSpec logLevel = flags & kOSKextLogLevelMask;
switch (logLevel) {
case kOSKextLogErrorLevel:
return VTRED VTBOLD;
case kOSKextLogWarningLevel:
return VTRED;
case kOSKextLogBasicLevel:
return VTYELLOW VTUNDER;
case kOSKextLogProgressLevel:
return VTYELLOW;
case kOSKextLogStepLevel:
return VTGREEN;
case kOSKextLogDetailLevel:
return VTCYAN;
case kOSKextLogDebugLevel:
return VTMAGENTA;
default:
return ""; // white
}
}
inline bool
logSpecMatch(
OSKextLogSpec msgLogSpec,
OSKextLogSpec logFilter)
{
OSKextLogSpec filterKextGlobal = logFilter & kOSKextLogKextOrGlobalMask;
OSKextLogSpec filterLevel = logFilter & kOSKextLogLevelMask;
OSKextLogSpec filterFlags = logFilter & kOSKextLogFlagsMask;
OSKextLogSpec msgKextGlobal = msgLogSpec & kOSKextLogKextOrGlobalMask;
OSKextLogSpec msgLevel = msgLogSpec & kOSKextLogLevelMask;
OSKextLogSpec msgFlags = msgLogSpec & kOSKextLogFlagsMask;
/* Explicit messages always get logged.
*/
if (msgLevel == kOSKextLogExplicitLevel) {
return true;
}
/* Warnings and errors are logged regardless of the flags.
*/
if (msgLevel <= kOSKextLogBasicLevel && (msgLevel <= filterLevel)) {
return true;
}
/* A verbose message that isn't for a logging-enabled kext and isn't global
* does *not* get logged.
*/
if (!msgKextGlobal && !filterKextGlobal) {
return false;
}
/* Warnings and errors are logged regardless of the flags.
* All other messages must fit the flags and
* have a level at or below the filter.
*
*/
if ((msgFlags & filterFlags) && (msgLevel <= filterLevel)) {
return true;
}
return false;
}
extern "C" {
void
OSKextLog(
OSKext * aKext,
OSKextLogSpec msgLogSpec,
const char * format, ...)
{
va_list argList;
va_start(argList, format);
OSKextVLog(aKext, msgLogSpec, format, argList);
va_end(argList);
}
void
OSKextVLog(
OSKext * aKext,
OSKextLogSpec msgLogSpec,
const char * format,
va_list srcArgList)
{
extern int disableConsoleOutput;
bool logForKernel = false;
bool logForUser = false;
va_list argList;
char stackBuffer[120];
uint32_t length = 0;
char * allocBuffer = NULL; // must kfree
OSSharedPtr<OSNumber> logSpecNum;
OSSharedPtr<OSString> logString;
char * buffer = stackBuffer; // do not free
IOLockLock(sKextLoggingLock);
/* Set the kext/global bit in the message spec if we have no
* kext or if the kext requests logging.
*/
if (!aKext || aKext->flags.loggingEnabled) {
msgLogSpec = msgLogSpec | kOSKextLogKextOrGlobalMask;
}
logForKernel = logSpecMatch(msgLogSpec, sKernelLogFilter);
if (sUserSpaceLogSpecArray && sUserSpaceLogMessageArray) {
logForUser = logSpecMatch(msgLogSpec, sUserSpaceKextLogFilter);
}
if (!(logForKernel || logForUser)) {
goto finish;
}
/* No goto from here until past va_end()!
*/
va_copy(argList, srcArgList);
length = vsnprintf(stackBuffer, sizeof(stackBuffer), format, argList);
va_end(argList);
if (length + 1 >= sizeof(stackBuffer)) {
allocBuffer = (char *)kheap_alloc_tag(KHEAP_TEMP,
length + 1, Z_WAITOK, VM_KERN_MEMORY_OSKEXT);
if (!allocBuffer) {
goto finish;
}
/* No goto from here until past va_end()!
*/
va_copy(argList, srcArgList);
vsnprintf(allocBuffer, length + 1, format, argList);
va_end(argList);
buffer = allocBuffer;
}
/* If user space wants the log message, queue it up.
*/
if (logForUser && sUserSpaceLogSpecArray && sUserSpaceLogMessageArray) {
logSpecNum = OSNumber::withNumber(msgLogSpec, 8 * sizeof(msgLogSpec));
logString = OSString::withCString(buffer);
if (logSpecNum && logString) {
sUserSpaceLogSpecArray->setObject(logSpecNum.get());
sUserSpaceLogMessageArray->setObject(logString.get());
}
}
/* Always log messages from the kernel according to the kernel's
* log flags.
*/
if (logForKernel) {
/* If we are in console mode and have a custom log filter,
* colorize the log message.
*/
if (!disableConsoleOutput && sBootArgLogFilterFound) {
const char * color = ""; // do not free
color = colorForFlags(msgLogSpec);
printf("%s%s%s\n", colorForFlags(msgLogSpec),
buffer, color[0] ? VTRESET : "");
} else {
printf("%s\n", buffer);
}
}
finish:
IOLockUnlock(sKextLoggingLock);
if (allocBuffer) {
kheap_free(KHEAP_TEMP, allocBuffer, (length + 1) * sizeof(char));
}
return;
}
#if KASLR_IOREG_DEBUG
#define IOLOG_INDENT( the_indention ) \
{ \
int i; \
for ( i = 0; i < (the_indention); i++ ) { \
IOLog(" "); \
} \
}
extern vm_offset_t vm_kernel_stext;
extern vm_offset_t vm_kernel_etext;
extern mach_vm_offset_t kext_alloc_base;
extern mach_vm_offset_t kext_alloc_max;
bool ScanForAddrInObject(OSObject * theObject,
int indent );
bool
ScanForAddrInObject(OSObject * theObject,
int indent)
{
const OSMetaClass * myTypeID;
OSSharedPtr<OSCollectionIterator> myIter;
OSSymbol * myKey;
OSObject * myValue;
bool myResult = false;
if (theObject == NULL) {
IOLog("%s: theObject is NULL \n",
__FUNCTION__);
return myResult;
}
myTypeID = OSTypeIDInst(theObject);
if (myTypeID == OSTypeID(OSDictionary)) {
OSDictionary * myDictionary;
myDictionary = OSDynamicCast(OSDictionary, theObject);
myIter = OSCollectionIterator::withCollection( myDictionary );
if (myIter == NULL) {
return myResult;
}
// !! reset the iterator
myIter->reset();
while ((myKey = OSDynamicCast(OSSymbol, myIter->getNextObject()))) {
bool myTempResult;
myValue = myDictionary->getObject(myKey);
myTempResult = ScanForAddrInObject(myValue, (indent + 4));
if (myTempResult) {
// if we ever get a true result return true
myResult = true;
IOLOG_INDENT(indent);
IOLog("OSDictionary key \"%s\" \n", myKey->getCStringNoCopy());
}
}
// !! release the iterator
myIter.reset();
} else if (myTypeID == OSTypeID(OSArray)) {
OSArray * myArray;
myArray = OSDynamicCast(OSArray, theObject);
myIter = OSCollectionIterator::withCollection(myArray);
if (myIter == NULL) {
return myResult;
}
// !! reset the iterator
myIter->reset();
while ((myValue = myIter->getNextObject())) {
bool myTempResult;
myTempResult = ScanForAddrInObject(myValue, (indent + 4));
if (myTempResult) {
// if we ever get a true result return true
myResult = true;
IOLOG_INDENT(indent);
IOLog("OSArray: \n");
}
}
// !! release the iterator
myIter.reset();
} else if (myTypeID == OSTypeID(OSString) || myTypeID == OSTypeID(OSSymbol)) {
// should we look for addresses in strings?
} else if (myTypeID == OSTypeID(OSData)) {
void * * myPtrPtr;
unsigned int myLen;
OSData * myDataObj;
myDataObj = OSDynamicCast(OSData, theObject);
myPtrPtr = (void * *) myDataObj->getBytesNoCopy();
myLen = myDataObj->getLength();
if (myPtrPtr && myLen && myLen > 7) {
int i;
int myPtrCount = (myLen / sizeof(void *));
for (i = 0; i < myPtrCount; i++) {
UInt64 numberValue = (UInt64) * (myPtrPtr);
if (kext_alloc_max != 0 &&
numberValue >= kext_alloc_base &&
numberValue < kext_alloc_max) {
OSSharedPtr<OSKext> myKext;
// IOLog("found OSData %p in kext map %p to %p \n",
// *(myPtrPtr),
// (void *) kext_alloc_base,
// (void *) kext_alloc_max);
myKext = OSKext::lookupKextWithAddress((vm_address_t) *(myPtrPtr));
if (myKext) {
IOLog("found addr %p from an OSData obj within kext \"%s\" \n",
*(myPtrPtr),
myKext->getIdentifierCString());
}
myResult = true;
}
if (vm_kernel_etext != 0 &&
numberValue >= vm_kernel_stext &&
numberValue < vm_kernel_etext) {
IOLog("found addr %p from an OSData obj within kernel text segment %p to %p \n",
*(myPtrPtr),
(void *) vm_kernel_stext,
(void *) vm_kernel_etext);
myResult = true;
}
myPtrPtr++;
}
}
} else if (myTypeID == OSTypeID(OSBoolean)) {
// do nothing here...
} else if (myTypeID == OSTypeID(OSNumber)) {
OSNumber * number = OSDynamicCast(OSNumber, theObject);
UInt64 numberValue = number->unsigned64BitValue();
if (kext_alloc_max != 0 &&
numberValue >= kext_alloc_base &&
numberValue < kext_alloc_max) {
OSSharedPtr<OSKext> myKext;
IOLog("found OSNumber in kext map %p to %p \n",
(void *) kext_alloc_base,
(void *) kext_alloc_max);
IOLog("OSNumber 0x%08llx (%llu) \n", numberValue, numberValue);
myKext = OSKext::lookupKextWithAddress((vm_address_t) numberValue );
if (myKext) {
IOLog("found in kext \"%s\" \n",
myKext->getIdentifierCString());
}
myResult = true;
}
if (vm_kernel_etext != 0 &&
numberValue >= vm_kernel_stext &&
numberValue < vm_kernel_etext) {
IOLog("found OSNumber in kernel text segment %p to %p \n",
(void *) vm_kernel_stext,
(void *) vm_kernel_etext);
IOLog("OSNumber 0x%08llx (%llu) \n", numberValue, numberValue);
myResult = true;
}
}
#if 0
else {
const OSMetaClass* myMetaClass = NULL;
myMetaClass = theObject->getMetaClass();
if (myMetaClass) {
IOLog("class %s \n", myMetaClass->getClassName());
} else {
IOLog("Unknown object \n" );
}
}
#endif
return myResult;
}
#endif // KASLR_KEXT_DEBUG
}; /* extern "C" */
#if PRAGMA_MARK
#pragma mark Backtrace Dump & kmod_get_info() support
#endif
/*********************************************************************
* This function must be safe to call in panic context.
*********************************************************************/
/* static */
void
OSKext::printKextsInBacktrace(
vm_offset_t * addr __unused,
unsigned int cnt __unused,
int (* printf_func)(const char *fmt, ...) __unused,
uint32_t flags __unused)
{
addr64_t summary_page = 0;
addr64_t last_summary_page = 0;
bool found_kmod = false;
u_int i = 0;
if (kPrintKextsLock & flags) {
if (!sKextSummariesLock) {
return;
}
IOLockLock(sKextSummariesLock);
}
if (!gLoadedKextSummaries) {
(*printf_func)(" can't perform kext scan: no kext summary");
goto finish;
}
summary_page = trunc_page((addr64_t)(uintptr_t)gLoadedKextSummaries);
last_summary_page = round_page(summary_page + sLoadedKextSummariesAllocSize);
for (; summary_page < last_summary_page; summary_page += PAGE_SIZE) {
if (pmap_find_phys(kernel_pmap, summary_page) == 0) {
(*printf_func)(" can't perform kext scan: "
"missing kext summary page %p", summary_page);
goto finish;
}
}
for (i = 0; i < gLoadedKextSummaries->numSummaries; ++i) {
OSKextLoadedKextSummary * summary;
summary = gLoadedKextSummaries->summaries + i;
if (!summary->address) {
continue;
}
if (!summaryIsInBacktrace(summary, addr, cnt)) {
continue;
}
if (!found_kmod) {
if (!(kPrintKextsTerse & flags)) {
(*printf_func)(" Kernel Extensions in backtrace:\n");
}
found_kmod = true;
}
printSummary(summary, printf_func, flags);
}
finish:
if (kPrintKextsLock & flags) {
IOLockUnlock(sKextSummariesLock);
}
return;
}
/*********************************************************************
* This function must be safe to call in panic context.
*********************************************************************/
/* static */
boolean_t
OSKext::summaryIsInBacktrace(
OSKextLoadedKextSummary * summary,
vm_offset_t * addr,
unsigned int cnt)
{
u_int i = 0;
for (i = 0; i < cnt; i++) {
vm_offset_t kscan_addr = addr[i];
#if __has_feature(ptrauth_calls)
kscan_addr = (vm_offset_t)VM_KERNEL_STRIP_PTR(kscan_addr);
#endif /* __has_feature(ptrauth_calls) */
if ((kscan_addr >= summary->text_exec_address) &&
(kscan_addr < (summary->text_exec_address + summary->text_exec_size))) {
return TRUE;
}
}
return FALSE;
}
/*
* Get the kext summary object for the kext where 'addr' lies. Must be called with
* sKextSummariesLock held.
*/
OSKextLoadedKextSummary *
OSKext::summaryForAddress(uintptr_t addr)
{
#if __has_feature(ptrauth_calls)
addr = (uintptr_t)VM_KERNEL_STRIP_PTR(addr);
#endif /* __has_feature(ptrauth_calls) */
for (unsigned i = 0; i < gLoadedKextSummaries->numSummaries; ++i) {
OSKextLoadedKextSummary *summary = &gLoadedKextSummaries->summaries[i];
if (!summary->address) {
continue;
}
#if VM_MAPPED_KEXTS
/* On our platforms that use VM_MAPPED_KEXTS, we currently do not
* support split kexts, but we also may unmap the kexts, which can
* race with the above codepath (see OSKext::unload). As such,
* use a simple range lookup if we are using VM_MAPPED_KEXTS.
*/
if ((addr >= summary->address) && (addr < (summary->address + summary->size))) {
return summary;
}
#else
kernel_mach_header_t *mh = (kernel_mach_header_t *)summary->address;
kernel_segment_command_t *seg;
for (seg = firstsegfromheader(mh); seg != NULL; seg = nextsegfromheader(mh, seg)) {
if ((addr >= seg->vmaddr) && (addr < (seg->vmaddr + seg->vmsize))) {
return summary;
}
}
#endif
}
/* addr did not map to any kext */
return NULL;
}
/* static */
void *
OSKext::kextForAddress(const void *address)
{
void * image = NULL;
OSKextActiveAccount * active;
OSKext * kext = NULL;
uint32_t baseIdx;
uint32_t lim;
uintptr_t addr = (uintptr_t) address;
size_t i;
if (!addr) {
return NULL;
}
#if __has_feature(ptrauth_calls)
addr = (uintptr_t)VM_KERNEL_STRIP_PTR(addr);
#endif /* __has_feature(ptrauth_calls) */
if (sKextAccountsCount) {
IOSimpleLockLock(sKextAccountsLock);
// bsearch sKextAccounts list
for (baseIdx = 0, lim = sKextAccountsCount; lim; lim >>= 1) {
active = &sKextAccounts[baseIdx + (lim >> 1)];
if ((addr >= active->address) && (addr < active->address_end)) {
kext = active->account->kext;
if (kext && kext->kmod_info) {
image = (void *) kext->kmod_info->address;
}
break;
} else if (addr > active->address) {
// move right
baseIdx += (lim >> 1) + 1;
lim--;
}
// else move left
}
IOSimpleLockUnlock(sKextAccountsLock);
}
if (!image && (addr >= vm_kernel_stext) && (addr < vm_kernel_etext)) {
image = (void *) &_mh_execute_header;
}
if (!image && gLoadedKextSummaries) {
IOLockLock(sKextSummariesLock);
for (i = 0; i < gLoadedKextSummaries->numSummaries; i++) {
OSKextLoadedKextSummary *summary = gLoadedKextSummaries->summaries + i;
if (addr >= summary->address && addr < summary->address + summary->size) {
image = (void *)summary->address;
}
}
IOLockUnlock(sKextSummariesLock);
}
return image;
}
/*
* Find a OSKextLoadedKextSummary given the ID from a kmod_info_t *
* Safe to call in panic context.
*/
static OSKextLoadedKextSummary *
findSummary(uint32_t tagID)
{
OSKextLoadedKextSummary * summary;
for (size_t i = 0; i < gLoadedKextSummaries->numSummaries; ++i) {
summary = gLoadedKextSummaries->summaries + i;
if (summary->loadTag == tagID) {
return summary;
}
}
return NULL;
}
/*********************************************************************
* This function must be safe to call in panic context.
*********************************************************************/
void
OSKext::printSummary(
OSKextLoadedKextSummary * summary,
int (* printf_func)(const char *fmt, ...),
uint32_t flags)
{
kmod_reference_t * kmod_ref = NULL;
uuid_string_t uuid;
char version[kOSKextVersionMaxLength];
uint64_t tmpAddr;
uint64_t tmpSize;
OSKextLoadedKextSummary *dependencySummary;
if (!OSKextVersionGetString(summary->version, version, sizeof(version))) {
strlcpy(version, "unknown version", sizeof(version));
}
(void) uuid_unparse(summary->uuid, uuid);
#if defined(__arm__) || defined(__arm64__)
tmpAddr = summary->text_exec_address;
tmpSize = summary->text_exec_size;
#else
tmpAddr = summary->address;
tmpSize = summary->size;
#endif
if (kPrintKextsUnslide & flags) {
tmpAddr = ml_static_unslide(tmpAddr);
}
(*printf_func)("%s%s(%s)[%s]@0x%llx->0x%llx\n",
(kPrintKextsTerse & flags) ? "" : " ",
summary->name, version, uuid,
tmpAddr, tmpAddr + tmpSize - 1);
if (kPrintKextsTerse & flags) {
return;
}
/* print dependency info */
for (kmod_ref = (kmod_reference_t *) summary->reference_list;
kmod_ref;
kmod_ref = kmod_ref->next) {
kmod_info_t * rinfo;
if (pmap_find_phys(kernel_pmap, (addr64_t)((uintptr_t)kmod_ref)) == 0) {
(*printf_func)(" kmod dependency scan stopped "
"due to missing dependency page: %p\n",
(kPrintKextsUnslide & flags) ? (void *)ml_static_unslide((vm_offset_t)kmod_ref) : kmod_ref);
break;
}
rinfo = kmod_ref->info;
if (pmap_find_phys(kernel_pmap, (addr64_t)((uintptr_t)rinfo)) == 0) {
(*printf_func)(" kmod dependency scan stopped "
"due to missing kmod page: %p\n",
(kPrintKextsUnslide & flags) ? (void *)ml_static_unslide((vm_offset_t)rinfo) : rinfo);
break;
}
if (!rinfo->address) {
continue; // skip fake entries for built-ins
}
dependencySummary = findSummary(rinfo->id);
uuid[0] = 0x00;
tmpAddr = rinfo->address;
tmpSize = rinfo->size;
if (dependencySummary) {
(void) uuid_unparse(dependencySummary->uuid, uuid);
#if defined(__arm__) || defined(__arm64__)
tmpAddr = dependencySummary->text_exec_address;
tmpSize = dependencySummary->text_exec_size;
#endif
}
if (kPrintKextsUnslide & flags) {
tmpAddr = ml_static_unslide(tmpAddr);
}
(*printf_func)(" dependency: %s(%s)[%s]@%p->%p\n",
rinfo->name, rinfo->version, uuid, tmpAddr, tmpAddr + tmpSize - 1);
}
return;
}
#if !defined(__arm__) && !defined(__arm64__)
/*******************************************************************************
* substitute() looks at an input string (a pointer within a larger buffer)
* for a match to a substring, and on match it writes the marker & substitution
* character to an output string, updating the scan (from) and
* output (to) indexes as appropriate.
*******************************************************************************/
static int substitute(
const char * scan_string,
char * string_out,
uint32_t * to_index,
uint32_t * from_index,
const char * substring,
char marker,
char substitution);
/* string_out must be at least KMOD_MAX_NAME bytes.
*/
static int
substitute(
const char * scan_string,
char * string_out,
uint32_t * to_index,
uint32_t * from_index,
const char * substring,
char marker,
char substitution)
{
size_t substring_length = strnlen(substring, KMOD_MAX_NAME - 1);
/* On a substring match, append the marker (if there is one) and then
* the substitution character, updating the output (to) index accordingly.
* Then update the input (from) length by the length of the substring
* that got replaced.
*/
if (!strncmp(scan_string, substring, substring_length)) {
if (marker) {
string_out[(*to_index)++] = marker;
}
string_out[(*to_index)++] = substitution;
(*from_index) += substring_length;
return 1;
}
return 0;
}
/*******************************************************************************
* compactIdentifier() takes a CFBundleIdentifier in a buffer of at least
* KMOD_MAX_NAME characters and performs various substitutions of common
* prefixes & substrings as defined by tables in kext_panic_report.h.
*******************************************************************************/
static void compactIdentifier(
const char * identifier,
char * identifier_out,
char ** identifier_out_end);
static void
compactIdentifier(
const char * identifier,
char * identifier_out,
char ** identifier_out_end)
{
uint32_t from_index, to_index;
uint32_t scan_from_index = 0;
uint32_t scan_to_index = 0;
subs_entry_t * subs_entry = NULL;
int did_sub = 0;
from_index = to_index = 0;
identifier_out[0] = '\0';
/* Replace certain identifier prefixes with shorter @+character sequences.
* Check the return value of substitute() so we only replace the prefix.
*/
for (subs_entry = &kext_identifier_prefix_subs[0];
subs_entry->substring && !did_sub;
subs_entry++) {
did_sub = substitute(identifier, identifier_out,
&scan_to_index, &scan_from_index,
subs_entry->substring, /* marker */ '\0', subs_entry->substitute);
}
did_sub = 0;
/* Now scan through the identifier looking for the common substrings
* and replacing them with shorter !+character sequences via substitute().
*/
for (/* see above */;
scan_from_index < KMOD_MAX_NAME - 1 && identifier[scan_from_index];
/* see loop */) {
const char * scan_string = &identifier[scan_from_index];
did_sub = 0;
if (scan_from_index) {
for (subs_entry = &kext_identifier_substring_subs[0];
subs_entry->substring && !did_sub;
subs_entry++) {
did_sub = substitute(scan_string, identifier_out,
&scan_to_index, &scan_from_index,
subs_entry->substring, '!', subs_entry->substitute);
}
}
/* If we didn't substitute, copy the input character to the output.
*/
if (!did_sub) {
identifier_out[scan_to_index++] = identifier[scan_from_index++];
}
}
identifier_out[scan_to_index] = '\0';
if (identifier_out_end) {
*identifier_out_end = &identifier_out[scan_to_index];
}
return;
}
#endif /* !defined(__arm__) && !defined(__arm64__) */
/*******************************************************************************
* assemble_identifier_and_version() adds to a string buffer a compacted
* bundle identifier followed by a version string.
*******************************************************************************/
/* identPlusVers must be at least 2*KMOD_MAX_NAME in length.
*/
static size_t assemble_identifier_and_version(
kmod_info_t * kmod_info,
char * identPlusVers,
size_t bufSize);
static size_t
assemble_identifier_and_version(
kmod_info_t * kmod_info,
char * identPlusVers,
size_t bufSize)
{
size_t result = 0;
#if defined(__arm__) || defined(__arm64__)
result = strlcpy(identPlusVers, kmod_info->name, KMOD_MAX_NAME);
#else
compactIdentifier(kmod_info->name, identPlusVers, NULL);
result = strnlen(identPlusVers, KMOD_MAX_NAME - 1);
#endif
identPlusVers[result++] = '\t'; // increment for real char
identPlusVers[result] = '\0'; // don't increment for nul char
result = strlcat(identPlusVers, kmod_info->version, bufSize);
if (result >= bufSize) {
identPlusVers[bufSize - 1] = '\0';
result = bufSize - 1;
}
return result;
}
/*******************************************************************************
* Assumes sKextLock is held.
*******************************************************************************/
/* static */
int
OSKext::saveLoadedKextPanicListTyped(
const char * prefix,
int invertFlag,
int libsFlag,
char * paniclist,
uint32_t list_size)
{
int result = -1;
unsigned int count, i;
count = sLoadedKexts->getCount();
if (!count) {
goto finish;
}
i = count - 1;
do {
OSObject * rawKext = sLoadedKexts->getObject(i);
OSKext * theKext = OSDynamicCast(OSKext, rawKext);
int match;
size_t identPlusVersLength;
size_t tempLen;
char identPlusVers[2 * KMOD_MAX_NAME];
if (!rawKext) {
printf("OSKext::saveLoadedKextPanicListTyped - "
"NULL kext in loaded kext list; continuing\n");
continue;
}
if (!theKext) {
printf("OSKext::saveLoadedKextPanicListTyped - "
"Kext type cast failed in loaded kext list; continuing\n");
continue;
}
/* Skip all built-in kexts.
*/
if (theKext->isKernelComponent()) {
continue;
}
kmod_info_t * kmod_info = theKext->kmod_info;
/* Filter for kmod name (bundle identifier).
*/
match = !strncmp(kmod_info->name, prefix, strnlen(prefix, KMOD_MAX_NAME));
if ((match && invertFlag) || (!match && !invertFlag)) {
continue;
}
/* Filter for libraries (kexts that have a compatible version).
*/
if ((libsFlag == 0 && theKext->getCompatibleVersion() > 1) ||
(libsFlag == 1 && theKext->getCompatibleVersion() < 1)) {
continue;
}
if (!kmod_info ||
!pmap_find_phys(kernel_pmap, (addr64_t)((uintptr_t)kmod_info))) {
printf("kext scan stopped due to missing kmod_info page: %p\n",
kmod_info);
goto finish;
}
identPlusVersLength = assemble_identifier_and_version(kmod_info,
identPlusVers,
sizeof(identPlusVers));
if (!identPlusVersLength) {
printf("error saving loaded kext info\n");
goto finish;
}
/* make sure everything fits and we null terminate.
*/
tempLen = strlcat(paniclist, identPlusVers, list_size);
if (tempLen >= list_size) {
// panic list is full, keep it and null terminate
paniclist[list_size - 1] = 0x00;
result = 0;
goto finish;
}
tempLen = strlcat(paniclist, "\n", list_size);
if (tempLen >= list_size) {
// panic list is full, keep it and null terminate
paniclist[list_size - 1] = 0x00;
result = 0;
goto finish;
}
} while (i--);
result = 0;
finish:
return result;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::saveLoadedKextPanicList(void)
{
char * newlist = NULL;
uint32_t newlist_size = 0;
newlist_size = KEXT_PANICLIST_SIZE;
newlist = (char *)kheap_alloc_tag(KHEAP_DATA_BUFFERS, newlist_size,
Z_WAITOK, VM_KERN_MEMORY_OSKEXT);
if (!newlist) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag,
"Couldn't allocate kext panic log buffer.");
goto finish;
}
newlist[0] = '\0';
// non-"com.apple." kexts
if (OSKext::saveLoadedKextPanicListTyped("com.apple.", /* invert? */ 1,
/* libs? */ -1, newlist, newlist_size) != 0) {
goto finish;
}
// "com.apple." nonlibrary kexts
if (OSKext::saveLoadedKextPanicListTyped("com.apple.", /* invert? */ 0,
/* libs? */ 0, newlist, newlist_size) != 0) {
goto finish;
}
// "com.apple." library kexts
if (OSKext::saveLoadedKextPanicListTyped("com.apple.", /* invert? */ 0,
/* libs? */ 1, newlist, newlist_size) != 0) {
goto finish;
}
if (loaded_kext_paniclist) {
kheap_free(KHEAP_DATA_BUFFERS, loaded_kext_paniclist,
loaded_kext_paniclist_size);
}
loaded_kext_paniclist = newlist;
newlist = NULL;
loaded_kext_paniclist_size = newlist_size;
finish:
if (newlist) {
kheap_free(KHEAP_TEMP, newlist, newlist_size);
}
return;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
void
OSKext::savePanicString(bool isLoading)
{
u_long len;
if (!kmod_info) {
return; // do not goto finish here b/c of lock
}
len = assemble_identifier_and_version( kmod_info,
(isLoading) ? last_loaded_str_buf : last_unloaded_str_buf,
(isLoading) ? sizeof(last_loaded_str_buf) : sizeof(last_unloaded_str_buf));
if (!len) {
printf("error saving unloaded kext info\n");
goto finish;
}
if (isLoading) {
last_loaded_strlen = len;
last_loaded_address = (void *)kmod_info->address;
last_loaded_size = kmod_info->size;
clock_get_uptime(&last_loaded_timestamp);
} else {
last_unloaded_strlen = len;
last_unloaded_address = (void *)kmod_info->address;
last_unloaded_size = kmod_info->size;
clock_get_uptime(&last_unloaded_timestamp);
}
finish:
return;
}
/*********************************************************************
*********************************************************************/
/* static */
void
OSKext::printKextPanicLists(int (*printf_func)(const char *fmt, ...))
{
if (last_loaded_strlen) {
printf_func("last started kext at %llu: %.*s (addr %p, size %lu)\n",
AbsoluteTime_to_scalar(&last_loaded_timestamp),
last_loaded_strlen, last_loaded_str_buf,
last_loaded_address, last_loaded_size);
}
if (last_unloaded_strlen) {
printf_func("last stopped kext at %llu: %.*s (addr %p, size %lu)\n",
AbsoluteTime_to_scalar(&last_unloaded_timestamp),
last_unloaded_strlen, last_unloaded_str_buf,
last_unloaded_address, last_unloaded_size);
}
printf_func("loaded kexts:\n");
if (loaded_kext_paniclist &&
pmap_find_phys(kernel_pmap, (addr64_t) (uintptr_t) loaded_kext_paniclist) &&
loaded_kext_paniclist[0]) {
printf_func("%.*s",
strnlen(loaded_kext_paniclist, loaded_kext_paniclist_size),
loaded_kext_paniclist);
} else {
printf_func("(none)\n");
}
return;
}
/*********************************************************************
* Assumes sKextLock is held.
*********************************************************************/
/* static */
void
OSKext::updateLoadedKextSummaries(void)
{
kern_return_t result = KERN_FAILURE;
OSKextLoadedKextSummaryHeader *summaryHeader = NULL;
OSKextLoadedKextSummaryHeader *summaryHeaderAlloc = NULL;
OSKext *aKext;
vm_map_offset_t start, end;
size_t summarySize = 0;
size_t size;
u_int count;
u_int maxKexts;
u_int i, j;
OSKextActiveAccount * accountingList;
OSKextActiveAccount * prevAccountingList;
uint32_t idx, accountingListAlloc, accountingListCount, prevAccountingListCount;
prevAccountingList = NULL;
prevAccountingListCount = 0;
#if DEVELOPMENT || DEBUG
if (IORecursiveLockHaveLock(sKextLock) == false) {
panic("sKextLock must be held");
}
#endif
IOLockLock(sKextSummariesLock);
count = sLoadedKexts->getCount();
for (i = 0, maxKexts = 0; i < count; ++i) {
aKext = OSDynamicCast(OSKext, sLoadedKexts->getObject(i));
maxKexts += (aKext && aKext->isExecutable());
}
if (!maxKexts) {
goto finish;
}
if (maxKexts < kOSKextTypicalLoadCount) {
maxKexts = kOSKextTypicalLoadCount;
}
/* Calculate the size needed for the new summary headers.
*/
size = sizeof(*gLoadedKextSummaries);
size += maxKexts * sizeof(*gLoadedKextSummaries->summaries);
size = round_page(size);
if (gLoadedKextSummaries == NULL || sLoadedKextSummariesAllocSize < size) {
if (gLoadedKextSummaries) {
kmem_free(kernel_map, (vm_offset_t)gLoadedKextSummaries, sLoadedKextSummariesAllocSize);
gLoadedKextSummaries = NULL;
gLoadedKextSummariesTimestamp = mach_absolute_time();
sLoadedKextSummariesAllocSize = 0;
}
result = kmem_alloc(kernel_map, (vm_offset_t *)&summaryHeaderAlloc, size, VM_KERN_MEMORY_OSKEXT);
if (result != KERN_SUCCESS) {
goto finish;
}
summaryHeader = summaryHeaderAlloc;
summarySize = size;
} else {
summaryHeader = gLoadedKextSummaries;
summarySize = sLoadedKextSummariesAllocSize;
start = (vm_map_offset_t) summaryHeader;
end = start + summarySize;
result = vm_map_protect(kernel_map,
start,
end,
VM_PROT_DEFAULT,
FALSE);
if (result != KERN_SUCCESS) {
goto finish;
}
}
/* Populate the summary header.
*/
bzero(summaryHeader, summarySize);
summaryHeader->version = kOSKextLoadedKextSummaryVersion;
summaryHeader->entry_size = sizeof(OSKextLoadedKextSummary);
/* Populate each kext summary.
*/
count = sLoadedKexts->getCount();
accountingListAlloc = 0;
for (i = 0, j = 0; i < count && j < maxKexts; ++i) {
aKext = OSDynamicCast(OSKext, sLoadedKexts->getObject(i));
if (!aKext || !aKext->isExecutable()) {
continue;
}
aKext->updateLoadedKextSummary(&summaryHeader->summaries[j++]);
summaryHeader->numSummaries++;
accountingListAlloc++;
}
accountingList = IONew(typeof(accountingList[0]), accountingListAlloc);
accountingListCount = 0;
for (i = 0, j = 0; i < count && j < maxKexts; ++i) {
aKext = OSDynamicCast(OSKext, sLoadedKexts->getObject(i));
if (!aKext || !aKext->isExecutable()) {
continue;
}
OSKextActiveAccount activeAccount;
aKext->updateActiveAccount(&activeAccount);
// order by address
for (idx = 0; idx < accountingListCount; idx++) {
if (activeAccount.address < accountingList[idx].address) {
break;
}
}
bcopy(&accountingList[idx], &accountingList[idx + 1], (accountingListCount - idx) * sizeof(accountingList[0]));
accountingList[idx] = activeAccount;
accountingListCount++;
}
assert(accountingListCount == accountingListAlloc);
/* Write protect the buffer and move it into place.
*/
start = (vm_map_offset_t) summaryHeader;
end = start + summarySize;
result = vm_map_protect(kernel_map, start, end, VM_PROT_READ, FALSE);
if (result != KERN_SUCCESS) {
goto finish;
}
gLoadedKextSummaries = summaryHeader;
gLoadedKextSummariesTimestamp = mach_absolute_time();
sLoadedKextSummariesAllocSize = summarySize;
summaryHeaderAlloc = NULL;
/* Call the magic breakpoint function through a static function pointer so
* the compiler can't optimize the function away.
*/
if (sLoadedKextSummariesUpdated) {
(*sLoadedKextSummariesUpdated)();
}
IOSimpleLockLock(sKextAccountsLock);
prevAccountingList = sKextAccounts;
prevAccountingListCount = sKextAccountsCount;
sKextAccounts = accountingList;
sKextAccountsCount = accountingListCount;
IOSimpleLockUnlock(sKextAccountsLock);
finish:
IOLockUnlock(sKextSummariesLock);
/* If we had to allocate a new buffer but failed to generate the summaries,
* free that now.
*/
if (summaryHeaderAlloc) {
kmem_free(kernel_map, (vm_offset_t)summaryHeaderAlloc, summarySize);
}
if (prevAccountingList) {
IODelete(prevAccountingList, typeof(accountingList[0]), prevAccountingListCount);
}
return;
}
/*********************************************************************
*********************************************************************/
void
OSKext::updateLoadedKextSummary(OSKextLoadedKextSummary *summary)
{
OSSharedPtr<OSData> uuid;
strlcpy(summary->name, getIdentifierCString(),
sizeof(summary->name));
uuid = copyUUID();
if (uuid) {
memcpy(summary->uuid, uuid->getBytesNoCopy(), sizeof(summary->uuid));
}
if (flags.builtin) {
// this value will stop lldb from parsing the mach-o header
// summary->address = UINT64_MAX;
// summary->size = 0;
summary->address = kmod_info->address;
summary->size = kmod_info->size;
} else {
summary->address = kmod_info->address;
summary->size = kmod_info->size;
}
summary->version = getVersion();
summary->loadTag = kmod_info->id;
summary->flags = 0;
summary->reference_list = (uint64_t) kmod_info->reference_list;
summary->text_exec_address = (uint64_t) getsegdatafromheader((kernel_mach_header_t *)summary->address, "__TEXT_EXEC", &summary->text_exec_size);
if (summary->text_exec_address == 0) {
// Fallback to __TEXT
summary->text_exec_address = (uint64_t) getsegdatafromheader((kernel_mach_header_t *)summary->address, "__TEXT", &summary->text_exec_size);
}
return;
}
/*********************************************************************
*********************************************************************/
void
OSKext::updateActiveAccount(OSKextActiveAccount *accountp)
{
kernel_mach_header_t *hdr = NULL;
kernel_segment_command_t *seg = NULL;
bzero(accountp, sizeof(*accountp));
hdr = (kernel_mach_header_t *)kmod_info->address;
if (getcommandfromheader(hdr, LC_SEGMENT_SPLIT_INFO) || isInFileset()) {
/*
* If this kext supports split segments (or is in a new
* MH_FILESET kext collection), use the first
* executable segment as the range for instructions
* (and thus for backtracing.
*/
for (seg = firstsegfromheader(hdr); seg != NULL; seg = nextsegfromheader(hdr, seg)) {
if (seg->initprot & VM_PROT_EXECUTE) {
break;
}
}
}
if (seg) {
accountp->address = seg->vmaddr;
if (accountp->address) {
accountp->address_end = seg->vmaddr + seg->vmsize;
}
} else {
/* For non-split kexts and for kexts without executable
* segments, just use the kmod_info range (as the kext
* is either all in one range or should not show up in
* instruction backtraces).
*/
accountp->address = kmod_info->address;
if (accountp->address) {
accountp->address_end = kmod_info->address + kmod_info->size;
}
}
accountp->account = this->account;
}
bool
OSKext::isDriverKit(void)
{
OSString *bundleType;
if (infoDict) {
bundleType = OSDynamicCast(OSString, infoDict->getObject(kCFBundlePackageTypeKey));
if (bundleType && bundleType->isEqualTo(kOSKextBundlePackageTypeDriverKit)) {
return TRUE;
}
}
return FALSE;
}
bool
OSKext::isInFileset(void)
{
if (!kmod_info) {
goto check_prelinked;
}
if (kmod_info->address && kernel_mach_header_is_in_fileset((kernel_mach_header_t *)kmod_info->address)) {
return true;
}
check_prelinked:
if (isPrelinked()) {
/*
* If we haven't setup kmod_info yet, but we know
* we're loading a prelinked kext in an MH_FILESET KC,
* then return true
*/
kc_format_t kc_format;
if (PE_get_primary_kc_format(&kc_format) && kc_format == KCFormatFileset) {
return true;
}
}
return false;
}
bool
OSKextSavedMutableSegment::initWithSegment(kernel_segment_command_t *seg)
{
kern_return_t result;
if (!super::init()) {
return false;
}
if (seg == nullptr) {
return false;
}
result = kmem_alloc_pageable(kernel_map, (vm_offset_t *)&data, seg->vmsize, VM_KERN_MEMORY_KEXT);
if (result != KERN_SUCCESS) {
return false;
}
memcpy((void *)data, (const void *)seg->vmaddr, seg->vmsize);
savedSegment = seg;
vmsize = seg->vmsize;
vmaddr = seg->vmaddr;
return true;
}
OSSharedPtr<OSKextSavedMutableSegment>
OSKextSavedMutableSegment::withSegment(kernel_segment_command_t *seg)
{
OSSharedPtr<OSKextSavedMutableSegment> me = OSMakeShared<OSKextSavedMutableSegment>();
if (me && !me->initWithSegment(seg)) {
return nullptr;
}
return me;
}
void
OSKextSavedMutableSegment::free(void)
{
if (data) {
kmem_free(kernel_map, (vm_offset_t)data, vmsize);
}
}
vm_offset_t
OSKextSavedMutableSegment::getVMAddr() const
{
return vmaddr;
}
vm_offset_t
OSKextSavedMutableSegment::getVMSize() const
{
return vmsize;
}
OSReturn
OSKextSavedMutableSegment::restoreContents(kernel_segment_command_t *seg)
{
if (seg != savedSegment) {
return kOSKextReturnInvalidArgument;
}
if (seg->vmaddr != vmaddr || seg->vmsize != vmsize) {
return kOSKextReturnInvalidArgument;
}
memcpy((void *)seg->vmaddr, data, vmsize);
return kOSReturnSuccess;
}
extern "C" kern_return_t
OSKextSetReceiptQueried(void)
{
OSKextLog(/* kext */ NULL,
kOSKextLogStepLevel | kOSKextLogGeneralFlag,
"Setting kext receipt as queried");
IOService::publishResource(kOSKextReceiptQueried, kOSBooleanTrue);
return KERN_SUCCESS;
}
extern "C" const vm_allocation_site_t *
OSKextGetAllocationSiteForCaller(uintptr_t address)
{
OSKextActiveAccount * active;
vm_allocation_site_t * site;
vm_allocation_site_t * releasesite;
uint32_t baseIdx;
uint32_t lim;
#if __has_feature(ptrauth_calls)
address = (uintptr_t)VM_KERNEL_STRIP_PTR(address);
#endif /* __has_feature(ptrauth_calls) */
IOSimpleLockLock(sKextAccountsLock);
site = releasesite = NULL;
// bsearch sKextAccounts list
for (baseIdx = 0, lim = sKextAccountsCount; lim; lim >>= 1) {
active = &sKextAccounts[baseIdx + (lim >> 1)];
if ((address >= active->address) && (address < active->address_end)) {
site = &active->account->site;
if (!site->tag) {
vm_tag_alloc_locked(site, &releasesite);
}
break;
} else if (address > active->address) {
// move right
baseIdx += (lim >> 1) + 1;
lim--;
}
// else move left
}
IOSimpleLockUnlock(sKextAccountsLock);
if (releasesite) {
kern_allocation_name_release(releasesite);
}
return site;
}
extern "C" uint32_t
OSKextGetKmodIDForSite(const vm_allocation_site_t * site, char * name, vm_size_t namelen)
{
OSKextAccount * account = (typeof(account))site;
const char * kname;
if (name) {
if (account->kext) {
kname = account->kext->getIdentifierCString();
} else {
kname = "<>";
}
strlcpy(name, kname, namelen);
}
return account->loadTag;
}
extern "C" void
OSKextFreeSite(vm_allocation_site_t * site)
{
OSKextAccount * freeAccount = (typeof(freeAccount))site;
IODelete(freeAccount, OSKextAccount, 1);
}
/*********************************************************************
*********************************************************************/
#if CONFIG_IMAGEBOOT
int
OSKextGetUUIDForName(const char *name, uuid_t uuid)
{
OSSharedPtr<OSKext> kext = OSKext::lookupKextWithIdentifier(name);
if (!kext) {
return 1;
}
OSSharedPtr<OSData> uuid_data = kext->copyUUID();
if (uuid_data) {
memcpy(uuid, uuid_data->getBytesNoCopy(), sizeof(uuid_t));
return 0;
}
return 1;
}
#endif
static int
sysctl_willuserspacereboot
(__unused struct sysctl_oid *oidp, __unused void *arg1, __unused int arg2, struct sysctl_req *req)
{
int new_value = 0, old_value = 0, changed = 0;
int error = sysctl_io_number(req, old_value, sizeof(int), &new_value, &changed);
if (error) {
return error;
}
if (changed) {
OSKext::willUserspaceReboot();
}
return 0;
}
static SYSCTL_PROC(_kern, OID_AUTO, willuserspacereboot,
CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED,
NULL, 0, sysctl_willuserspacereboot, "I", "");