kext.cpp   [plain text]


/*
 * Copyright (c) 2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (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.
 * 
 * This 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@
 */
#include <libkern/c++/OSContainers.h>
#include <IOKit/IOCatalogue.h>
#include <IOKit/IOLib.h>
#include <libsa/kext.h>
#include <libsa/catalogue.h>

extern "C" {
#include <mach-o/kld.h>
#include <libsa/vers_rsrc.h>
#include <libsa/stdlib.h>
#include <mach/kmod.h>
#include <vm/vm_kern.h>
#include <mach/kern_return.h>
#include <mach-o/fat.h>
#include <mach_loader.h>

#include "kld_patch.h"
#include "dgraph.h"
#include "load.h"
};


extern "C" {
extern kern_return_t
kmod_create_internal(
            kmod_info_t *info,
            kmod_t *id);

extern kern_return_t
kmod_destroy_internal(kmod_t id);

extern kern_return_t
kmod_start_or_stop(
    kmod_t id,
    int start,
    kmod_args_t *data,
    mach_msg_type_number_t *dataCount);

extern kern_return_t kmod_retain(kmod_t id);
extern kern_return_t kmod_release(kmod_t id);

extern void flush_dcache(vm_offset_t addr, unsigned cnt, int phys);
extern void invalidate_icache(vm_offset_t addr, unsigned cnt, int phys);
};

#define DEBUG
#ifdef DEBUG
#define LOG_DELAY(x)    IODelay((x) * 1000000)
#define VTYELLOW  "\033[33m"
#define VTRESET   "\033[0m"
#else
#define LOG_DELAY(x)
#define VTYELLOW
#define VTRESET
#endif /* DEBUG */

/*********************************************************************
*
*********************************************************************/
static
bool getKext(
    const char * bundleid,
    OSDictionary ** plist,
    unsigned char ** code,
    unsigned long * code_size,
    bool * caller_owns_code)
{
    bool result = true;
    OSDictionary * extensionsDict;   // don't release
    OSDictionary * extDict;          // don't release
    OSDictionary * extPlist;         // don't release
    unsigned long code_size_local;

   /* Get the dictionary of startup extensions.
    * This is keyed by module name.
    */
    extensionsDict = getStartupExtensions();
    if (!extensionsDict) {
        IOLog("startup extensions dictionary is missing\n");
        result = false;
        goto finish;
    }

   /* Get the requested extension's dictionary entry and its property
    * list, containing module dependencies.
    */
    extDict = OSDynamicCast(OSDictionary,
        extensionsDict->getObject(bundleid));

    if (!extDict) {
        IOLog("extension \"%s\" cannot be found\n",
           bundleid);
        result = false;
        goto finish;
    }

    if (plist) {
        extPlist = OSDynamicCast(OSDictionary, extDict->getObject("plist"));
        if (!extPlist) {
            IOLog("extension \"%s\" has no info dictionary\n",
                bundleid);
            result = false;
            goto finish;
        }
        *plist = extPlist;
    }

    if (code) {

       /* If asking for code, the caller must provide a return buffer
        * for ownership!
        */
        if (!caller_owns_code) {
            IOLog("getKext(): invalid usage (caller_owns_code not provided)\n");
            result = false;
            goto finish;
        }
    
        *code = 0;
        if (code_size) {
            *code_size = 0;
        }
        *caller_owns_code = false;
    
        *code = (unsigned char *)kld_file_getaddr(bundleid,
            (long *)&code_size_local);
        if (*code) {
            if (code_size) {
                *code_size = code_size_local;
            }
        } else {
            OSData * driverCode = 0; // release only if uncompressing!
    
            driverCode = OSDynamicCast(OSData, extDict->getObject("code"));
            if (driverCode) {
                *code = (unsigned char *)driverCode->getBytesNoCopy();
                if (code_size) {
                    *code_size = driverCode->getLength();
                }
            } else { // Look for compressed code and uncompress it
                OSData * compressedCode = 0;
                compressedCode = OSDynamicCast(OSData,
                    extDict->getObject("compressedCode"));
                if (compressedCode) {
                    if (!uncompressModule(compressedCode, &driverCode)) {
                        IOLog("extension \"%s\": couldn't uncompress code\n",
                            bundleid);
                        LOG_DELAY(1);
                        result = false;
                        goto finish;
                    }
                    *caller_owns_code = true;
                    *code = (unsigned char *)driverCode->getBytesNoCopy();
                    if (code_size) {
                        *code_size = driverCode->getLength();
                    }
                    driverCode->release();
                }
            }
        }
    }

finish:

    return result;
}


