]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
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 | ||
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); | |
126 | char result[4 + sizeof(uuidc->uuid)]; | |
127 | memcpy(result, "UUID", 4); | |
128 | memcpy(result+4, uuidc->uuid, sizeof(uuidc->uuid)); | |
129 | return makeCFData(result, sizeof(result)); | |
130 | } | |
131 | ||
132 | // otherwise, use the SHA-1 hash of the entire load command area | |
133 | SHA1 hash; | |
134 | hash(&macho->header(), sizeof(mach_header)); | |
135 | hash(macho->loadCommands(), macho->commandLength()); | |
136 | SHA1::Digest digest; | |
137 | hash.finish(digest); | |
138 | return makeCFData(digest, sizeof(digest)); | |
139 | } | |
140 | ||
141 | ||
142 | // | |
143 | // Retrieve a component from the executable. | |
144 | // This reads the entire signing SuperBlob when first called for an executable, | |
145 | // and then caches it for further use. | |
146 | // Note that we could read individual components directly off disk and only cache | |
147 | // the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected | |
148 | // to cache the pieces anyway. | |
149 | // | |
150 | CFDataRef MachORep::component(CodeDirectory::SpecialSlot slot) | |
151 | { | |
152 | switch (slot) { | |
153 | case cdInfoSlot: | |
154 | return infoPlist(); | |
155 | default: | |
156 | return embeddedComponent(slot); | |
157 | } | |
158 | } | |
159 | ||
160 | ||
161 | // Retrieve a component from the embedded signature SuperBlob (if present). | |
162 | // This reads the entire signing SuperBlob when first called for an executable, | |
163 | // and then caches it for further use. | |
164 | // Note that we could read individual components directly off disk and only cache | |
165 | // the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected | |
166 | // to cache the pieces anyway. But it's not clear that the resulting multiple I/O | |
167 | // calls wouldn't be slower in the end. | |
168 | // | |
169 | CFDataRef MachORep::embeddedComponent(CodeDirectory::SpecialSlot slot) | |
170 | { | |
171 | if (!mSigningData) { // fetch and cache | |
172 | auto_ptr<MachO> macho(mainExecutableImage()->architecture()); | |
173 | if (macho.get()) | |
174 | if (const linkedit_data_command *cs = macho->findCodeSignature()) { | |
175 | size_t offset = macho->flip(cs->dataoff); | |
176 | size_t length = macho->flip(cs->datasize); | |
177 | if ((mSigningData = EmbeddedSignatureBlob::readBlob(macho->fd(), macho->offset() + offset, length))) { | |
178 | secdebug("machorep", "%zd signing bytes in %d blob(s) from %s(%s)", | |
179 | mSigningData->length(), mSigningData->count(), | |
180 | mainExecutablePath().c_str(), macho->architecture().name()); | |
181 | } else { | |
182 | secdebug("machorep", "failed to read signing bytes from %s(%s)", | |
183 | mainExecutablePath().c_str(), macho->architecture().name()); | |
184 | MacOSError::throwMe(errSecCSSignatureInvalid); | |
185 | } | |
186 | } | |
187 | } | |
188 | if (mSigningData) | |
189 | return mSigningData->component(slot); | |
190 | ||
191 | // not found | |
192 | return NULL; | |
193 | } | |
194 | ||
195 | ||
196 | // | |
197 | // Extract an embedded Info.plist from the file. | |
198 | // Returns NULL if none is found. | |
199 | // | |
200 | CFDataRef MachORep::infoPlist() | |
201 | { | |
202 | CFRef<CFDataRef> info; | |
203 | try { | |
204 | auto_ptr<MachO> macho(mainExecutableImage()->architecture()); | |
205 | if (const section *sect = macho->findSection("__TEXT", "__info_plist")) { | |
206 | if (macho->is64()) { | |
207 | const section_64 *sect64 = reinterpret_cast<const section_64 *>(sect); | |
427c49bc | 208 | info.take(macho->dataAt(macho->flip(sect64->offset), (size_t)macho->flip(sect64->size))); |
b1ab9ed8 A |
209 | } else { |
210 | info.take(macho->dataAt(macho->flip(sect->offset), macho->flip(sect->size))); | |
211 | } | |
212 | } | |
213 | } catch (...) { | |
214 | secdebug("machorep", "exception reading embedded Info.plist"); | |
215 | } | |
216 | return info.yield(); | |
217 | } | |
218 | ||
219 | ||
220 | // | |
221 | // Provide a (vaguely) human readable characterization of this code | |
222 | // | |
223 | string MachORep::format() | |
224 | { | |
225 | if (Universal *fat = mainExecutableImage()) { | |
226 | Universal::Architectures archs; | |
227 | fat->architectures(archs); | |
228 | if (fat->isUniversal()) { | |
229 | string s = "Mach-O universal ("; | |
230 | for (Universal::Architectures::const_iterator it = archs.begin(); | |
231 | it != archs.end(); ++it) { | |
232 | if (it != archs.begin()) | |
233 | s += " "; | |
234 | s += it->displayName(); | |
235 | } | |
236 | return s + ")"; | |
237 | } else { | |
238 | assert(archs.size() == 1); | |
239 | return string("Mach-O thin (") + archs.begin()->displayName() + ")"; | |
240 | } | |
241 | } else | |
242 | return "Mach-O (unrecognized format)"; | |
243 | } | |
244 | ||
245 | ||
246 | // | |
247 | // Flush cached data | |
248 | // | |
249 | void MachORep::flush() | |
250 | { | |
e3d3b979 | 251 | size_t offset = mExecutable->offset(); |
80e23899 | 252 | size_t length = mExecutable->length(); |
b1ab9ed8 A |
253 | delete mExecutable; |
254 | mExecutable = NULL; | |
255 | ::free(mSigningData); | |
256 | mSigningData = NULL; | |
257 | SingleDiskRep::flush(); | |
80e23899 | 258 | mExecutable = new Universal(fd(), offset, length); |
b1ab9ed8 A |
259 | } |
260 | ||
261 | ||
262 | // | |
263 | // Return a recommended unique identifier. | |
264 | // If our file has an embedded Info.plist, use the CFBundleIdentifier from that. | |
265 | // Otherwise, use the default. | |
266 | // | |
267 | string MachORep::recommendedIdentifier(const SigningContext &ctx) | |
268 | { | |
269 | if (CFDataRef info = infoPlist()) { | |
270 | if (CFRef<CFDictionaryRef> dict = makeCFDictionaryFrom(info)) { | |
271 | CFStringRef code = CFStringRef(CFDictionaryGetValue(dict, kCFBundleIdentifierKey)); | |
272 | if (code && CFGetTypeID(code) != CFStringGetTypeID()) | |
273 | MacOSError::throwMe(errSecCSBadDictionaryFormat); | |
274 | if (code) | |
275 | return cfString(code); | |
276 | } else | |
277 | MacOSError::throwMe(errSecCSBadDictionaryFormat); | |
278 | } | |
279 | ||
280 | // ah well. Use the default | |
281 | return SingleDiskRep::recommendedIdentifier(ctx); | |
282 | } | |
283 | ||
284 | ||
285 | // | |
286 | // The default suggested requirements for Mach-O binaries are as follows: | |
287 | // Library requirement: Composed from dynamic load commands. | |
288 | // | |
289 | const Requirements *MachORep::defaultRequirements(const Architecture *arch, const SigningContext &ctx) | |
290 | { | |
291 | assert(arch); // enforced by signing infrastructure | |
292 | Requirements::Maker maker; | |
293 | ||
294 | // add library requirements from DYLIB commands (if any) | |
295 | if (Requirement *libreq = libraryRequirements(arch, ctx)) | |
296 | maker.add(kSecLibraryRequirementType, libreq); // takes ownership | |
297 | ||
298 | // that's all | |
299 | return maker.make(); | |
300 | } | |
301 | ||
302 | Requirement *MachORep::libraryRequirements(const Architecture *arch, const SigningContext &ctx) | |
303 | { | |
304 | auto_ptr<MachO> macho(mainExecutableImage()->architecture(*arch)); | |
305 | Requirement::Maker maker; | |
306 | Requirement::Maker::Chain chain(maker, opOr); | |
307 | ||
308 | if (macho.get()) | |
309 | if (const linkedit_data_command *ldep = macho->findLibraryDependencies()) { | |
310 | size_t offset = macho->flip(ldep->dataoff); | |
311 | size_t length = macho->flip(ldep->datasize); | |
312 | if (LibraryDependencyBlob *deplist = LibraryDependencyBlob::readBlob(macho->fd(), macho->offset() + offset, length)) { | |
313 | try { | |
314 | secdebug("machorep", "%zd library dependency bytes in %d blob(s) from %s(%s)", | |
315 | deplist->length(), deplist->count(), | |
316 | mainExecutablePath().c_str(), macho->architecture().name()); | |
317 | unsigned count = deplist->count(); | |
318 | // we could walk through DYLIB load commands in parallel. We just don't need anything from them so far | |
319 | for (unsigned n = 0; n < count; n++) { | |
320 | const Requirement *req = NULL; | |
321 | if (const BlobCore *dep = deplist->blob(n)) { | |
322 | if ((req = Requirement::specific(dep))) { | |
323 | // binary code requirement; good to go | |
324 | } else if (const BlobWrapper *wrap = BlobWrapper::specific(dep)) { | |
325 | // blob-wrapped text form - convert to binary requirement | |
326 | std::string reqString = std::string((const char *)wrap->data(), wrap->length()); | |
327 | CFRef<SecRequirementRef> areq; | |
328 | MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &areq.aref())); | |
329 | CFRef<CFDataRef> reqData; | |
330 | MacOSError::check(SecRequirementCopyData(areq, kSecCSDefaultFlags, &reqData.aref())); | |
331 | req = Requirement::specific((const BlobCore *)CFDataGetBytePtr(reqData)); | |
332 | } else { | |
333 | secdebug("machorep", "unexpected blob type 0x%x in slot %d of binary dependencies", dep->magic(), n); | |
334 | continue; | |
335 | } | |
336 | chain.add(); | |
337 | maker.copy(req); | |
338 | } else | |
339 | secdebug("machorep", "missing DR info for library index %d", n); | |
340 | } | |
341 | ::free(deplist); | |
342 | } catch (...) { | |
343 | ::free(deplist); | |
344 | throw; | |
345 | } | |
346 | } | |
347 | } | |
348 | if (chain.empty()) | |
349 | return NULL; | |
350 | else | |
351 | return maker.make(); | |
352 | } | |
353 | ||
354 | ||
355 | // | |
356 | // Default to system page size for segmented (paged) signatures | |
357 | // | |
358 | size_t MachORep::pageSize(const SigningContext &) | |
359 | { | |
360 | return segmentedPageSize; | |
361 | } | |
362 | ||
363 | ||
80e23899 A |
364 | // |
365 | // Strict validation | |
366 | // | |
367 | void MachORep::strictValidate(const ToleratedErrors& tolerated) | |
368 | { | |
369 | if (mExecutable->isSuspicious() && tolerated.find(errSecCSBadMainExecutable) == tolerated.end()) | |
370 | MacOSError::throwMe(errSecCSBadMainExecutable); | |
371 | } | |
372 | ||
373 | ||
b1ab9ed8 A |
374 | // |
375 | // FileDiskRep::Writers | |
376 | // | |
377 | DiskRep::Writer *MachORep::writer() | |
378 | { | |
379 | return new Writer(this); | |
380 | } | |
381 | ||
382 | ||
383 | // | |
384 | // Write a component. | |
385 | // MachORep::Writers don't write to components directly; the signing code uses special | |
386 | // knowledge of the Mach-O format to build embedded signatures and blasts them directly | |
387 | // to disk. Thus this implementation will never be called (and, if called, will simply fail). | |
388 | // | |
389 | void MachORep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data) | |
390 | { | |
391 | assert(false); | |
392 | MacOSError::throwMe(errSecCSInternalError); | |
393 | } | |
394 | ||
395 | ||
396 | } // end namespace CodeSigning | |
397 | } // end namespace Security |