]> git.saurik.com Git - apple/libsecurity_codesigning.git/blob - lib/machorep.cpp
8753ca609bfa2cc9db04201b0b3de66ea6598d28
[apple/libsecurity_codesigning.git] / lib / machorep.cpp
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)
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);
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 //
72 bool MachORep::candidate(FileDesc &fd)
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
89
90 //
91 // Nowadays, the main executable object is created upon construction.
92 //
93 Universal *MachORep::mainExecutableImage()
94 {
95 return mExecutable;
96 }
97
98
99 //
100 // Signing base is the start of the Mach-O architecture we're using
101 //
102 size_t MachORep::signingBase()
103 {
104 return mainExecutableImage()->archOffset();
105 }
106
107
108 //
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.
112 //
113 CFDataRef MachORep::identification()
114 {
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));
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 {
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);
175 if (mSigningData = EmbeddedSignatureBlob::readBlob(macho->fd(), macho->offset() + offset, length)) {
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);
183 }
184 }
185 }
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);
206 info.take(macho->dataAt(macho->flip(sect64->offset), macho->flip(sect64->size)));
207 } else {
208 info.take(macho->dataAt(macho->flip(sect->offset), macho->flip(sect->size)));
209 }
210 }
211 } catch (...) {
212 secdebug("machorep", "exception reading embedded Info.plist");
213 }
214 return info.yield();
215 }
216
217
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 += " ";
232 s += it->displayName();
233 }
234 return s + ")";
235 } else {
236 assert(archs.size() == 1);
237 return string("Mach-O thin (") + archs.begin()->displayName() + ")";
238 }
239 } else
240 return "Mach-O (unrecognized format)";
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();
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()) {
266 if (CFDictionaryRef dict = makeCFDictionaryFrom(info)) {
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:
283 // Hosting requirement: Rosetta if it's PPC, none otherwise.
284 // Library requirement: Composed from dynamic load commands.
285 //
286 static const uint8_t ppc_host_ireq[] = { // anchor apple and identifier com.apple.translate
287 0xfa, 0xde, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06,
288 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x13, 0x63, 0x6f, 0x6d, 0x2e,
289 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x6c, 0x61, 0x74, 0x65, 0x00,
290 };
291
292 const Requirements *MachORep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
293 {
294 assert(arch); // enforced by signing infrastructure
295 Requirements::Maker maker;
296
297 // if ppc architecture, add hosting requirement for Rosetta's translate tool
298 if (arch->cpuType() == CPU_TYPE_POWERPC)
299 maker.add(kSecHostRequirementType, ((const Requirement *)ppc_host_ireq)->clone());
300
301 // add library requirements from DYLIB commands (if any)
302 if (Requirement *libreq = libraryRequirements(arch, ctx))
303 maker.add(kSecLibraryRequirementType, libreq); // takes ownership
304
305 // that's all
306 return maker.make();
307 }
308
309 Requirement *MachORep::libraryRequirements(const Architecture *arch, const SigningContext &ctx)
310 {
311 auto_ptr<MachO> macho(mainExecutableImage()->architecture(*arch));
312 Requirement::Maker maker;
313 Requirement::Maker::Chain chain(maker, opOr);
314 if (macho.get()) {
315 for (const load_command *command = macho->loadCommands(); command; command = macho->nextCommand(command)) {
316 if (macho->flip(command->cmd) == LC_LOAD_DYLIB) {
317 const dylib_command *dycmd = (const dylib_command *)command;
318 if (const char *name = macho->string(command, dycmd->dylib.name))
319 try {
320 string path = ctx.sdkPath(name);
321 secdebug("machorep", "examining DYLIB %s", path.c_str());
322 // find path on disk, get designated requirement (if signed)
323 if (RefPointer<DiskRep> rep = DiskRep::bestGuess(path))
324 if (SecPointer<SecStaticCode> code = new SecStaticCode(rep))
325 if (const Requirement *req = code->designatedRequirement()) {
326 CODESIGN_SIGN_DEP_MACHO(this, (char*)path.c_str(), (void*)req);
327 chain.add();
328 chain.maker.copy(req);
329 }
330 } catch (...) {
331 CODESIGN_SIGN_DEP_MACHO(this, (char*)name, NULL);
332 secdebug("machorep", "exception getting library requirement (ignored)");
333 }
334 else
335 CODESIGN_SIGN_DEP_MACHO(this, NULL, NULL);
336 }
337 }
338 }
339 if (chain.empty())
340 return NULL;
341 else
342 return maker.make();
343 }
344
345
346 //
347 // Default to system page size for segmented (paged) signatures
348 //
349 size_t MachORep::pageSize(const SigningContext &)
350 {
351 return segmentedPageSize;
352 }
353
354
355 //
356 // FileDiskRep::Writers
357 //
358 DiskRep::Writer *MachORep::writer()
359 {
360 return new Writer(this);
361 }
362
363
364 //
365 // Write a component.
366 // MachORep::Writers don't write to components directly; the signing code uses special
367 // knowledge of the Mach-O format to build embedded signatures and blasts them directly
368 // to disk. Thus this implementation will never be called (and, if called, will simply fail).
369 //
370 void MachORep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
371 {
372 assert(false);
373 MacOSError::throwMe(errSecCSInternalError);
374 }
375
376
377 } // end namespace CodeSigning
378 } // end namespace Security