/*********************************************************************
*
*********************************************************************/
static
bool verifyCompatibility(OSString * extName, OSString * requiredVersion)
{
    OSDictionary * extPlist;         // don't release
    OSString     * extVersion;       // don't release
    OSString     * extCompatVersion; // don't release
    VERS_version ext_version;
    VERS_version ext_compat_version;
    VERS_version required_version;

    if (!getKext(extName->getCStringNoCopy(), &extPlist, NULL, NULL, NULL)) {
        return false;
    }

    extVersion = OSDynamicCast(OSString,
        extPlist->getObject("CFBundleVersion"));
    if (!extVersion) {
        IOLog("verifyCompatibility(): "
            "Extension \"%s\" has no \"CFBundleVersion\" property.\n",
            extName->getCStringNoCopy());
        return false;
    }

    extCompatVersion = OSDynamicCast(OSString,
        extPlist->getObject("OSBundleCompatibleVersion"));
    if (!extCompatVersion) {
        IOLog("verifyCompatibility(): "
            "Extension \"%s\" has no \"OSBundleCompatibleVersion\" property.\n",
            extName->getCStringNoCopy());
        return false;
    }

    required_version = VERS_parse_string(requiredVersion->getCStringNoCopy());
    if (required_version < 0) {
        IOLog("verifyCompatibility(): "
            "Can't parse required version \"%s\" of dependency %s.\n",
            requiredVersion->getCStringNoCopy(),
            extName->getCStringNoCopy());
        return false;
    }
    ext_version = VERS_parse_string(extVersion->getCStringNoCopy());
    if (ext_version < 0) {
        IOLog("verifyCompatibility(): "
            "Can't parse version \"%s\" of dependency %s.\n",
            extVersion->getCStringNoCopy(),
            extName->getCStringNoCopy());
        return false;
    }
    ext_compat_version = VERS_parse_string(extCompatVersion->getCStringNoCopy());
    if (ext_compat_version < 0) {
        IOLog("verifyCompatibility(): "
            "Can't parse compatible version \"%s\" of dependency %s.\n",
            extCompatVersion->getCStringNoCopy(),
            extName->getCStringNoCopy());
        return false;
    }

    if (required_version > ext_version || required_version < ext_compat_version) {
        return false;
    }

    return true;
}

/*********************************************************************
*********************************************************************/
static
bool kextIsDependency(const char * kext_name, char * is_kernel) {
    bool result = true;
    OSDictionary * extensionsDict = 0;    // don't release
    OSDictionary * extDict = 0;           // don't release
    OSDictionary * extPlist = 0;          // don't release
    OSBoolean * isKernelResourceObj = 0;  // don't release
    OSData * driverCode = 0;              // don't release
    OSData * compressedCode = 0;          // don't release

    if (is_kernel) {
        *is_kernel = false;
    }

   /* Get the dictionary of startup extensions.
    * This is keyed by module name.
    */
    extensionsDict = getStartupExtensions();
    if (!extensionsDict) {
        IOLog("startup extensions dictionary is missing\n");
        result = false;
        goto finish;
    }

   /* Get the requested extension's dictionary entry and its property
    * list, containing module dependencies.
    */
    extDict = OSDynamicCast(OSDictionary,
        extensionsDict->getObject(kext_name));

    if (!extDict) {
        IOLog("extension \"%s\" cannot be found\n",
           kext_name);
        result = false;
        goto finish;
    }

    extPlist = OSDynamicCast(OSDictionary, extDict->getObject("plist"));
    if (!extPlist) {
        IOLog("extension \"%s\" has no info dictionary\n",
            kext_name);
        result = false;
        goto finish;
    }

   /* A kext that is a kernel component is still a dependency, as there
    * are fake kmod entries for them.
    */
    isKernelResourceObj = OSDynamicCast(OSBoolean,
        extPlist->getObject("OSKernelResource"));
    if (isKernelResourceObj && isKernelResourceObj->isTrue()) {
        if (is_kernel) {
            *is_kernel = true;
        }
    }

    driverCode = OSDynamicCast(OSData, extDict->getObject("code"));
    compressedCode = OSDynamicCast(OSData,
        extDict->getObject("compressedCode"));

    if ((driverCode || compressedCode) && is_kernel && *is_kernel) {
	*is_kernel = 2;
    }

    if (!driverCode && !compressedCode && !isKernelResourceObj) {
        result = false;
        goto finish;
    }

finish:

    return result;
}

