]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 | 1 | /* |
d8f41ccd | 2 | * Copyright (c) 2006,2011-2012,2014 Apple Inc. All Rights Reserved. |
b1ab9ed8 A |
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 | ||
24 | // | |
25 | // machorep - DiskRep mix-in for handling Mach-O main executables | |
26 | // | |
27 | #include "machorep.h" | |
28 | #include "StaticCode.h" | |
29 | #include "reqmaker.h" | |
30 | ||
31 | ||
32 | namespace Security { | |
33 | namespace CodeSigning { | |
34 | ||
35 | using namespace UnixPlusPlus; | |
36 | ||
37 | ||
38 | // | |
39 | // Object management. | |
40 | // We open the main executable lazily, so nothing much happens on construction. | |
41 | // If the context specifies a file offset, we directly pick that Mach-O binary (only). | |
42 | // if it specifies an architecture, we try to pick that. Otherwise, we deliver the whole | |
43 | // Universal object (which will usually deliver the "native" architecture later). | |
44 | // | |
45 | MachORep::MachORep(const char *path, const Context *ctx) | |
46 | : SingleDiskRep(path), mSigningData(NULL) | |
47 | { | |
48 | if (ctx) | |
49 | if (ctx->offset) | |
80e23899 | 50 | mExecutable = new Universal(fd(), (size_t)ctx->offset, ctx->size); |
b1ab9ed8 A |
51 | else if (ctx->arch) { |
52 | auto_ptr<Universal> full(new Universal(fd())); | |
80e23899 | 53 | mExecutable = new Universal(fd(), full->archOffset(ctx->arch), full->archLength(ctx->arch)); |
b1ab9ed8 A |
54 | } else |
55 | mExecutable = new Universal(fd()); | |
56 | else | |
57 | mExecutable = new Universal(fd()); | |
80e23899 | 58 | |
b1ab9ed8 A |
59 | assert(mExecutable); |
60 | CODESIGN_DISKREP_CREATE_MACHO(this, (char*)path, (void*)ctx); | |
61 | } | |
62 | ||
63 | MachORep::~MachORep() | |
64 | { | |
65 | delete mExecutable; | |
66 | ::free(mSigningData); | |
67 | } | |
68 | ||
69 | ||
70 | // | |
71 | // Sniffer function for "plausible Mach-O binary" | |
72 | // | |
73 | bool MachORep::candidate(FileDesc &fd) | |
74 | { | |
75 | switch (Universal::typeOf(fd)) { | |
76 | case MH_EXECUTE: | |
77 | case MH_DYLIB: | |
78 | case MH_DYLINKER: | |
79 | case MH_BUNDLE: | |
c2a06e24 | 80 | case MH_KEXT_BUNDLE: |
b1ab9ed8 A |
81 | case MH_PRELOAD: |
82 | return true; // dynamic image; supported | |
83 | case MH_OBJECT: | |
84 | return false; // maybe later... | |
85 | default: | |
86 | return false; // not Mach-O (or too exotic) | |
87 | } | |
88 | } | |
89 | ||
90 | ||
91 | ||
92 | // | |
93 | // Nowadays, the main executable object is created upon construction. | |
94 | // | |
95 | Universal *MachORep::mainExecutableImage() | |
96 | { | |
97 | return mExecutable; | |
98 | } | |
99 | ||
100 | ||
101 | // | |
102 | // Signing base is the start of the Mach-O architecture we're using | |
103 | // | |
104 | size_t MachORep::signingBase() | |
105 | { | |
106 | return mainExecutableImage()->archOffset(); | |
107 | } | |
108 | ||
109 | ||
110 | // | |
111 | // We choose the binary identifier for a Mach-O binary as follows: | |
112 | // - If the Mach-O headers have a UUID command, use the UUID. | |
113 | // - Otherwise, use the SHA-1 hash of the (entire) load commands. | |
114 | // | |
115 | CFDataRef MachORep::identification() | |
116 | { | |
117 | std::auto_ptr<MachO> macho(mainExecutableImage()->architecture()); | |
118 | return identificationFor(macho.get()); | |
119 | } | |
120 | ||
121 | CFDataRef MachORep::identificationFor(MachO *macho) | |
122 | { | |
123 | // if there is a LC_UUID load command, use the UUID contained therein | |
124 | if (const load_command *cmd = macho->findCommand(LC_UUID)) { | |
125 | const uuid_command *uuidc = reinterpret_cast<const uuid_command *>(cmd); | |
d8f41ccd A |
126 | // uuidc->cmdsize should be sizeof(uuid_command), so if it is not, |
127 | // something is wrong. Fail out. | |
128 | if (macho->flip(uuidc->cmdsize) != sizeof(uuid_command)) | |
129 | MacOSError::throwMe(errSecCSSignatureInvalid); | |
b1ab9ed8 A |
130 | char result[4 + sizeof(uuidc->uuid)]; |
131 | memcpy(result, "UUID", 4); | |
132 | memcpy(result+4, uuidc->uuid, sizeof(uuidc->uuid)); | |
133 | return makeCFData(result, sizeof(result)); | |
134 | } | |
135 | ||
136 | // otherwise, use the SHA-1 hash of the entire load command area | |
137 | SHA1 hash; | |
138 | hash(&macho->header(), sizeof(mach_header)); | |
139 | hash(macho->loadCommands(), macho->commandLength()); | |
140 | SHA1::Digest digest; | |
141 | hash.finish(digest); | |
142 | return makeCFData(digest, sizeof(digest)); | |
143 | } | |
144 | ||
145 | ||
146 | // | |
147 | // Retrieve a component from the executable. | |
148 | // This reads the entire signing SuperBlob when first called for an executable, | |
149 | // and then caches it for further use. | |
150 | // Note that we could read individual components directly off disk and only cache | |
151 | // the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected | |
152 | // to cache the pieces anyway. | |
153 | // | |
154 | CFDataRef MachORep::component(CodeDirectory::SpecialSlot slot) | |
155 | { | |
156 | switch (slot) { | |
157 | case cdInfoSlot: | |
158 | return infoPlist(); | |
159 | default: | |
160 | return embeddedComponent(slot); | |
161 | } | |
162 | } | |
163 | ||
164 | ||
165 | // Retrieve a component from the embedded signature SuperBlob (if present). | |
166 | // This reads the entire signing SuperBlob when first called for an executable, | |
167 | // and then caches it for further use. | |
168 | // Note that we could read individual components directly off disk and only cache | |
169 | // the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected | |
170 | // to cache the pieces anyway. But it's not clear that the resulting multiple I/O | |
171 | // calls wouldn't be slower in the end. | |
172 | // | |
173 | CFDataRef MachORep::embeddedComponent(CodeDirectory::SpecialSlot slot) | |
174 | { | |
175 | if (!mSigningData) { // fetch and cache | |
176 | auto_ptr<MachO> macho(mainExecutableImage()->architecture()); | |
177 | if (macho.get()) | |
178 | if (const linkedit_data_command *cs = macho->findCodeSignature()) { | |
179 | size_t offset = macho->flip(cs->dataoff); | |
180 | size_t length = macho->flip(cs->datasize); | |
181 | if ((mSigningData = EmbeddedSignatureBlob::readBlob(macho->fd(), macho->offset() + offset, length))) { | |
182 | secdebug("machorep", "%zd signing bytes in %d blob(s) from %s(%s)", | |
183 | mSigningData->length(), mSigningData->count(), | |
184 | mainExecutablePath().c_str(), macho->architecture().name()); | |
185 | } else { | |
186 | secdebug("machorep", "failed to read signing bytes from %s(%s)", | |
187 | mainExecutablePath().c_str(), macho->architecture().name()); | |
188 | MacOSError::throwMe(errSecCSSignatureInvalid); | |
189 | } | |
190 | } | |
191 | } | |
192 | if (mSigningData) | |
193 | return mSigningData->component(slot); | |
194 | ||
195 | // not found | |
196 | return NULL; | |
197 | } | |
198 | ||
199 | ||
200 | // | |
201 | // Extract an embedded Info.plist from the file. | |
202 | // Returns NULL if none is found. | |
203 | // | |
204 | CFDataRef MachORep::infoPlist() | |
205 | { | |
206 | CFRef<CFDataRef> info; | |
207 | try { | |
208 | auto_ptr<MachO> macho(mainExecutableImage()->architecture()); | |
209 | if (const section *sect = macho->findSection("__TEXT", "__info_plist")) { | |
210 | if (macho->is64()) { | |
211 | const section_64 *sect64 = reinterpret_cast<const section_64 *>(sect); | |
427c49bc | 212 | info.take(macho->dataAt(macho->flip(sect64->offset), (size_t)macho->flip(sect64->size))); |
b1ab9ed8 A |
213 | } else { |
214 | info.take(macho->dataAt(macho->flip(sect->offset), macho->flip(sect->size))); | |
215 | } | |
216 | } | |
217 | } catch (...) { | |
218 | secdebug("machorep", "exception reading embedded Info.plist"); | |
219 | } | |
220 | return info.yield(); | |
221 | } | |
222 | ||
223 | ||
224 | // | |
225 | // Provide a (vaguely) human readable characterization of this code | |
226 | // | |
227 | string MachORep::format() | |
228 | { | |
229 | if (Universal *fat = mainExecutableImage()) { | |
230 | Universal::Architectures archs; | |
231 | fat->architectures(archs); | |
232 | if (fat->isUniversal()) { | |
233 | string s = "Mach-O universal ("; | |
234 | for (Universal::Architectures::const_iterator it = archs.begin(); | |
235 | it != archs.end(); ++it) { | |
236 | if (it != archs.begin()) | |
237 | s += " "; | |
238 | s += it->displayName(); | |
239 | } | |
240 | return s + ")"; | |
241 | } else { | |
242 | assert(archs.size() == 1); | |
243 | return string("Mach-O thin (") + archs.begin()->displayName() + ")"; | |
244 | } | |
245 | } else | |
246 | return "Mach-O (unrecognized format)"; | |
247 | } | |
248 | ||
249 | ||
250 | // | |
251 | // Flush cached data | |
252 | // | |
253 | void MachORep::flush() | |
254 | { | |
e3d3b979 | 255 | size_t offset = mExecutable->offset(); |
80e23899 | 256 | size_t length = mExecutable->length(); |
b1ab9ed8 A |
257 | delete mExecutable; |
258 | mExecutable = NULL; | |
259 | ::free(mSigningData); | |
260 | mSigningData = NULL; | |
261 | SingleDiskRep::flush(); | |
80e23899 | 262 | mExecutable = new Universal(fd(), offset, length); |
b1ab9ed8 A |
263 | } |
264 | ||
265 | ||
266 | // | |
267 | // Return a recommended unique identifier. | |
268 | // If our file has an embedded Info.plist, use the CFBundleIdentifier from that. | |
269 | // Otherwise, use the default. | |
270 | // | |
271 | string MachORep::recommendedIdentifier(const SigningContext &ctx) | |
272 | { | |
273 | if (CFDataRef info = infoPlist()) { | |
274 | if (CFRef<CFDictionaryRef> dict = makeCFDictionaryFrom(info)) { | |
275 | CFStringRef code = CFStringRef(CFDictionaryGetValue(dict, kCFBundleIdentifierKey)); | |
276 | if (code && CFGetTypeID(code) != CFStringGetTypeID()) | |
277 | MacOSError::throwMe(errSecCSBadDictionaryFormat); | |
278 | if (code) | |
279 | return cfString(code); | |
280 | } else | |
281 | MacOSError::throwMe(errSecCSBadDictionaryFormat); | |
282 | } | |
283 | ||
284 | // ah well. Use the default | |
285 | return SingleDiskRep::recommendedIdentifier(ctx); | |
286 | } | |
287 | ||
288 | ||
289 | // | |
290 | // The default suggested requirements for Mach-O binaries are as follows: | |
291 | // Library requirement: Composed from dynamic load commands. | |
292 | // | |
293 | const Requirements *MachORep::defaultRequirements(const Architecture *arch, const SigningContext &ctx) | |
294 | { | |
295 | assert(arch); // enforced by signing infrastructure | |
296 | Requirements::Maker maker; | |
297 | ||
298 | // add library requirements from DYLIB commands (if any) | |
299 | if (Requirement *libreq = libraryRequirements(arch, ctx)) | |
300 | maker.add(kSecLibraryRequirementType, libreq); // takes ownership | |
301 | ||
302 | // that's all | |
303 | return maker.make(); | |
304 | } | |
305 | ||
306 | Requirement *MachORep::libraryRequirements(const Architecture *arch, const SigningContext &ctx) | |
307 | { | |
308 | auto_ptr<MachO> macho(mainExecutableImage()->architecture(*arch)); | |
309 | Requirement::Maker maker; | |
310 | Requirement::Maker::Chain chain(maker, opOr); | |
311 | ||
312 | if (macho.get()) | |
313 | if (const linkedit_data_command *ldep = macho->findLibraryDependencies()) { | |
314 | size_t offset = macho->flip(ldep->dataoff); | |
315 | size_t length = macho->flip(ldep->datasize); | |
316 | if (LibraryDependencyBlob *deplist = LibraryDependencyBlob::readBlob(macho->fd(), macho->offset() + offset, length)) { | |
317 | try { | |
318 | secdebug("machorep", "%zd library dependency bytes in %d blob(s) from %s(%s)", | |
319 | deplist->length(), deplist->count(), | |
320 | mainExecutablePath().c_str(), macho->architecture().name()); | |
321 | unsigned count = deplist->count(); | |
322 | // we could walk through DYLIB load commands in parallel. We just don't need anything from them so far | |
323 | for (unsigned n = 0; n < count; n++) { | |
324 | const Requirement *req = NULL; | |
325 | if (const BlobCore *dep = deplist->blob(n)) { | |
326 | if ((req = Requirement::specific(dep))) { | |
327 | // binary code requirement; good to go | |
328 | } else if (const BlobWrapper *wrap = BlobWrapper::specific(dep)) { | |
329 | // blob-wrapped text form - convert to binary requirement | |
330 | std::string reqString = std::string((const char *)wrap->data(), wrap->length()); | |
331 | CFRef<SecRequirementRef> areq; | |
332 | MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &areq.aref())); | |
333 | CFRef<CFDataRef> reqData; | |
334 | MacOSError::check(SecRequirementCopyData(areq, kSecCSDefaultFlags, &reqData.aref())); | |
335 | req = Requirement::specific((const BlobCore *)CFDataGetBytePtr(reqData)); | |
336 | } else { | |
337 | secdebug("machorep", "unexpected blob type 0x%x in slot %d of binary dependencies", dep->magic(), n); | |
338 | continue; | |
339 | } | |
340 | chain.add(); | |
341 | maker.copy(req); | |
342 | } else | |
343 | secdebug("machorep", "missing DR info for library index %d", n); | |
344 | } | |
345 | ::free(deplist); | |
346 | } catch (...) { | |
347 | ::free(deplist); | |
348 | throw; | |
349 | } | |
350 | } | |
351 | } | |
352 | if (chain.empty()) | |
353 | return NULL; | |
354 | else | |
355 | return maker.make(); | |
356 | } | |
357 | ||
358 | ||
359 | // | |
360 | // Default to system page size for segmented (paged) signatures | |
361 | // | |
362 | size_t MachORep::pageSize(const SigningContext &) | |
363 | { | |
364 | return segmentedPageSize; | |
365 | } | |
366 | ||
367 | ||
80e23899 A |
368 | // |
369 | // Strict validation | |
370 | // | |
60c433a9 | 371 | void MachORep::strictValidate(const CodeDirectory* cd, const ToleratedErrors& tolerated) |
80e23899 | 372 | { |
60c433a9 | 373 | // if the constructor found suspicious issues, fail a struct validation now |
80e23899 A |
374 | if (mExecutable->isSuspicious() && tolerated.find(errSecCSBadMainExecutable) == tolerated.end()) |
375 | MacOSError::throwMe(errSecCSBadMainExecutable); | |
60c433a9 A |
376 | |
377 | // the signature's code extent must be what we would have picked (no funny hand editing) | |
378 | if (cd) { | |
379 | auto_ptr<MachO> macho(mExecutable->architecture()); | |
380 | if (cd->codeLimit != macho->signingExtent()) | |
381 | MacOSError::throwMe(errSecCSSignatureInvalid); | |
382 | } | |
80e23899 A |
383 | } |
384 | ||
385 | ||
b1ab9ed8 A |
386 | // |
387 | // FileDiskRep::Writers | |
388 | // | |
389 | DiskRep::Writer *MachORep::writer() | |
390 | { | |
391 | return new Writer(this); | |
392 | } | |
393 | ||
394 | ||
395 | // | |
396 | // Write a component. | |
397 | // MachORep::Writers don't write to components directly; the signing code uses special | |
398 | // knowledge of the Mach-O format to build embedded signatures and blasts them directly | |
399 | // to disk. Thus this implementation will never be called (and, if called, will simply fail). | |
400 | // | |
401 | void MachORep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data) | |
402 | { | |
403 | assert(false); | |
404 | MacOSError::throwMe(errSecCSInternalError); | |
405 | } | |
406 | ||
407 | ||
408 | } // end namespace CodeSigning | |
409 | } // end namespace Security |