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