-#include <mach-o/mach_header.h>
-#include <mach-o/loader.h>
-#include <mach-o/nlist.h>
-
-#include <mach/kext_panic_report.h>
-
-/*
- * XXX headers for which prototypes should be in a common include file;
- * XXX see libsa/kext.cpp for why.
- */
-kern_return_t kmod_create_internal(kmod_info_t *info, kmod_t *id);
-kern_return_t kmod_destroy_internal(kmod_t id);
-kern_return_t kmod_start_or_stop(kmod_t id, int start, kmod_args_t *data,
- mach_msg_type_number_t *dataCount);
-kern_return_t kmod_retain(kmod_t id);
-kern_return_t kmod_release(kmod_t id);
-kern_return_t kmod_queue_cmd(vm_address_t data, vm_size_t size);
-kern_return_t kmod_get_info(host_t host, kmod_info_array_t *kmods,
- mach_msg_type_number_t *kmodCount);
-
-static kern_return_t kmod_get_symbol_data(kmod_args_t * data,
- mach_msg_type_number_t * dataCount);
-static kern_return_t kmod_free_linkedit_data(void);
-static kern_return_t kmod_get_kext_uuid(
- const char * kext_id,
- kmod_args_t * data,
- mach_msg_type_number_t * dataCount);
-
-extern int IODTGetLoaderInfo(const char * key, void ** infoAddr, vm_size_t * infoSize);
-extern void IODTFreeLoaderInfo(const char * key, void * infoAddr, vm_size_t infoSize);
-/* operates on 32 bit segments */
-extern void OSRuntimeUnloadCPPForSegment(struct segment_command * segment);
-
-#define WRITE_PROTECT_MODULE_TEXT (0)
-
-kmod_info_t *kmod;
-static int kmod_index = 1;
-static int kmod_load_disabled = 0;
-
-mutex_t * kmod_lock = 0;
-static mutex_t * kmod_queue_lock = 0;
-
-typedef struct cmd_queue_entry {
- queue_chain_t links;
- vm_address_t data;
- vm_size_t size;
-} cmd_queue_entry_t;
-
-queue_head_t kmod_cmd_queue;
-
-/*******************************************************************************
-*******************************************************************************/
-#define KMOD_PANICLIST_SIZE (2 * PAGE_SIZE)
-
-char * unloaded_kext_paniclist = NULL;
-uint32_t unloaded_kext_paniclist_size = 0;
-uint32_t unloaded_kext_paniclist_length = 0;
-uint64_t last_loaded_timestamp = 0;
-
-char * loaded_kext_paniclist = NULL;
-uint32_t loaded_kext_paniclist_size = 0;
-uint32_t loaded_kext_paniclist_length = 0;
-uint64_t last_unloaded_timestamp = 0;
-
-int substitute(
- const char * scan_string,
- char * string_out,
- uint32_t * to_index,
- uint32_t * from_index,
- const char * substring,
- char marker,
- char substitution);
-
-/* identifier_out must be at least KMOD_MAX_NAME bytes.
- */
-int substitute(
- const char * scan_string,
- char * string_out,
- uint32_t * to_index,
- uint32_t * from_index,
- const char * substring,
- char marker,
- char substitution)
-{
- uint32_t substring_length = strnlen(substring, KMOD_MAX_NAME - 1);
-
- 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;
-}
-
-void compactIdentifier(
- const char * identifier,
- char * identifier_out,
- char ** identifier_out_end);
-
-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.
- */
- 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.
- */
- 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 (!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;
-}
-
-/* identPlusVers must be at least 2*KMOD_MAX_NAME in length.
- */
-int assemble_identifier_and_version(
- kmod_info_t * kmod_info,
- char * identPlusVers);
-int assemble_identifier_and_version(
- kmod_info_t * kmod_info,
- char * identPlusVers)
-{
- int result = 0;
-
- compactIdentifier(kmod_info->name, identPlusVers, NULL);
- result = strnlen(identPlusVers, KMOD_MAX_NAME - 1);
- identPlusVers[result++] = '\t'; // increment for real char
- identPlusVers[result] = '\0'; // don't increment for nul char
- result = strlcat(identPlusVers, kmod_info->version, KMOD_MAX_NAME);
-
- return result;
-}
-
-#define LAST_LOADED " - last loaded "
-#define LAST_LOADED_TS_WIDTH (16)
-
-uint32_t save_loaded_kext_paniclist_typed(
- const char * prefix,
- int invertFlag,
- int libsFlag,
- char * paniclist,
- uint32_t list_size,
- uint32_t * list_length_ptr,
- int (*printf_func)(const char *fmt, ...));
-uint32_t save_loaded_kext_paniclist_typed(
- const char * prefix,
- int invertFlag,
- int libsFlag,
- char * paniclist,
- uint32_t list_size,
- uint32_t * list_length_ptr,
- int (*printf_func)(const char *fmt, ...))
-{
- uint32_t result = 0;
- int error = 0;
- kmod_info_t * kmod_info;
-
- for (kmod_info = kmod;
- kmod_info && (*list_length_ptr + 1 < list_size);
- kmod_info = kmod_info->next) {
-
- int match;
- char identPlusVers[2*KMOD_MAX_NAME];
- uint32_t identPlusVersLength;
- char timestampBuffer[17]; // enough for a uint64_t
-
- if (!pmap_find_phys(kernel_pmap, (addr64_t)((uintptr_t)kmod_info))) {
- (*printf_func)("kmod scan stopped due to missing kmod page: %p\n",
- kmod_info);
- error = 1;
- goto finish;
- }
-
- /* Skip all built-in/fake entries.
- */
- if (!kmod_info->address) {
- continue;
- }
-
- /* 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. This isn't a strictly correct check,
- * but any kext that does have references to it has to be a library.
- * A kext w/o references may or may not be a library.
- */
- if ((libsFlag == 0 && kmod_info->reference_count) ||
- (libsFlag == 1 && !kmod_info->reference_count)) {
-
- continue;
- }
-
- identPlusVersLength = assemble_identifier_and_version(kmod_info,
- identPlusVers);
- if (!identPlusVersLength) {
- printf_func("error saving loaded kext info\n");
- goto finish;
- }
-
- /* We're going to note the last-loaded kext in the list.
- */
- if (kmod_info == kmod) {
- snprintf(timestampBuffer, sizeof(timestampBuffer), "%llu",
- last_loaded_timestamp);
- identPlusVersLength += sizeof(LAST_LOADED) - 1 +
- strnlen(timestampBuffer, sizeof(timestampBuffer));
- }
-
- /* Adding 1 for the newline.
- */
- if (*list_length_ptr + identPlusVersLength + 1 >= list_size) {
- goto finish;
- }
-
- *list_length_ptr = strlcat(paniclist, identPlusVers, list_size);
- if (kmod_info == kmod) {
- *list_length_ptr = strlcat(paniclist, LAST_LOADED, list_size);
- *list_length_ptr = strlcat(paniclist, timestampBuffer, list_size);
- }
- *list_length_ptr = strlcat(paniclist, "\n", list_size);
- }
-
-finish:
- if (!error) {
- if (*list_length_ptr + 1 <= list_size) {
- result = list_size - (*list_length_ptr + 1);
- }
- }
-
- return result;
-}
-
-void save_loaded_kext_paniclist(
- int (*printf_func)(const char *fmt, ...));
-
-void save_loaded_kext_paniclist(
- int (*printf_func)(const char *fmt, ...))
-{
- char * newlist = NULL;
- uint32_t newlist_size = 0;
- uint32_t newlist_length = 0;
-
- newlist_length = 0;
- newlist_size = KMOD_PANICLIST_SIZE;
- newlist = (char *)kalloc(newlist_size);
-
- if (!newlist) {
- printf_func("couldn't allocate kext panic log buffer\n");
- goto finish;
- }
-
- newlist[0] = '\0';
-
- // non-"com.apple." kexts
- if (!save_loaded_kext_paniclist_typed("com.apple.", /* invert? */ 1,
- /* libs? */ -1, newlist, newlist_size, &newlist_length,
- printf_func)) {
-
- goto finish;
- }
- // "com.apple." nonlibrary kexts
- if (!save_loaded_kext_paniclist_typed("com.apple.", /* invert? */ 0,
- /* libs? */ 0, newlist, newlist_size, &newlist_length,
- printf_func)) {
-
- goto finish;
- }
- // "com.apple." library kexts
- if (!save_loaded_kext_paniclist_typed("com.apple.", /* invert? */ 0,
- /* libs? */ 1, newlist, newlist_size, &newlist_length,
- printf_func)) {
-
- goto finish;
- }
-
- if (loaded_kext_paniclist) {
- kfree(loaded_kext_paniclist, loaded_kext_paniclist_size);
- }
- loaded_kext_paniclist = newlist;
- loaded_kext_paniclist_size = newlist_size;
- loaded_kext_paniclist_length = newlist_length;
-
-finish:
- return;
-}
-
-void save_unloaded_kext_paniclist(
- kmod_info_t * kmod_info,
- int (*printf_func)(const char *fmt, ...));
-void save_unloaded_kext_paniclist(
- kmod_info_t * kmod_info,
- int (*printf_func)(const char *fmt, ...))
-{
- char * newlist = NULL;
- uint32_t newlist_size = 0;
- uint32_t newlist_length = 0;
- char identPlusVers[2*KMOD_MAX_NAME];
- uint32_t identPlusVersLength;
-
- identPlusVersLength = assemble_identifier_and_version(kmod_info,
- identPlusVers);
- if (!identPlusVersLength) {
- printf_func("error saving unloaded kext info\n");
- goto finish;
- }
-
- newlist_length = identPlusVersLength;
- newlist_size = newlist_length + 1;
- newlist = (char *)kalloc(newlist_size);
-
- if (!newlist) {
- printf_func("couldn't allocate kext panic log buffer\n");
- goto finish;
- }
-
- newlist[0] = '\0';
-
- strlcpy(newlist, identPlusVers, newlist_size);
-
- if (unloaded_kext_paniclist) {
- kfree(unloaded_kext_paniclist, unloaded_kext_paniclist_size);
- }
- unloaded_kext_paniclist = newlist;
- unloaded_kext_paniclist_size = newlist_size;
- unloaded_kext_paniclist_length = newlist_length;
-
-finish:
- return;
-}
-
-// proto is in header
-void record_kext_unload(kmod_t kmod_id)
-{
- kmod_info_t * kmod_info = NULL;
-
- mutex_lock(kmod_lock);
-
- kmod_info = kmod_lookupbyid(kmod_id);
- if (kmod_info) {
- clock_get_uptime(&last_unloaded_timestamp);
- save_unloaded_kext_paniclist(kmod_info, &printf);
- }
- mutex_unlock(kmod_lock);
- return;
-}
-
-void dump_kext_info(int (*printf_func)(const char *fmt, ...))
-{
- printf_func("unloaded kexts:\n");
- if (unloaded_kext_paniclist && (pmap_find_phys(kernel_pmap, (addr64_t) (uintptr_t) unloaded_kext_paniclist))) {
- printf_func("%.*s - last unloaded %llu\n",
- unloaded_kext_paniclist_length, unloaded_kext_paniclist,
- last_unloaded_timestamp);
- } else {
- printf_func("(none)\n");
- }
- 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", loaded_kext_paniclist_length, loaded_kext_paniclist);
- } else {
- printf_func("(none)\n");
- }
- return;
-}
-
-/*******************************************************************************
-*******************************************************************************/
-void
-kmod_init(void)
-{
- kmod_lock = mutex_alloc(0);
- kmod_queue_lock = mutex_alloc(0);
- queue_init(&kmod_cmd_queue);
-}
-
-kmod_info_t *
-kmod_lookupbyid(kmod_t id)
-{
- kmod_info_t *k = NULL;
-
- k = kmod;
- while (k) {
- if (k->id == id) break;
- k = k->next;
- }
-
- return k;
-}
-
-kmod_info_t *
-kmod_lookupbyname(const char * name)
-{
- kmod_info_t *k = NULL;
-
- k = kmod;
- while (k) {
- if (!strncmp(k->name, name, sizeof(k->name)))
- break;
- k = k->next;
- }
-
- return k;
-}
-
-// get the id of a kext in a given range, if the address is not in a kext
-// -1 is returned
-int kmod_lookupidbyaddress_locked(vm_address_t addr)
-{
- kmod_info_t *k = 0;
-
- mutex_lock(kmod_queue_lock);
- k = kmod;
- if(NULL != k) {
- while (k) {
- if ((k->address <= addr) && ((k->address + k->size) > addr)) {
- break;
- }
- k = k->next;
- }
- mutex_unlock(kmod_queue_lock);
- } else {
- mutex_unlock(kmod_queue_lock);
- return -1;
- }
-
- if(NULL == k) {
- return -1;
- } else {
- return k->id;
- }
-}
-
-kmod_info_t *
-kmod_lookupbyaddress(vm_address_t addr)
-{
- kmod_info_t *k = 0;
-
- k = kmod;
- while (k) {
- if ((k->address <= addr) && ((k->address + k->size) > addr)) break;
- k = k->next;
- }
-
- return k;
-}
-
-kmod_info_t *
-kmod_lookupbyid_locked(kmod_t id)
-{
- kmod_info_t *k = NULL;
- kmod_info_t *kc = NULL;
-
- kc = (kmod_info_t *)kalloc(sizeof(kmod_info_t));
- if (!kc) return kc;
-
- mutex_lock(kmod_lock);
- k = kmod_lookupbyid(id);
- if (k) {
- bcopy((char*)k, (char *)kc, sizeof(kmod_info_t));
- }
-
- mutex_unlock(kmod_lock);
-
- if (k == 0) {
- kfree(kc, sizeof(kmod_info_t));
- kc = NULL;
- }
- return kc;
-}
-
-kmod_info_t *
-kmod_lookupbyname_locked(const char * name)
-{
- kmod_info_t *k = NULL;
- kmod_info_t *kc = NULL;
-
- kc = (kmod_info_t *)kalloc(sizeof(kmod_info_t));
- if (!kc) return kc;
-
- mutex_lock(kmod_lock);
- k = kmod_lookupbyname(name);
- if (k) {
- bcopy((char *)k, (char *)kc, sizeof(kmod_info_t));
- }
-
- mutex_unlock(kmod_lock);
-
- if (k == 0) {
- kfree(kc, sizeof(kmod_info_t));
- kc = NULL;
- }
- return kc;
-}
-
-// XXX add a nocopy flag??
-
-kern_return_t
-kmod_queue_cmd(vm_address_t data, vm_size_t size)
-{
- kern_return_t rc;
- cmd_queue_entry_t *e = (cmd_queue_entry_t *)kalloc(sizeof(struct cmd_queue_entry));
- if (!e) return KERN_RESOURCE_SHORTAGE;
-
- rc = kmem_alloc(kernel_map, &e->data, size);
- if (rc != KERN_SUCCESS) {
- kfree(e, sizeof(struct cmd_queue_entry));
- return rc;
- }
- e->size = size;
- bcopy((void *)data, (void *)e->data, size);
-
- mutex_lock(kmod_queue_lock);
- enqueue_tail(&kmod_cmd_queue, (queue_entry_t)e);
- mutex_unlock(kmod_queue_lock);
-
- thread_wakeup_one((event_t)&kmod_cmd_queue);
-
- return KERN_SUCCESS;
-}
-
-kern_return_t
-kmod_load_extension(char *name)
-{
- kmod_load_extension_cmd_t data;
-
- if (kmod_load_disabled) {
- return KERN_NO_ACCESS;
- }
-
- data.type = KMOD_LOAD_EXTENSION_PACKET;
- strncpy(data.name, name, sizeof(data.name));
-
- return kmod_queue_cmd((vm_address_t)&data, sizeof(data));
-}
-
-kern_return_t
-kmod_load_extension_with_dependencies(char *name, char **dependencies)
-{
- kern_return_t result;
- kmod_load_with_dependencies_cmd_t * data;
- vm_size_t size;
- char **c;
- int i, count = 0;
-
- if (kmod_load_disabled) {
- return KERN_NO_ACCESS;
- }
-
- c = dependencies;
- if (c) {
- while (*c) {
- count++; c++;
- }
- }
- size = sizeof(int) + KMOD_MAX_NAME * (count + 1) + 1;
- data = (kmod_load_with_dependencies_cmd_t *)kalloc(size);
- if (!data) return KERN_RESOURCE_SHORTAGE;
-
- data->type = KMOD_LOAD_WITH_DEPENDENCIES_PACKET;
- strncpy(data->name, name, KMOD_MAX_NAME);
-
- c = dependencies;
- for (i=0; i < count; i++) {
- strncpy(data->dependencies[i], *c, KMOD_MAX_NAME);
- c++;
- }
- data->dependencies[count][0] = 0;
-
- result = kmod_queue_cmd((vm_address_t)data, size);
- kfree(data, size);
- return result;
-}
-kern_return_t
-kmod_send_generic(int type, void *generic_data, int size)
-{
- kern_return_t result;
- kmod_generic_cmd_t * data;
- vm_size_t cmd_size;
-
- // add sizeof(int) for the type field
- cmd_size = size + sizeof(int);
- data = (kmod_generic_cmd_t *)kalloc(cmd_size);
- if (!data) return KERN_RESOURCE_SHORTAGE;
-
- data->type = type;
- bcopy(data->data, generic_data, size);
-
- result = kmod_queue_cmd((vm_address_t)data, cmd_size);
- kfree(data, cmd_size);
- return result;
-}
-
-extern vm_offset_t sectPRELINKB;
-extern int sectSizePRELINK;
-extern int kth_started;
-
-/*
- * Operates only on 32 bit mach keaders on behalf of kernel module loader
- * if WRITE_PROTECT_MODULE_TEXT is defined.
- */
-kern_return_t
-kmod_create_internal(kmod_info_t *info, kmod_t *id)
-{
- kern_return_t rc;
- boolean_t isPrelink;
-
- if (!info) return KERN_INVALID_ADDRESS;
-
- // double check for page alignment
- if ((info->address | info->hdr_size) & (PAGE_SIZE - 1)) {
- return KERN_INVALID_ADDRESS;
- }
-
- isPrelink = ((info->address >= sectPRELINKB) && (info->address < (sectPRELINKB + sectSizePRELINK)));
- if (!isPrelink && kth_started) {
- rc = vm_map_wire(kernel_map, info->address + info->hdr_size,
- info->address + info->size, VM_PROT_DEFAULT, FALSE);
- if (rc != KERN_SUCCESS) {
- return rc;
- }
- }
-#if WRITE_PROTECT_MODULE_TEXT
- {
- struct section * sect = getsectbynamefromheader(
- (struct mach_header*) info->address, "__TEXT", "__text");
-
- if(sect) {
- (void) vm_map_protect(kernel_map, round_page(sect->addr),
- trunc_page(sect->addr + sect->size),
- VM_PROT_READ|VM_PROT_EXECUTE, TRUE);
- }
- }
-#endif /* WRITE_PROTECT_MODULE_TEXT */
-
- mutex_lock(kmod_lock);
-
- // check to see if already loaded
- if (kmod_lookupbyname(info->name)) {
- mutex_unlock(kmod_lock);
- if (!isPrelink) {
- rc = vm_map_unwire(kernel_map, info->address + info->hdr_size,
- info->address + info->size, FALSE);
- assert(rc == KERN_SUCCESS);
- }
- return KERN_INVALID_ARGUMENT;
- }
-
- info->id = kmod_index++;
- info->reference_count = 0;
-
- info->next = kmod;
- kmod = info;
-
- *id = info->id;
-
- clock_get_uptime(&last_loaded_timestamp);
- save_loaded_kext_paniclist(&printf);
-
- mutex_unlock(kmod_lock);
-
-#if DEBUG
- printf("kmod_create: %s (id %d), %d pages loaded at 0x%x, header size 0x%x\n",
- info->name, info->id, info->size / PAGE_SIZE, info->address, info->hdr_size);
-#endif /* DEBUG */
-
- return KERN_SUCCESS;
-}
-
-
-kern_return_t
-kmod_create(host_priv_t host_priv,
- vm_address_t addr,
- kmod_t *id)
-{
-#ifdef SECURE_KERNEL