/*********************************************************************
*********************************************************************/
static bool
figureDependenciesForKext(OSDictionary * kextPlist,
    OSDictionary * dependencies,
    OSString * trueParent)
{
    bool result = true;
    OSString * kextName = 0;  // don't release
    OSDictionary * libraries = 0;  // don't release
    OSCollectionIterator * keyIterator = 0; // must release
    OSString * libraryName = 0; // don't release

    kextName = OSDynamicCast(OSString,
        kextPlist->getObject("CFBundleIdentifier"));
    if (!kextName) {
        // XXX: Add log message
        result = false;
        goto finish;
    }

    libraries = OSDynamicCast(OSDictionary,
        kextPlist->getObject("OSBundleLibraries"));
    if (!libraries) {
        result = true;
        goto finish;
    }

    keyIterator = OSCollectionIterator::withCollection(libraries);
    if (!keyIterator) {
        // XXX: Add log message
        result = false;
        goto finish;
    }

    while ( (libraryName = OSDynamicCast(OSString,
        keyIterator->getNextObject())) ) {

        OSString * libraryVersion = OSDynamicCast(OSString,
            libraries->getObject(libraryName));
        if (!libraryVersion) {
            // XXX: Add log message
            result = false;
            goto finish;
        }
        if (!verifyCompatibility(libraryName, libraryVersion)) {
            result = false;
            goto finish;
        } else {
            dependencies->setObject(libraryName,
                trueParent ? trueParent : kextName);
        }
    }

finish:
    if (keyIterator) keyIterator->release();
    return result;
}

/*********************************************************************
*********************************************************************/
static
bool getVersionForKext(OSDictionary * kextPlist, char ** version)
{
    OSString * kextName = 0;  // don't release
    OSString * kextVersion;       // don't release

    kextName = OSDynamicCast(OSString,
        kextPlist->getObject("CFBundleIdentifier"));
    if (!kextName) {
        // XXX: Add log message
        return false;
    }

    kextVersion = OSDynamicCast(OSString,
        kextPlist->getObject("CFBundleVersion"));
    if (!kextVersion) {
        IOLog("getVersionForKext(): "
            "Extension \"%s\" has no \"CFBundleVersion\" property.\n",
            kextName->getCStringNoCopy());
        return false;
    }

    if (version) {
        *version = (char *)kextVersion->getCStringNoCopy();
    }

    return true;
}

