/* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights * Reserved. 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 1.0 (the 'License'). You may not use this file * except in compliance with the License. Please obtain a copy of the * License at http://www.apple.com/publicsource 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 OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License." * * @APPLE_LICENSE_HEADER_END@ */ /*- * Copyright (c) 1997 Doug Rabson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Original code from: * "kldload.c,v 1.5 1998/07/06 06:58:32 charnier Exp" */ #ifndef lint static const char rcsid[] = "$Id: kmodload.c,v 1.10 2001/08/02 20:57:01 lindak Exp $"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kld_patch.h" #define KMOD_ERROR_USAGE 1 #define KMOD_ERROR_PERMS 2 #define KMOD_ERROR_LOADING 3 #define KMOD_ERROR_INTERNAL 4 #define KMOD_ERROR_ALREADY 5 #define kKMOD_INFO_SYMBOLNAME "_kmod_info" #define kKmodsymsName "kmodsyms" static mach_port_t kernel_port; static mach_port_t kernel_priv_port; static kmod_info_t *module_dependencies = 0; static vm_address_t kernel_alloc_address = 0; static unsigned long kernel_alloc_size = 0; static vm_address_t kernel_load_address = 0; static unsigned long kernel_load_size = 0; static unsigned long kernel_hdr_size = 0; static unsigned long kernel_hdr_pad = 0; static unsigned long faked_kernel_load_address = 0; static kmod_info_t *loaded_modules = 0; static int loaded_count = 0; static char *progname = "program name?"; static int kmodsyms = 0; static int link_addrs_set = 0; static int verbose = 0; static char *debugdumpfile = NULL; // must not be static; kld library calls extern void kld_error_vprintf(const char *format, va_list ap); static void e_printf(const char *fmt, ...); static void v_printf(const char *fmt, ...); static void machwarn(int error, const char *message); static void macherr(int error, const char *message); static unsigned long linkedit_address(unsigned long size, unsigned long headers_size); static void abort_load(int exitcode, const char *fmt, ...); static void map_and_patch(const char *base, const char **library_paths, const char *module); static void link_base(const char *base, const char **dependency_paths, const vm_address_t *dependency_addrs); static void clear_globals(void); static kmod_info_t *map_module(const char *filename); static struct mach_header *link_module(const char *filename, const char *output); static vm_address_t update_kmod_info(struct mach_header *mach_header); static kmod_t load_module(struct mach_header *mach_header, vm_address_t info); static void set_module_dependencies(kmod_t id); static void start_module(kmod_t id); static void usage(void) { if (kmodsyms) { fprintf(stderr, "usage: %s [-v] [-k kernelfile] [-d dependencyfile] -o symbolfile modulefile\n", progname); fprintf(stderr, " %s [-v] -k kernelfile [-d dependencyfile@address] -o symbolfile modulefile@address\n", progname); } else { fprintf(stderr, "usage: %s [-v] [-k kernelfile] [-d dependencyfile] [-o symbolfile] modulefile\n", progname); } fflush(stderr); exit(KMOD_ERROR_USAGE); } int main(int argc, char** argv) { int c, r, i; char * kernel = _PATH_UNIX; int kernel_set = 0; char * gdbfile = 0; #define MAX_DEPENDANCIES 128 char * dependencies[MAX_DEPENDANCIES]; vm_address_t loaded_addresses[MAX_DEPENDANCIES]; int dependency_count = 0; struct mach_header *rld_header; char * module_path = ""; vm_address_t module_info = 0; vm_address_t module_faked_address = 0; kmod_t module_id = 0; kmod_info_t *file_kinfo; if ((progname = strrchr(argv[0], '/')) == NULL) progname = argv[0]; else ++progname; kmodsyms = !strcmp(progname, kKmodsymsName); // XXX things to add: // -p data string to send as outofband data on start // -P data file to send as outofband data on start while ((c = getopt(argc, argv, "D:d:o:k:v")) != -1) switch (c) { case 'd': dependencies[dependency_count] = optarg; if (kmodsyms) { char *address; if ((address = strrchr(optarg, '@'))) { *address++ = 0; loaded_addresses[dependency_count] = strtoul(address, NULL, 0); link_addrs_set++; } else { loaded_addresses[dependency_count] = 0; } } if (++dependency_count == MAX_DEPENDANCIES) { abort_load(KMOD_ERROR_INTERNAL, "internal error, dependency count overflow."); } break; case 'o': gdbfile = optarg; break; case 'k': kernel_set++; kernel = optarg; break; case 'v': verbose = 1; break; case 'D': debugdumpfile = optarg; break; default: usage(); } argc -= optind; argv += optind; dependencies[dependency_count] = 0; loaded_addresses[dependency_count] = 0; if (argc != 1) usage(); module_path = argv[0]; if (kmodsyms) { char *address; if (!gdbfile) usage(); // check for @address if ((address = strrchr(module_path, '@'))) { *address++ = 0; module_faked_address = strtoul(address, NULL, 0); link_addrs_set++; } else { module_faked_address = 0; } // if any arg uses @address then they all must be and the kernel must be set if (link_addrs_set) { if (!kernel_set) usage(); if (!module_faked_address) usage(); for (i=0; i < dependency_count; i++) { if (!loaded_addresses[i]) usage(); } } } // map the module if possible, map_module will fail if there is a problem file_kinfo = map_module(module_path); if (!link_addrs_set) { kmod_info_t *k; // we only need the kernel port if we need to lookup loaded kmods r = task_for_pid(mach_task_self(), 0, &kernel_port); machwarn(r, "unable to get kernel task port"); if (KERN_SUCCESS != r) { abort_load(KMOD_ERROR_PERMS, "You must be running as root to load modules in the kernel."); } //get loaded modules r = kmod_get_info(kernel_port, (void *)&loaded_modules, &loaded_count); // never freed macherr(r, "kmod_get_info() failed"); // check to see if the module has been loaded k = loaded_modules; while (k) { if (!strcmp(k->name, file_kinfo->name)) { if (!kmodsyms) { abort_load(KMOD_ERROR_ALREADY, "the module named '%s' is already loaded.", k->name); } else { module_faked_address = k->address; } break; } k = (k->next) ? (k + 1) : 0; } if (kmodsyms && !module_faked_address) { abort_load(KMOD_ERROR_USAGE, "the module named '%s' has not been loaded.", file_kinfo->name); } //XXX it would be nice to be able to verify this is the correct kernel //XXX by comparing the kernel version strings (once we have them) } map_and_patch(kernel, dependencies, module_path); if (debugdumpfile) kld_file_debug_dump(module_path, debugdumpfile); // Tell the kld linker where to get its load address from kld_address_func(linkedit_address); // link the kernel along with any dependencies link_base(kernel, dependencies, loaded_addresses); if (kmodsyms) { faked_kernel_load_address = module_faked_address; if (!faked_kernel_load_address) { abort_load(KMOD_ERROR_INTERNAL, "internal error, fell thru without setting module load address."); } } rld_header = link_module(module_path, gdbfile); module_info = update_kmod_info(rld_header); if (kmodsyms) return 0; // we need the priv port to load modules into the kernel kernel_priv_port = mach_host_self(); /* if we are privileged */ module_id = load_module(rld_header, module_info); set_module_dependencies(module_id); start_module(module_id); return 0; } static void machwarn(int error, const char *message) { if (KERN_SUCCESS != error) e_printf("%s: %s", message, mach_error_string(error)); } static void macherr(int error, const char *message) { if (KERN_SUCCESS != error) abort_load(KMOD_ERROR_INTERNAL, "%s: %s", message, mach_error_string(error)); } static kmod_info_t *map_module(const char *filename) { kmod_info_t *file_kinfo; if (!kld_file_map(filename)) exit(KMOD_ERROR_LOADING); file_kinfo = kld_file_lookupsymbol(filename, kKMOD_INFO_SYMBOLNAME); if (!file_kinfo) { abort_load(KMOD_ERROR_USAGE, "%s is not a valid kernel module.", filename); } return file_kinfo; } static void map_and_patch(const char *base, const char **library_paths, const char *module) { if (!kld_file_map(base)) exit(KMOD_ERROR_INTERNAL); if (!kld_file_merge_OSObjects(base)) abort_load(KMOD_ERROR_LOADING, NULL); if (*library_paths) { char **library; for (library = library_paths; *library; library++) { map_module(*library); if (!kld_file_patch_OSObjects(*library)) abort_load(KMOD_ERROR_LOADING, NULL); } } // Patch the vtables of the object module we are about to load // The module has already been mapped in the main() routine as part // of validation if (!kld_file_patch_OSObjects(module)) abort_load(KMOD_ERROR_LOADING, NULL); // During the patch up process the mapped images were modified // to avoid having to allocate more data than necessary. // Now we have to give the patcher a chance to clean up after itself. if (!kld_file_prepare_for_link()) abort_load(KMOD_ERROR_LOADING, NULL); } static void link_base(const char *base, const char **dependency_paths, const vm_address_t *dependency_addrs) { struct mach_header *rld_header; char *base_addr; long base_size; int ok; // Get the address and size of the base, usually the kernel base_addr = kld_file_getaddr(base, &base_size); if (!base_addr) exit(KMOD_ERROR_INTERNAL); // Error reported by kld library. ok = kld_load_basefile_from_memory(base, base_addr, base_size); fflush(stdout); if (!ok) abort_load(KMOD_ERROR_LOADING, "kld_load_basefile(%s) failed.", base); if (*dependency_paths) { char **dependency = dependency_paths; const vm_address_t *load_addr = dependency_addrs; while (*dependency) { kmod_info_t *file_kinfo; // Find the kmod_info structure in the image. file_kinfo = kld_file_lookupsymbol(*dependency, kKMOD_INFO_SYMBOLNAME); if (!file_kinfo) { abort_load(KMOD_ERROR_USAGE, "%s is not a valid kernel module.", *dependency); } // find the address that this dependency is loaded at if (kmodsyms && *load_addr) { faked_kernel_load_address = *load_addr; } else { kmod_info_t *k; kmod_info_t *tmp; int found_it = 0; // match up file version of kmod_info with kernel version k = loaded_modules; while (k) { if (!strcmp(k->name, file_kinfo->name)) { if (strcmp(k->version, file_kinfo->version)) { e_printf("loaded kernel module '%s' version differs.", *dependency); abort_load(KMOD_ERROR_LOADING, "loaded version '%s', file version '%s'.", k->version, file_kinfo->version); } found_it++; break; } k = (k->next) ? (k + 1) : 0; } if (!found_it) { abort_load(KMOD_ERROR_USAGE, "kernel module '%s' is not loaded.", *dependency); } tmp = malloc(sizeof(kmod_info_t)); if (!tmp) abort_load(KMOD_ERROR_LOADING, "no memory."); *tmp = *k; tmp->next = module_dependencies; module_dependencies = tmp; faked_kernel_load_address = k->address; } rld_header = link_module(*dependency, 0); (void) update_kmod_info(rld_header); dependency++; load_addr++; } /* make sure we clear these so clean up does the right thing. */ clear_globals(); } } #if !defined(page_round) #define page_trunc(p) ((int)(p)&~(vm_page_size-1)) #define page_round(p) page_trunc((int)(p)+vm_page_size-1) #endif static unsigned long linkedit_address(unsigned long size, unsigned long headers_size) { int r; unsigned long round_segments_size; unsigned long round_headers_size; unsigned long round_size; kernel_load_size = size; // The actual size allocated by kld_load... round_headers_size = page_round(headers_size); round_segments_size = page_round(size - headers_size); round_size = round_headers_size + round_segments_size; kernel_alloc_size = round_size; kernel_hdr_size = headers_size; // will need to be rounded to page *after* link. kernel_hdr_pad = round_headers_size - headers_size; if (faked_kernel_load_address) { kernel_load_address = faked_kernel_load_address + kernel_hdr_pad; v_printf("Returning fake load address of 0x%8x", kernel_load_address); return kernel_load_address; } if (kmodsyms) { abort_load(KMOD_ERROR_INTERNAL, "internal error, almost tried to alloc kernel memory."); } r = vm_allocate(kernel_port, &kernel_alloc_address, kernel_alloc_size, TRUE); macherr(r, "unable to allocate kernel memory"); v_printf("allocated %ld bytes in kernel space at 0x%8x", kernel_alloc_size, kernel_alloc_address); kernel_load_address = kernel_alloc_address + kernel_hdr_pad; v_printf("Returning load address of 0x%x", kernel_load_address); return kernel_load_address; } static void clear_globals(void) { faked_kernel_load_address = 0; kernel_alloc_address = 0; kernel_alloc_size = 0; kernel_load_address = 0; kernel_load_size = 0; kernel_hdr_size = 0; kernel_hdr_pad = 0; return; } static struct mach_header * link_module(const char *filename, const char *output) { struct mach_header *rld_header; char *object_addr; long object_size; int ok; // Get the address of the thined MachO image. object_addr = kld_file_getaddr(filename, &object_size); if (!object_addr) abort_load(KMOD_ERROR_LOADING, NULL); ok = kld_load_from_memory(&rld_header, filename, object_addr, object_size, output); fflush(stdout); if (!ok) abort_load(KMOD_ERROR_LOADING, "kld_load() failed."); return rld_header; } // Update the kmod_info_t structure in the image to be laoded // Side effect of removing the kKMOD_INFO_SYMBOLNAME from the // loaded symbol name space, otherwise we would have a duplicate // defined symbol failure vm_address_t update_kmod_info(struct mach_header *mach_header) { char * symbol = kKMOD_INFO_SYMBOLNAME; kmod_info_t *info; unsigned long value; int ok; ok = kld_lookup(symbol, &value); fflush(stdout); if (!ok) abort_load(KMOD_ERROR_LOADING, "kld_lookup(%s) failed.", symbol); ok = kld_forget_symbol(symbol); fflush(stdout); if (!ok) abort_load(KMOD_ERROR_LOADING, "kld_forget_symbol(%s) failed.", symbol); /* Get the kmod info by translating from the kernel address at value. */ info = (kmod_info_t *)(value - (unsigned long)kernel_load_address + (unsigned long)mach_header); v_printf("kmod name: %s", info->name); v_printf("kmod start @ 0x%x", (vm_address_t)info->start); v_printf("kmod stop @ 0x%x", (vm_address_t)info->stop); /* Record link info in kmod info struct, rounding the hdr_size to fit * the adjustment that was made. */ info->address = kernel_alloc_address; info->size = kernel_alloc_size; info->hdr_size = page_round(kernel_hdr_size); if (!info->start) abort_load(KMOD_ERROR_LOADING, "invalid start address?"); else if (!info->stop) abort_load(KMOD_ERROR_LOADING, "invalid stop address?"); return (vm_address_t)value; } static kmod_t load_module(struct mach_header *mach_header, vm_address_t info) { int r; kmod_t id; vm_address_t vm_buffer = 0; r = vm_allocate(mach_task_self(), &vm_buffer, kernel_alloc_size, TRUE); macherr(r, "unable to vm_allocate() copy buffer"); /* Copy the linked segment data into the page-aligned buffer. * Do not round the header size here. */ bzero((void *)vm_buffer, kernel_alloc_size); memcpy((void *)vm_buffer, mach_header, kernel_hdr_size); memcpy((void *)vm_buffer + page_round(kernel_hdr_size), (void *)((unsigned long)mach_header + kernel_hdr_size), kernel_load_size - kernel_hdr_size); // copy linked header into kernel address space r = vm_write(kernel_port, kernel_alloc_address, vm_buffer, kernel_alloc_size); macherr(r, "unable to write module into kernel memory"); // let the kernel know about it r = kmod_create(kernel_priv_port, info, &id); macherr(r, "unable to register module with kernel"); v_printf("kmod id %d successfully created at 0x%x size %ld.\n", id, kernel_alloc_address, kernel_alloc_size); // FIXME: make sure this happens even on failure vm_deallocate(mach_task_self(), vm_buffer, kernel_alloc_size); return id; } static void set_module_dependencies(kmod_t id) { int r; void * args = 0; int argsCount= 0; kmod_info_t *module = module_dependencies; while (module) { r = kmod_control(kernel_priv_port, KMOD_PACK_IDS(id, module->id), KMOD_CNTL_RETAIN, &args, &argsCount); machwarn(r, "kmod_control(retain) failed"); if (r) { clear_globals(); r = kmod_destroy(kernel_priv_port, id); macherr(r, "kmod_destroy failed"); } v_printf("kmod id %d reference count was sucessfully incremented.", module->id); module = module->next; } } static void start_module(kmod_t id) { int r; void * args = 0; int argsCount= 0; r = kmod_control(kernel_priv_port, id, KMOD_CNTL_START, &args, &argsCount); machwarn(r, "kmod_control(start) failed"); if (r) { clear_globals(); kmod_destroy(kernel_priv_port, id); macherr(r, "kmod_destroy failed"); } v_printf("kmod id %d successfully started.", id); } static void e_printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); kld_error_vprintf(fmt, ap); va_end(ap); } static void v_printf(const char *fmt, ...) { va_list ap; char msg[1024]; if (!verbose) return; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); printf("%s: %s\n", progname, msg); } static void abort_load(int exitcode, const char *fmt, ...) { if (fmt) { va_list ap; va_start(ap, fmt); kld_error_vprintf(fmt, ap); va_end(ap); } if (!faked_kernel_load_address && (kernel_alloc_address || kernel_alloc_size)) { int r; v_printf("freeing %ld bytes in kernel space at 0x%x", kernel_alloc_size, kernel_alloc_address); r = vm_deallocate(kernel_port, kernel_alloc_address, kernel_alloc_size); machwarn(r, "unable to cleanup kernel memory"); } exit(exitcode); } __private_extern__ void kld_error_vprintf(const char *fmt, va_list ap) { char msg[1024]; vsnprintf(msg, sizeof(msg), fmt, ap); fprintf(stderr, "%s: %s", progname, msg); fflush(stderr); }