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
32namespace Security {
33namespace CodeSigning {
34
35using 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//
45MachORep::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(), (size_t)ctx->offset, ctx->size);
51		else if (ctx->arch) {
52			auto_ptr<Universal> full(new Universal(fd()));
53			mExecutable = new Universal(fd(), full->archOffset(ctx->arch), full->archLength(ctx->arch));
54		} else
55			mExecutable = new Universal(fd());
56	else
57		mExecutable = new Universal(fd());
58
59	assert(mExecutable);
60	CODESIGN_DISKREP_CREATE_MACHO(this, (char*)path, (void*)ctx);
61}
62
63MachORep::~MachORep()
64{
65	delete mExecutable;
66	::free(mSigningData);
67}
68
69
70//
71// Sniffer function for "plausible Mach-O binary"
72//
73bool 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:
80	case MH_KEXT_BUNDLE:
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//
95Universal *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//
104size_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//
115CFDataRef MachORep::identification()
116{
117	std::auto_ptr<MachO> macho(mainExecutableImage()->architecture());
118	return identificationFor(macho.get());
119}
120
121CFDataRef 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//
150CFDataRef 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//
169CFDataRef 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//
200CFDataRef 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);
208				info.take(macho->dataAt(macho->flip(sect64->offset), (size_t)macho->flip(sect64->size)));
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//
223string 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//
249void MachORep::flush()
250{
251	size_t offset = mExecutable->offset();
252	size_t length = mExecutable->length();
253	delete mExecutable;
254	mExecutable = NULL;
255	::free(mSigningData);
256	mSigningData = NULL;
257	SingleDiskRep::flush();
258	mExecutable = new Universal(fd(), offset, length);
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//
267string 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//
289const 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
302Requirement *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//
358size_t MachORep::pageSize(const SigningContext &)
359{
360	return segmentedPageSize;
361}
362
363
364//
365// Strict validation
366//
367void MachORep::strictValidate(const ToleratedErrors& tolerated)
368{
369	if (mExecutable->isSuspicious() && tolerated.find(errSecCSBadMainExecutable) == tolerated.end())
370		MacOSError::throwMe(errSecCSBadMainExecutable);
371}
372
373
374//
375// FileDiskRep::Writers
376//
377DiskRep::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//
389void 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
398