/*********************************************************************
*********************************************************************/
static
bool add_dependencies_for_kmod(const char * kmod_name, dgraph_t * dgraph)
{
    bool result = true;
    OSDictionary * kextPlist = 0; // don't release
    OSDictionary * workingDependencies = 0; // must release
    OSDictionary * pendingDependencies = 0; // must release
    OSDictionary * swapDict = 0; // don't release
    OSString * dependentName = 0; // don't release
    const char * dependent_name = 0;  // don't free
    OSString * libraryName = 0; // don't release
    const char * library_name = 0;  // don't free
    OSCollectionIterator * dependencyIterator = 0; // must release
    unsigned char * code = 0;
    unsigned long code_length = 0;
    bool code_is_kmem = false;
    char * kmod_vers = 0; // from plist, don't free
    char is_kernel_component = false;
    dgraph_entry_t * dgraph_entry = 0; // don't free
    dgraph_entry_t * dgraph_dependency = 0; // don't free
    unsigned int graph_depth = 0;
    bool kext_is_dependency = true;

    if (!getKext(kmod_name, &kextPlist, &code, &code_length,
        &code_is_kmem)) {
        IOLog("can't find extension %s\n", kmod_name);
        result = false;
        goto finish;
    }

    if (!kextIsDependency(kmod_name, &is_kernel_component)) {
        IOLog("extension %s is not loadable\n", kmod_name);
        result = false;
        goto finish;
    }

    if (!getVersionForKext(kextPlist, &kmod_vers)) {
        IOLog("can't get version for extension %s\n", kmod_name);
        result = false;
        goto finish;
    }

    dgraph_entry = dgraph_add_dependent(dgraph, kmod_name,
        code, code_length, code_is_kmem,
        kmod_name, kmod_vers,
        0 /* load_address not yet known */, is_kernel_component);
    if (!dgraph_entry) {
        IOLog("can't record %s in dependency graph\n", kmod_name);
        result = false;
        // kmem_alloc()ed code is freed in finish: block.
        goto finish;
    }

    // pass ownership of code to kld patcher
    if (code)
    {
        if (kload_map_entry(dgraph_entry) != kload_error_none) {
            IOLog("can't map %s in preparation for loading\n", kmod_name);
            result = false;
            // kmem_alloc()ed code is freed in finish: block.
           goto finish;
        }
    }
    // clear local record of code
    code = 0;
    code_length = 0;
    code_is_kmem = false;

    workingDependencies = OSDictionary::withCapacity(5);
    if (!workingDependencies) {
        IOLog("memory allocation failure\n");
        result = false;
        goto finish;
    }

    pendingDependencies = OSDictionary::withCapacity(5);
    if (!pendingDependencies) {
        IOLog("memory allocation failure\n");
        result = false;
        goto finish;
    }

    if (!figureDependenciesForKext(kextPlist, workingDependencies, NULL)) {
        IOLog("can't determine immediate dependencies for extension %s\n",
            kmod_name);
        result = false;
        goto finish;
    }

    graph_depth = 0;
    while (workingDependencies->getCount()) {
        if (graph_depth > 255) {
            IOLog("extension dependency graph ridiculously long, indicating a loop\n");
            result = false;
            goto finish;
        }

        if (dependencyIterator) {
            dependencyIterator->release();
            dependencyIterator = 0;
        }

        dependencyIterator = OSCollectionIterator::withCollection(
            workingDependencies);
        if (!dependencyIterator) {
            IOLog("memory allocation failure\n");
            result = false;
            goto finish;
        }

        while ( (libraryName =
                 OSDynamicCast(OSString, dependencyIterator->getNextObject())) ) {

            library_name = libraryName->getCStringNoCopy();

            dependentName = OSDynamicCast(OSString,
                workingDependencies->getObject(libraryName));

            dependent_name = dependentName->getCStringNoCopy();

            if (!getKext(library_name, &kextPlist, NULL, NULL, NULL)) {
                IOLog("can't find extension %s\n", library_name);
                result = false;
                goto finish;
            }

	    OSString * string;
	    if ((string = OSDynamicCast(OSString,
			    kextPlist->getObject("OSBundleSharedExecutableIdentifier"))))
	    {
		library_name = string->getCStringNoCopy();
		if (!getKext(library_name, &kextPlist, NULL, NULL, NULL)) {
		    IOLog("can't find extension %s\n", library_name);
		    result = false;
		    goto finish;
		}
	    }

            kext_is_dependency = kextIsDependency(library_name,
                &is_kernel_component);

            if (!kext_is_dependency) {

               /* For binaryless kexts, add a new pending dependency from the
                * original dependent onto the dependencies of the current,
                * binaryless, dependency.
                */
                if (!figureDependenciesForKext(kextPlist, pendingDependencies,
                    dependentName)) {

                    IOLog("can't determine immediate dependencies for extension %s\n",
                        library_name);
                    result = false;
                    goto finish;
                }
                continue;
            } else {
                dgraph_entry = dgraph_find_dependent(dgraph, dependent_name);
                if (!dgraph_entry) {
                    IOLog("internal error with dependency graph\n");
                    LOG_DELAY(1);
                    result = false;
                    goto finish;
                }

                if (!getVersionForKext(kextPlist, &kmod_vers)) {
                    IOLog("can't get version for extension %s\n", library_name);
                    result = false;
                    goto finish;
                }

               /* It's okay for code to be zero, as for a pseudokext
                * representing a kernel component.
                */
                if (!getKext(library_name, NULL /* already got it */,
                    &code, &code_length, &code_is_kmem)) {
                    IOLog("can't find extension %s\n", library_name);
                    result = false;
                    goto finish;
                }

                dgraph_dependency = dgraph_add_dependency(dgraph, dgraph_entry,
                    library_name, code, code_length, code_is_kmem,
                    library_name, kmod_vers,
                    0 /* load_address not yet known */, is_kernel_component);

                if (!dgraph_dependency) {
                    IOLog("can't record dependency %s -> %s\n", dependent_name,
                        library_name);
                    result = false;
                    // kmem_alloc()ed code is freed in finish: block.
                    goto finish;
                }

                // pass ownership of code to kld patcher
                if (code) {
                    if (kload_map_entry(dgraph_dependency) != kload_error_none) {
                        IOLog("can't map %s in preparation for loading\n", library_name);
                        result = false;
                        // kmem_alloc()ed code is freed in finish: block.
                        goto finish;
                    }
                }
                // clear local record of code
                code = 0;
                code_length = 0;
                code_is_kmem = false;
            }

           /* Now put the library's dependencies onto the pending set.
            */
            if (!figureDependenciesForKext(kextPlist, pendingDependencies,
                NULL)) {

                IOLog("can't determine immediate dependencies for extension %s\n",
                    library_name);
                result = false;
                goto finish;
            }
        }

        dependencyIterator->release();
        dependencyIterator = 0;

        workingDependencies->flushCollection();
        swapDict = workingDependencies;
        workingDependencies = pendingDependencies;
        pendingDependencies = swapDict;
        graph_depth++;
    }

finish:
    if (code && code_is_kmem) {
        kmem_free(kernel_map, (unsigned int)code, code_length);
    }
    if (workingDependencies)  workingDependencies->release();
    if (pendingDependencies)  pendingDependencies->release();
    if (dependencyIterator)   dependencyIterator->release();
    return result;
}

