-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathmachorep.cpp
More file actions
602 lines (516 loc) · 18 KB
/
machorep.cpp
File metadata and controls
602 lines (516 loc) · 18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
/*
* Copyright (c) 2006,2011-2012,2014 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
//
// machorep - DiskRep mix-in for handling Mach-O main executables
//
#include "machorep.h"
#include "notarization.h"
#include "StaticCode.h"
#include "reqmaker.h"
#include <security_utilities/logging.h>
#include <security_utilities/cfmunge.h>
#include <security_utilities/casts.h>
namespace Security {
namespace CodeSigning {
using namespace UnixPlusPlus;
//
// Object management.
// We open the main executable lazily, so nothing much happens on construction.
// If the context specifies a file offset, we directly pick that Mach-O binary (only).
// if it specifies an architecture, we try to pick that. Otherwise, we deliver the whole
// Universal object (which will usually deliver the "native" architecture later).
//
MachORep::MachORep(const char *path, const Context *ctx)
: SingleDiskRep(path), mSigningData(NULL)
{
if (ctx)
if (ctx->offset)
mExecutable = new Universal(fd(), (size_t)ctx->offset, ctx->size);
else if (ctx->arch) {
unique_ptr<Universal> full(new Universal(fd()));
mExecutable = new Universal(fd(), full->archOffset(ctx->arch), full->archLength(ctx->arch));
} else
mExecutable = new Universal(fd());
else
mExecutable = new Universal(fd());
assert(mExecutable);
CODESIGN_DISKREP_CREATE_MACHO(this, (char*)path, (void*)ctx);
}
MachORep::~MachORep()
{
delete mExecutable;
::free(mSigningData);
}
//
// Sniffer function for "plausible Mach-O binary"
//
bool MachORep::candidate(FileDesc &fd)
{
switch (Universal::typeOf(fd)) {
case MH_EXECUTE:
case MH_DYLIB:
case MH_DYLINKER:
case MH_BUNDLE:
case MH_KEXT_BUNDLE:
case MH_PRELOAD:
return true; // dynamic image; supported
case MH_OBJECT:
return false; // maybe later...
default:
return false; // not Mach-O (or too exotic)
}
}
//
// Nowadays, the main executable object is created upon construction.
//
Universal *MachORep::mainExecutableImage()
{
return mExecutable;
}
//
// Explicitly default to SHA256 (only) digests if the minimum deployment
// target is young enough.
//
void MachORep::prepareForSigning(SigningContext &context)
{
if (context.digestAlgorithms().empty()) {
bool requiresAgileHashes = false;
Universal::Architectures architectures;
mExecutable->architectures(architectures);
for (Universal::Architectures::const_iterator arch = architectures.begin(); arch != architectures.end(); ++arch) {
unique_ptr<MachO> slice(mExecutable->architecture(*arch));
uint32_t limit = 0;
switch (slice->platform()) {
case 0:
// If we don't know the platform, we stay agile.
requiresAgileHashes = true;
continue;
case PLATFORM_MACOS:
// 10.11.4 had first proper sha256 support.
limit = (10 << 16 | 11 << 8 | 4 << 0);
break;
case PLATFORM_TVOS:
case PLATFORM_IOS:
// iOS 11 and tvOS 11 had first proper sha256 support.
limit = (11 << 16 | 0 << 8 | 0 << 0);
break;
case PLATFORM_WATCHOS:
// We stay agile on the watch right now.
requiresAgileHashes = true;
continue;
default:
// All other platforms are assumed to be new and support SHA256.
continue;
}
if (slice->minVersion() < limit) {
// If any slice has a min version less than the limit, than we must remain agile.
requiresAgileHashes = true;
}
}
// Only if every slice met the minimum requirements can we set the digest algorithm to SHA256.
// Otherwise, we leave it empty and let it pick the default which will include legacy hash types.
if (!requiresAgileHashes) {
context.setDigestAlgorithm(kSecCodeSignatureHashSHA256);
}
}
}
//
// Signing base is the start of the Mach-O architecture we're using
//
size_t MachORep::signingBase()
{
return mainExecutableImage()->archOffset();
}
size_t MachORep::signingLimit()
{
unique_ptr<MachO> macho(mExecutable->architecture());
return macho->signingExtent();
}
bool MachORep::needsExecSeg(const MachO& macho) {
uint32_t platform = macho.platform();
// Everything gets an exec segment. This is ignored
// on non-PPL devices, and explicitly wastes some
// space on those devices, but is simpler logic.
return platform != 0;
}
size_t MachORep::execSegBase(const Architecture *arch)
{
unique_ptr<MachO> macho(arch ? mExecutable->architecture(*arch) : mExecutable->architecture());
if (!needsExecSeg(*macho)) {
return 0;
}
segment_command const * const text_cmd = macho->findSegment("__TEXT");
if (text_cmd == NULL) {
return 0;
}
size_t off = 0;
if (macho->is64()) {
off = int_cast<uint64_t,size_t>(reinterpret_cast<segment_command_64 const * const>(text_cmd)->fileoff);
} else {
off = text_cmd->fileoff;
}
return off;
}
size_t MachORep::execSegLimit(const Architecture *arch)
{
unique_ptr<MachO> macho(arch ? mExecutable->architecture(*arch) : mExecutable->architecture());
if (!needsExecSeg(*macho)) {
return 0;
}
segment_command const * const text_cmd = macho->findSegment("__TEXT");
if (text_cmd == NULL) {
return 0;
}
size_t size = 0;
if (macho->is64()) {
size = int_cast<uint64_t,size_t>(reinterpret_cast<segment_command_64 const * const>(text_cmd)->filesize);
} else {
size = text_cmd->filesize;
}
return size;
}
//
// We choose the binary identifier for a Mach-O binary as follows:
// - If the Mach-O headers have a UUID command, use the UUID.
// - Otherwise, use the SHA-1 hash of the (entire) load commands.
//
CFDataRef MachORep::identification()
{
std::unique_ptr<MachO> macho(mainExecutableImage()->architecture());
return identificationFor(macho.get());
}
CFDataRef MachORep::identificationFor(MachO *macho)
{
// if there is a LC_UUID load command, use the UUID contained therein
if (const load_command *cmd = macho->findCommand(LC_UUID)) {
const uuid_command *uuidc = reinterpret_cast<const uuid_command *>(cmd);
// uuidc->cmdsize should be sizeof(uuid_command), so if it is not,
// something is wrong. Fail out.
if (macho->flip(uuidc->cmdsize) != sizeof(uuid_command))
MacOSError::throwMe(errSecCSSignatureInvalid);
char result[4 + sizeof(uuidc->uuid)];
memcpy(result, "UUID", 4);
memcpy(result+4, uuidc->uuid, sizeof(uuidc->uuid));
return makeCFData(result, sizeof(result));
}
// otherwise, use the SHA-1 hash of the entire load command area (this is way, way obsolete)
SHA1 hash;
hash(&macho->header(), sizeof(mach_header));
hash(macho->loadCommands(), macho->commandLength());
SHA1::Digest digest;
hash.finish(digest);
return makeCFData(digest, sizeof(digest));
}
//
// Retrieve a component from the executable.
// This reads the entire signing SuperBlob when first called for an executable,
// and then caches it for further use.
// Note that we could read individual components directly off disk and only cache
// the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected
// to cache the pieces anyway.
//
CFDataRef MachORep::component(CodeDirectory::SpecialSlot slot)
{
switch (slot) {
case cdInfoSlot:
return infoPlist();
default:
return embeddedComponent(slot);
}
}
//
// Retrieve all components, used for signature editing.
//
EditableDiskRep::RawComponentMap MachORep::createRawComponents()
{
EditableDiskRep::RawComponentMap blobMap;
// First call to signingData() caches the result, so this
// _should_ not cause performance issues.
if (NULL == signingData()) {
MacOSError::throwMe(errSecCSUnsigned);
}
const EmbeddedSignatureBlob &blobs = *signingData();
for (unsigned int i = 0; i < blobs.count(); ++i) {
CodeDirectory::Slot slot = blobs.type(i);
const BlobCore *blob = blobs.blob(i);
blobMap[slot] = blobs.blobData(slot, blob);
}
return blobMap;
}
// Retrieve a component from the embedded signature SuperBlob (if present).
// This reads the entire signing SuperBlob when first called for an executable,
// and then caches it for further use.
// Note that we could read individual components directly off disk and only cache
// the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected
// to cache the pieces anyway. But it's not clear that the resulting multiple I/O
// calls wouldn't be slower in the end.
//
CFDataRef MachORep::embeddedComponent(CodeDirectory::SpecialSlot slot)
{
if (signingData()) {
return signingData()->component(slot);
}
// not found
return NULL;
}
EmbeddedSignatureBlob *MachORep::signingData()
{
if (!mSigningData) { // fetch and cache
unique_ptr<MachO> macho(mainExecutableImage()->architecture());
if (macho.get())
if (const linkedit_data_command *cs = macho->findCodeSignature()) {
size_t offset = macho->flip(cs->dataoff);
size_t length = macho->flip(cs->datasize);
if ((mSigningData = EmbeddedSignatureBlob::readBlob(macho->fd(), macho->offset() + offset, length))) {
secinfo("machorep", "%zd signing bytes in %d blob(s) from %s(%s)",
mSigningData->length(), mSigningData->count(),
mainExecutablePath().c_str(), macho->architecture().name());
} else {
secinfo("machorep", "failed to read signing bytes from %s(%s)",
mainExecutablePath().c_str(), macho->architecture().name());
MacOSError::throwMe(errSecCSSignatureInvalid);
}
}
}
return mSigningData;
}
//
// Extract an embedded Info.plist from the file.
// Returns NULL if none is found.
//
CFDataRef MachORep::infoPlist()
{
CFRef<CFDataRef> info;
try {
unique_ptr<MachO> macho(mainExecutableImage()->architecture());
if (const section *sect = macho->findSection("__TEXT", "__info_plist")) {
if (macho->is64()) {
const section_64 *sect64 = reinterpret_cast<const section_64 *>(sect);
info.take(macho->dataAt(macho->flip(sect64->offset), (size_t)macho->flip(sect64->size)));
} else {
info.take(macho->dataAt(macho->flip(sect->offset), macho->flip(sect->size)));
}
}
} catch (...) {
secinfo("machorep", "exception reading embedded Info.plist");
}
return info.yield();
}
//
// Provide a (vaguely) human readable characterization of this code
//
string MachORep::format()
{
if (Universal *fat = mainExecutableImage()) {
Universal::Architectures archs;
fat->architectures(archs);
if (fat->isUniversal()) {
string s = "Mach-O universal (";
for (Universal::Architectures::const_iterator it = archs.begin();
it != archs.end(); ++it) {
if (it != archs.begin())
s += " ";
s += it->displayName();
}
return s + ")";
} else {
assert(archs.size() == 1);
return string("Mach-O thin (") + archs.begin()->displayName() + ")";
}
} else
return "Mach-O (unrecognized format)";
}
//
// Flush cached data
//
void MachORep::flush()
{
size_t offset = mExecutable->offset();
size_t length = mExecutable->length();
delete mExecutable;
mExecutable = NULL;
::free(mSigningData);
mSigningData = NULL;
SingleDiskRep::flush();
mExecutable = new Universal(fd(), offset, length);
}
CFDictionaryRef MachORep::copyDiskRepInformation()
{
unique_ptr<MachO> macho (mainExecutableImage()->architecture());
CFRef<CFDictionaryRef> info;
uint32_t platform = 0;
uint32_t minVersion = 0;
uint32_t sdkVersion = 0;
if (macho->version(&platform, &minVersion, &sdkVersion)) {
/* These keys replace the old kSecCodeInfoDiskRepOSPlatform, kSecCodeInfoDiskRepOSVersionMin
* and kSecCodeInfoDiskRepOSSDKVersion. The keys were renamed because we changed what value
* "platform" represents: For the old key, the actual load command (e.g. LC_VERSION_MIN_MACOSX)
* was returned; for the new key, we return one of the PLATFORM_* values used by LC_BUILD_VERSION.
*
* The keys are private and undocumented, and maintaining a translation table between the old and
* new domain would provide little value at high cost, but we do remove the old keys to make
* the change obvious.
*/
info.take(cfmake<CFMutableDictionaryRef>("{%O = %d,%O = %d,%O = %d}",
kSecCodeInfoDiskRepVersionPlatform, platform,
kSecCodeInfoDiskRepVersionMin, minVersion,
kSecCodeInfoDiskRepVersionSDK, sdkVersion));
if (platform == PLATFORM_MACOS && sdkVersion < (10 << 16 | 9 << 8))
{
info.take(cfmake<CFMutableDictionaryRef>("{+%O, %O = 'OS X SDK version before 10.9 does not support Library Validation'}",
info.get(),
kSecCodeInfoDiskRepNoLibraryValidation));
}
}
return info.yield();
}
//
// Return a recommended unique identifier.
// If our file has an embedded Info.plist, use the CFBundleIdentifier from that.
// Otherwise, use the default.
//
string MachORep::recommendedIdentifier(const SigningContext &ctx)
{
if (CFDataRef info = infoPlist()) {
if (CFRef<CFDictionaryRef> dict = makeCFDictionaryFrom(info)) {
CFStringRef code = CFStringRef(CFDictionaryGetValue(dict, kCFBundleIdentifierKey));
if (code && CFGetTypeID(code) != CFStringGetTypeID())
MacOSError::throwMe(errSecCSBadDictionaryFormat);
if (code)
return cfString(code);
} else
MacOSError::throwMe(errSecCSBadDictionaryFormat);
}
// ah well. Use the default
return SingleDiskRep::recommendedIdentifier(ctx);
}
//
// The default suggested requirements for Mach-O binaries are as follows:
// Library requirement: Composed from dynamic load commands.
//
const Requirements *MachORep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
{
assert(arch); // enforced by signing infrastructure
Requirements::Maker maker;
// add library requirements from DYLIB commands (if any)
if (Requirement *libreq = libraryRequirements(arch, ctx))
maker.add(kSecLibraryRequirementType, libreq); // takes ownership
// that's all
return maker.make();
}
Requirement *MachORep::libraryRequirements(const Architecture *arch, const SigningContext &ctx)
{
unique_ptr<MachO> macho(mainExecutableImage()->architecture(*arch));
Requirement::Maker maker;
Requirement::Maker::Chain chain(maker, opOr);
if (macho.get())
if (const linkedit_data_command *ldep = macho->findLibraryDependencies()) {
size_t offset = macho->flip(ldep->dataoff);
size_t length = macho->flip(ldep->datasize);
if (LibraryDependencyBlob *deplist = LibraryDependencyBlob::readBlob(macho->fd(), macho->offset() + offset, length)) {
try {
secinfo("machorep", "%zd library dependency bytes in %d blob(s) from %s(%s)",
deplist->length(), deplist->count(),
mainExecutablePath().c_str(), macho->architecture().name());
unsigned count = deplist->count();
// we could walk through DYLIB load commands in parallel. We just don't need anything from them so far
for (unsigned n = 0; n < count; n++) {
const Requirement *req = NULL;
if (const BlobCore *dep = deplist->blob(n)) {
if ((req = Requirement::specific(dep))) {
// binary code requirement; good to go
} else if (const BlobWrapper *wrap = BlobWrapper::specific(dep)) {
// blob-wrapped text form - convert to binary requirement
std::string reqString = std::string((const char *)wrap->data(), wrap->length());
CFRef<SecRequirementRef> areq;
MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &areq.aref()));
CFRef<CFDataRef> reqData;
MacOSError::check(SecRequirementCopyData(areq, kSecCSDefaultFlags, &reqData.aref()));
req = Requirement::specific((const BlobCore *)CFDataGetBytePtr(reqData));
} else {
secinfo("machorep", "unexpected blob type 0x%x in slot %d of binary dependencies", dep->magic(), n);
continue;
}
chain.add();
maker.copy(req);
} else
secinfo("machorep", "missing DR info for library index %d", n);
}
::free(deplist);
} catch (...) {
::free(deplist);
throw;
}
}
}
if (chain.empty())
return NULL;
else
return maker.make();
}
//
// Default to system page size for segmented (paged) signatures
//
size_t MachORep::pageSize(const SigningContext &)
{
return segmentedPageSize;
}
//
// Strict validation
//
void MachORep::strictValidate(const CodeDirectory* cd, const ToleratedErrors& tolerated, SecCSFlags flags)
{
SingleDiskRep::strictValidate(cd, tolerated, flags);
// if the constructor found suspicious issues, fail a struct validation now
if (mExecutable->isSuspicious() && tolerated.find(errSecCSBadMainExecutable) == tolerated.end())
MacOSError::throwMe(errSecCSBadMainExecutable);
}
//
// FileDiskRep::Writers
//
DiskRep::Writer *MachORep::writer()
{
return new Writer(this);
}
//
// Write a component.
// MachORep::Writers don't write to components directly; the signing code uses special
// knowledge of the Mach-O format to build embedded signatures and blasts them directly
// to disk. Thus this implementation will never be called (and, if called, will simply fail).
//
void MachORep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
{
assert(false);
Syslog::notice("code signing internal error: trying to write Mach-O component directly");
MacOSError::throwMe(errSecCSInternalError);
}
void MachORep::registerStapledTicket()
{
CFRef<CFDataRef> data = NULL;
if (mSigningData) {
data.take(mSigningData->component(cdTicketSlot));
registerStapledTicketInMachO(data);
}
}
} // end namespace CodeSigning
} // end namespace Security