1/*
2 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23#include <fcntl.h>
24#include <stdio.h>
25#include <string.h>
26#include <stdarg.h>
27#include <stdlib.h>
28#include <sysexits.h>
29#include <unistd.h>
30
31#include <sys/mman.h>
32#include <sys/param.h>
33#include <sys/stat.h>
34#include <sys/types.h>
35
36#include <mach/mach.h>
37#include <mach/mach_error.h>
38
39#include <mach-o/arch.h>
40
41#include <System/libkern/mkext.h>
42#include <CoreFoundation/CFBundlePriv.h>
43#include <IOKit/kext/OSKextPrivate.h>
44
45#include "kext_tools_util.h"
46#include "compression.h"
47
48
49/*******************************************************************************
50*******************************************************************************/
51typedef struct {
52    CFMutableDataRef   mkext;
53    uint32_t           kextIndex;
54    uint32_t           compressOffset;
55    const NXArchInfo * arch;
56    Boolean            fatal;
57    Boolean            compress;
58} Mkext1Context;
59
60void addToMkext1(
61    const void * vKey,
62    const void * vValue,
63    void       * vContext);
64
65Boolean addDataToMkext(
66    CFDataRef       data,
67    Mkext1Context * context,
68    char          * kextPath,
69    Boolean         isInfoDict);
70
71/*******************************************************************************
72*******************************************************************************/
73CFDataRef createMkext1ForArch(const NXArchInfo * arch, CFArrayRef archiveKexts,
74    boolean_t compress)
75{
76    CFMutableDataRef       result            = NULL;
77    CFMutableDictionaryRef kextsByIdentifier = NULL;
78    Mkext1Context          context;
79    mkext1_header        * mkextHeader       = NULL;  // do not free
80    const uint8_t        * adler_point = 0;
81    CFIndex count, i;
82
83    result = CFDataCreateMutable(kCFAllocatorDefault, /* capaacity */ 0);
84    if (!result || !createCFMutableDictionary(&kextsByIdentifier)) {
85        OSKextLogMemError();
86        goto finish;
87    }
88
89   /* mkext1 can only contain 1 kext for a given bundle identifier, so we
90    * have to pick out the most recent versions.
91    */
92    count = CFArrayGetCount(archiveKexts);
93    for (i = 0; i < count; i++) {
94        OSKextRef   theKext = (OSKextRef)CFArrayGetValueAtIndex(archiveKexts, i);
95        CFStringRef bundleIdentifier = OSKextGetIdentifier(theKext);
96        OSKextRef   savedKext = (OSKextRef)CFDictionaryGetValue(kextsByIdentifier,
97            bundleIdentifier);
98        OSKextVersion thisVersion, savedVersion;
99
100
101        if (!OSKextSupportsArchitecture(theKext, arch)) {
102            continue;
103        }
104
105        if (!savedKext) {
106            CFDictionarySetValue(kextsByIdentifier, bundleIdentifier, theKext);
107            continue;
108        }
109
110        thisVersion = OSKextGetVersion(theKext);
111        savedVersion = OSKextGetVersion(savedKext);
112
113        if (thisVersion > savedVersion) {
114            CFDictionarySetValue(kextsByIdentifier, bundleIdentifier, theKext);
115        }
116    }
117
118   /* Add room for the mkext header and kext descriptors.
119    */
120    CFDataSetLength(result, sizeof(mkext1_header) +
121        CFDictionaryGetCount(kextsByIdentifier) * sizeof(mkext_kext));
122
123    context.mkext = result;
124    context.kextIndex = 0;
125    context.compressOffset = (uint32_t)CFDataGetLength(result);
126    context.arch = arch;
127    context.fatal = false;
128    context.compress = compress;
129    CFDictionaryApplyFunction(kextsByIdentifier, addToMkext1, &context);
130    if (context.fatal) {
131        SAFE_RELEASE_NULL(result);
132        goto finish;
133    }
134
135    mkextHeader = (mkext1_header *)CFDataGetBytePtr(result);
136    mkextHeader->magic = OSSwapHostToBigInt32(MKEXT_MAGIC);
137    mkextHeader->signature = OSSwapHostToBigInt32(MKEXT_SIGN);
138    mkextHeader->version = OSSwapHostToBigInt32(0x01008000);   // 'vers' 1.0.0
139    mkextHeader->numkexts =
140        OSSwapHostToBigInt32(CFDictionaryGetCount(kextsByIdentifier));
141    mkextHeader->cputype = OSSwapHostToBigInt32(arch->cputype);
142    mkextHeader->cpusubtype = OSSwapHostToBigInt32(arch->cpusubtype);
143    mkextHeader->length = OSSwapHostToBigInt32(CFDataGetLength(result));
144
145    adler_point = (UInt8 *)&mkextHeader->version;
146    mkextHeader->adler32 = OSSwapHostToBigInt32(local_adler32(
147        (UInt8 *)&mkextHeader->version,
148        (int)(CFDataGetLength(result) - (adler_point - (uint8_t *)mkextHeader))));
149
150    OSKextLog(/* kext */ NULL, kOSKextLogProgressLevel | kOSKextLogArchiveFlag,
151        "Created mkext for %s containing %lu kexts.",
152        arch->name,
153        CFDictionaryGetCount(kextsByIdentifier));
154
155finish:
156    SAFE_RELEASE(kextsByIdentifier);
157    return result;
158}
159
160/*******************************************************************************
161*******************************************************************************/
162void addToMkext1(
163    const void * vKey __unused,
164    const void * vValue,
165    void       * vContext)
166{
167    OSKextRef       aKext            = (OSKextRef)vValue;
168    Mkext1Context * context          = (Mkext1Context *)vContext;
169
170    CFBundleRef     kextBundle       = NULL;  // must release
171    CFURLRef        infoDictURL      = NULL;  // must release
172    CFDataRef       rawInfoDict      = NULL;  // must release
173    CFDataRef       executable       = NULL;  // must release
174    char            kextPath[PATH_MAX];
175
176    if (context->fatal) {
177        goto finish;
178    }
179
180    if (!CFURLGetFileSystemRepresentation(OSKextGetURL(aKext),
181        /* resolveToBase */ false, (UInt8 *)kextPath, sizeof(kextPath))) {
182
183        strlcpy(kextPath, "(unknown)", sizeof(kextPath));
184    }
185
186    OSKextLog(aKext,
187        kOSKextLogProgressLevel | kOSKextLogArchiveFlag,
188        "Adding %s to mkext.", kextPath);
189
190    OSKextLog(aKext,
191        kOSKextLogStepLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
192        "Opening CFBundle for %s.", kextPath);
193    kextBundle = CFBundleCreate(kCFAllocatorDefault, OSKextGetURL(aKext));
194    if (!kextBundle) {
195        OSKextLog(aKext,
196            kOSKextLogStepLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
197            "Can't open bundle for %s.", kextPath);
198        context->fatal = true;
199        goto finish;
200    }
201
202    infoDictURL = _CFBundleCopyInfoPlistURL(kextBundle);
203    if (!infoDictURL) {
204        OSKextLog(aKext,
205            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
206            "Can't get URL for info dict of %s.", kextPath);
207        context->fatal = true;
208        goto finish;
209    }
210
211    /* create and fill infoDictPath
212     */
213    char            infoDictPath[PATH_MAX];
214
215    if (!CFURLGetFileSystemRepresentation(infoDictURL,
216                                          true,
217                                          (uint8_t *)infoDictPath,
218                                          sizeof(infoDictPath))) {
219        OSKextLogStringError(/* kext */ NULL);
220        context->fatal = true;
221        goto finish;
222    }
223
224    if (!createCFDataFromFile(&rawInfoDict,
225                              infoDictPath)) {
226        OSKextLog(/* kext */ NULL,
227                  kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
228                  "%s: Can't read info dictoionary file '%s'",
229                  __func__, infoDictPath);
230        context->fatal = true;
231        goto finish;
232    }
233
234    if (!addDataToMkext(rawInfoDict, context, kextPath, /* isInfoDict */ true)) {
235        context->fatal = true;
236        goto finish;
237    }
238
239    executable = OSKextCopyExecutableForArchitecture(aKext, context->arch);
240    if (executable) {
241        if (!addDataToMkext(executable, context, kextPath, /* isInfoDict */ false)) {
242            context->fatal = true;
243            goto finish;
244        }
245    } else if (OSKextDeclaresExecutable(aKext)) {
246        OSKextLog(aKext,
247            kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
248            "Can't get executable for %s (architecture %s).", kextPath,
249            context->arch->name);
250        context->fatal = true;
251        goto finish;
252    }
253
254    context->kextIndex++;
255
256finish:
257    if (kextBundle) {
258        OSKextLog(aKext,
259            kOSKextLogStepLevel | kOSKextLogArchiveFlag | kOSKextLogFileAccessFlag,
260            "Releaseing CFBundle for %s.", kextPath);
261        SAFE_RELEASE(kextBundle);
262    }
263    SAFE_RELEASE(infoDictURL);
264    SAFE_RELEASE(rawInfoDict);
265    SAFE_RELEASE(executable);
266    return;
267}
268
269/*******************************************************************************
270*******************************************************************************/
271Boolean addDataToMkext(
272    CFDataRef       data,
273    Mkext1Context * context,
274    char          * kextPath,
275    Boolean         isInfoDict)
276{
277    Boolean         result             = false;
278    CFIndex         newMkextLength;
279    uint32_t        origCompressOffset;
280    const UInt8   * mkextStart         = NULL;  // must calc after changing length!
281    UInt8         * addedDataStart     = NULL;  // must calc after changing length!
282    UInt8         * addedDataEnd       = NULL;  // do not free
283    uint32_t        addedDataFullLength;
284    uint32_t        compressedLength;
285
286    uint8_t       * checkBuffer        = NULL;  // must free
287
288    mkext1_header * mkextHeader        = NULL;  // do not free
289    mkext_kext    * mkextKextEntry     = NULL;  // do not free
290    mkext_file    * mkextFileEntry     = NULL;  // do not free
291
292   /* Add enough to the mkext buffer to append the whole uncompressed file.
293    * If the file can't be compressed, we'll just copy it in; if it can
294    * be compressed, we'll set the mkext buffer length to fit exactly.
295    */
296    addedDataFullLength = (uint32_t)CFDataGetLength(data);
297    newMkextLength = CFDataGetLength(context->mkext) + addedDataFullLength;
298    CFDataSetLength(context->mkext, newMkextLength);
299    if (CFDataGetLength(context->mkext) != newMkextLength) {
300        OSKextLog(/* kext */ NULL,
301            kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
302            "Can't resize mkext buffer.");
303        goto finish;
304    }
305    mkextStart = CFDataGetBytePtr(context->mkext);
306
307    origCompressOffset = context->compressOffset;
308    addedDataStart = (UInt8 *)mkextStart + origCompressOffset;
309
310    if (context->compress) {
311        addedDataEnd = compress_lzss((uint8_t *)addedDataStart, addedDataFullLength,
312            (uint8_t *)CFDataGetBytePtr(data), addedDataFullLength);
313        if (!addedDataEnd) {
314            OSKextLog(/* kext */ NULL,
315                kOSKextLogWarningLevel | kOSKextLogArchiveFlag,
316                "%s did not compress; copying file (%d bytes).",
317                isInfoDict ? "info dictionary" : "executable",
318                addedDataFullLength);
319        }
320    }
321
322    if (!addedDataEnd) {
323        memcpy(addedDataStart, (const void *)CFDataGetBytePtr(data),
324            addedDataFullLength);
325        addedDataEnd = addedDataStart + addedDataFullLength;
326        compressedLength = 0;
327        context->compressOffset += addedDataFullLength;
328    } else {
329        size_t checkLength;
330
331        compressedLength = (uint32_t)(addedDataEnd - addedDataStart);
332        context->compressOffset += compressedLength;
333
334        checkBuffer = (uint8_t *)malloc(addedDataFullLength);
335        if (!checkBuffer) {
336            OSKextLogMemError();
337            goto finish;
338        }
339
340        checkLength = decompress_lzss(checkBuffer, addedDataFullLength,
341            addedDataStart, compressedLength);
342        if (checkLength != addedDataFullLength) {
343            OSKextLog(/* kext */ NULL,
344                kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
345                "%s - %s decompressed size %d differs from original size %d",
346                kextPath,
347                isInfoDict ? "info dictionary" : "executable",
348                (int)checkLength, (int)addedDataFullLength);
349            goto finish;
350        }
351        if (0 != memcmp(checkBuffer, CFDataGetBytePtr(data), checkLength)) {
352            OSKextLog(/* kext */ NULL,
353                kOSKextLogErrorLevel | kOSKextLogArchiveFlag,
354                "%s - %s decompressed data differs from input",
355                kextPath,
356                isInfoDict ? "info dictionary" : "executable");
357            goto finish;
358        }
359
360        OSKextLog(/* kext */ NULL,
361            kOSKextLogDetailLevel | kOSKextLogArchiveFlag,
362            "Compressed %s from %u to %u bytes (%.2f%%).",
363            isInfoDict ? "info dict" : "executable",
364            addedDataFullLength, compressedLength,
365            (100.0 * (float)compressedLength/(float)addedDataFullLength));
366    }
367
368   /* Truncate the mkext to exactly fit the new total size.
369    */
370    CFDataSetLength(context->mkext, addedDataEnd - mkextStart);
371
372    mkextHeader = (mkext1_header *)CFDataGetBytePtr(context->mkext);
373    mkextKextEntry = &(mkextHeader->kext[context->kextIndex]);
374    if (isInfoDict) {
375        mkextFileEntry = &(mkextKextEntry->plist);
376    } else {
377        mkextFileEntry = &(mkextKextEntry->module);
378    }
379    mkextFileEntry->offset = OSSwapHostToBigInt32(origCompressOffset);
380    mkextFileEntry->realsize = OSSwapHostToBigInt32(addedDataFullLength);
381    mkextFileEntry->compsize = OSSwapHostToBigInt32(compressedLength);
382    mkextFileEntry->modifiedsecs = 0;  // we never use this anyway
383
384    result = true;
385
386finish:
387    SAFE_FREE(checkBuffer);
388    return result;
389}
390