/*********************************************************************
* This is the function that IOCatalogue calls in order to load a kmod.
* It first checks whether the kmod is already loaded. If the kmod
* isn't loaded, this function builds a dependency list and calls
* load_kmod() repeatedly to guarantee that each dependency is in fact
* loaded.
*********************************************************************/
__private_extern__
kern_return_t load_kernel_extension(char * kmod_name)
{
    kern_return_t result = KERN_SUCCESS;
    kload_error load_result = kload_error_none;
    dgraph_t dgraph;
    bool free_dgraph = false;
    kmod_info_t * kmod_info;

// Put this in for lots of messages about kext loading.
#if 0
    kload_set_log_level(kload_log_level_load_details);
#endif

   /* See if the kmod is already loaded.
    */
    if ((kmod_info = kmod_lookupbyname_locked(kmod_name))) {
	kfree((vm_offset_t) kmod_info, sizeof(kmod_info_t));
        return KERN_SUCCESS;
    }

    if (dgraph_init(&dgraph) != dgraph_valid) {
        IOLog("Can't initialize dependency graph to load %s.\n",
            kmod_name);
        result = KERN_FAILURE;
        goto finish;
    }

    free_dgraph = true;
    if (!add_dependencies_for_kmod(kmod_name, &dgraph)) {
        IOLog("Can't determine dependencies for %s.\n",
            kmod_name);
        result = KERN_FAILURE;
        goto finish;
    }

    dgraph.root = dgraph_find_root(&dgraph);

    if (!dgraph.root) {
        IOLog("Dependency graph to load %s has no root.\n",
            kmod_name);
        result = KERN_FAILURE;
        goto finish;
    }

   /* A kernel component is built in and need not be loaded.
    */
    if (dgraph.root->is_kernel_component) {
        result = KERN_SUCCESS;
        goto finish;
    }

    dgraph_establish_load_order(&dgraph);

    load_result = kload_load_dgraph(&dgraph);
    if (load_result != kload_error_none &&
        load_result != kload_error_already_loaded) {

        IOLog(VTYELLOW "Failed to load extension %s.\n" VTRESET, kmod_name);

        result = KERN_FAILURE;
        goto finish;
    }

finish:

    if (free_dgraph) {
        dgraph_free(&dgraph, 0 /* don't free dgraph itself */);
    }
    return result;
}