]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_codesigning/lib/bundlediskrep.cpp
Security-57337.60.2.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / bundlediskrep.cpp
1 /*
2 * Copyright (c) 2006-2014 Apple 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 #include "bundlediskrep.h"
24 #include "filediskrep.h"
25 #include "dirscanner.h"
26 #include <CoreFoundation/CFBundlePriv.h>
27 #include <CoreFoundation/CFURLAccess.h>
28 #include <CoreFoundation/CFBundlePriv.h>
29 #include <security_utilities/cfmunge.h>
30 #include <copyfile.h>
31 #include <fts.h>
32 #include <sstream>
33
34 namespace Security {
35 namespace CodeSigning {
36
37 using namespace UnixPlusPlus;
38
39
40 //
41 // Local helpers
42 //
43 static std::string findDistFile(const std::string &directory);
44
45
46 //
47 // We make a CFBundleRef immediately, but everything else is lazy
48 //
49 BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx)
50 : mBundle(CFBundleCreate(NULL, CFTempURL(path)))
51 {
52 if (!mBundle)
53 MacOSError::throwMe(errSecCSBadBundleFormat);
54 setup(ctx);
55 CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep);
56 }
57
58 BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx)
59 {
60 mBundle = ref; // retains
61 setup(ctx);
62 CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep);
63 }
64
65 BundleDiskRep::~BundleDiskRep()
66 {
67 }
68
69 void BundleDiskRep::checkMoved(CFURLRef oldPath, CFURLRef newPath)
70 {
71 char cOld[PATH_MAX];
72 char cNew[PATH_MAX];
73 // The realpath call is important because alot of Framework bundles have a symlink
74 // to their "Current" version binary in the main bundle
75 if (realpath(cfString(oldPath).c_str(), cOld) == NULL ||
76 realpath(cfString(newPath).c_str(), cNew) == NULL)
77 MacOSError::throwMe(errSecCSInternalError);
78
79 if (strcmp(cOld, cNew) != 0)
80 recordStrictError(errSecCSAmbiguousBundleFormat);
81 }
82
83 // common construction code
84 void BundleDiskRep::setup(const Context *ctx)
85 {
86 mInstallerPackage = false; // default
87 mAppLike = false; // pessimism first
88 bool appDisqualified = false; // found reason to disqualify as app
89
90 // capture the path of the main executable before descending into a specific version
91 CFRef<CFURLRef> mainExecBefore = CFBundleCopyExecutableURL(mBundle);
92 CFRef<CFURLRef> infoPlistBefore = _CFBundleCopyInfoPlistURL(mBundle);
93
94 // validate the bundle root; fish around for the desired framework version
95 string root = cfStringRelease(copyCanonicalPath());
96 string contents = root + "/Contents";
97 string supportFiles = root + "/Support Files";
98 string version = root + "/Versions/"
99 + ((ctx && ctx->version) ? ctx->version : "Current")
100 + "/.";
101 if (::access(contents.c_str(), F_OK) == 0) { // not shallow
102 DirValidator val;
103 val.require("^Contents$", DirValidator::directory); // duh
104 val.allow("^(\\.LSOverride|\\.DS_Store|Icon\r|\\.SoftwareDepot\\.tracking)$", DirValidator::file | DirValidator::noexec);
105 try {
106 val.validate(root, errSecCSUnsealedAppRoot);
107 } catch (const MacOSError &err) {
108 recordStrictError(err.error);
109 }
110 } else if (::access(supportFiles.c_str(), F_OK) == 0) { // ancient legacy boondoggle bundle
111 // treat like a shallow bundle; do not allow Versions arbitration
112 appDisqualified = true;
113 } else if (::access(version.c_str(), F_OK) == 0) { // versioned bundle
114 if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version)))
115 mBundle.take(versionBundle); // replace top bundle ref
116 else
117 MacOSError::throwMe(errSecCSStaticCodeNotFound);
118 appDisqualified = true;
119 validateFrameworkRoot(root);
120 } else {
121 if (ctx && ctx->version) // explicitly specified
122 MacOSError::throwMe(errSecCSStaticCodeNotFound);
123 }
124
125 CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
126 assert(infoDict); // CFBundle will always make one up for us
127 CFTypeRef mainHTML = CFDictionaryGetValue(infoDict, CFSTR("MainHTML"));
128 CFTypeRef packageVersion = CFDictionaryGetValue(infoDict, CFSTR("IFMajorVersion"));
129
130 // conventional executable bundle: CFBundle identifies an executable for us
131 if (CFRef<CFURLRef> mainExec = CFBundleCopyExecutableURL(mBundle)) // if CFBundle claims an executable...
132 if (mainHTML == NULL) { // ... and it's not a widget
133
134 // Note that this check is skipped if there is a specific framework version checked.
135 // That's because you know what you are doing if you are looking at a specific version.
136 // This check is designed to stop someone who did a verification on an app root, from mistakenly
137 // verifying a framework
138 if (!ctx || !ctx->version) {
139 if (mainExecBefore)
140 checkMoved(mainExecBefore, mainExec);
141 if (infoPlistBefore)
142 if (CFRef<CFURLRef> infoDictPath = _CFBundleCopyInfoPlistURL(mBundle))
143 checkMoved(infoPlistBefore, infoDictPath);
144 }
145
146 mMainExecutableURL = mainExec;
147 mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx);
148 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
149 recordStrictError(errSecCSRegularFile);
150 CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle);
151 bool isAppBundle = false;
152 if (infoDict)
153 if (CFTypeRef packageType = CFDictionaryGetValue(infoDict, CFSTR("CFBundlePackageType")))
154 if (CFEqual(packageType, CFSTR("APPL")))
155 isAppBundle = true;
156 mFormat = "bundle with " + mExecRep->format();
157 if (isAppBundle)
158 mFormat = "app " + mFormat;
159 mAppLike = isAppBundle && !appDisqualified;
160 return;
161 }
162
163 // widget
164 if (mainHTML) {
165 if (CFGetTypeID(mainHTML) != CFStringGetTypeID())
166 MacOSError::throwMe(errSecCSBadBundleFormat);
167 mMainExecutableURL.take(makeCFURL(cfString(CFStringRef(mainHTML)), false,
168 CFRef<CFURLRef>(CFBundleCopySupportFilesDirectoryURL(mBundle))));
169 if (!mMainExecutableURL)
170 MacOSError::throwMe(errSecCSBadBundleFormat);
171 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
172 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
173 recordStrictError(errSecCSRegularFile);
174 mFormat = "widget bundle";
175 mAppLike = true;
176 return;
177 }
178
179 // do we have a real Info.plist here?
180 if (CFRef<CFURLRef> infoURL = _CFBundleCopyInfoPlistURL(mBundle)) {
181 // focus on the Info.plist (which we know exists) as the nominal "main executable" file
182 mMainExecutableURL = infoURL;
183 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
184 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
185 recordStrictError(errSecCSRegularFile);
186 if (packageVersion) {
187 mInstallerPackage = true;
188 mFormat = "installer package bundle";
189 } else {
190 mFormat = "bundle";
191 }
192 return;
193 }
194
195 // we're getting desperate here. Perhaps an oldish-style installer package? Look for a *.dist file
196 std::string distFile = findDistFile(this->resourcesRootPath());
197 if (!distFile.empty()) {
198 mMainExecutableURL = makeCFURL(distFile);
199 mExecRep = new FileDiskRep(this->mainExecutablePath().c_str());
200 if (!mExecRep->fd().isPlainFile(this->mainExecutablePath()))
201 recordStrictError(errSecCSRegularFile);
202 mInstallerPackage = true;
203 mFormat = "installer package bundle";
204 return;
205 }
206
207 // this bundle cannot be signed
208 MacOSError::throwMe(errSecCSBadBundleFormat);
209 }
210
211
212 //
213 // Return the full path to the one-and-only file named something.dist in a directory.
214 // Return empty string if none; throw an exception if multiple. Do not descend into subdirectories.
215 //
216 static std::string findDistFile(const std::string &directory)
217 {
218 std::string found;
219 char *paths[] = {(char *)directory.c_str(), NULL};
220 FTS *fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR | FTS_NOSTAT, NULL);
221 bool root = true;
222 while (FTSENT *ent = fts_read(fts)) {
223 switch (ent->fts_info) {
224 case FTS_F:
225 case FTS_NSOK:
226 if (!strcmp(ent->fts_path + ent->fts_pathlen - 5, ".dist")) { // found plain file foo.dist
227 if (found.empty()) // first found
228 found = ent->fts_path;
229 else // multiple *.dist files (bad)
230 MacOSError::throwMe(errSecCSBadBundleFormat);
231 }
232 break;
233 case FTS_D:
234 if (!root)
235 fts_set(fts, ent, FTS_SKIP); // don't descend
236 root = false;
237 break;
238 default:
239 break;
240 }
241 }
242 fts_close(fts);
243 return found;
244 }
245
246
247 //
248 // Create a path to a bundle signing resource, by name.
249 // If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files
250 // will be read and written there. Otherwise, they go directly into the support directory.
251 //
252 string BundleDiskRep::metaPath(const char *name)
253 {
254 if (mMetaPath.empty()) {
255 string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
256 mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY;
257 if (::access(mMetaPath.c_str(), F_OK) == 0) {
258 mMetaExists = true;
259 } else {
260 mMetaPath = support;
261 mMetaExists = false;
262 }
263 }
264 return mMetaPath + "/" + name;
265 }
266
267
268 //
269 // Try to create the meta-file directory in our bundle.
270 // Does nothing if the directory already exists.
271 // Throws if an error occurs.
272 //
273 void BundleDiskRep::createMeta()
274 {
275 string meta = metaPath(BUNDLEDISKREP_DIRECTORY);
276 if (!mMetaExists) {
277 if (::mkdir(meta.c_str(), 0755) == 0) {
278 copyfile(cfStringRelease(copyCanonicalPath()).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY);
279 mMetaPath = meta;
280 mMetaExists = true;
281 } else if (errno != EEXIST)
282 UnixError::throwMe();
283 }
284 }
285
286 //
287 // Load's a CFURL and makes sure that it is a regular file and not a symlink (or fifo, etc.)
288 //
289 CFDataRef BundleDiskRep::loadRegularFile(CFURLRef url)
290 {
291 assert(url);
292
293 CFDataRef data = NULL;
294
295 std::string path(cfString(url));
296
297 AutoFileDesc fd(path);
298
299 if (!fd.isPlainFile(path))
300 recordStrictError(errSecCSRegularFile);
301
302 data = cfLoadFile(fd, fd.fileSize());
303
304 if (!data) {
305 secdebug(__PRETTY_FUNCTION__, "failed to load %s", cfString(url).c_str());
306 MacOSError::throwMe(errSecCSInternalError);
307 }
308
309 return data;
310 }
311
312 //
313 // Load and return a component, by slot number.
314 // Info.plist components come from the bundle, always (we don't look
315 // for Mach-O embedded versions).
316 // Everything else comes from the embedded blobs of a Mach-O image, or from
317 // files located in the Contents directory of the bundle.
318 //
319 CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot)
320 {
321 switch (slot) {
322 // the Info.plist comes from the magic CFBundle-indicated place and ONLY from there
323 case cdInfoSlot:
324 if (CFRef<CFURLRef> info = _CFBundleCopyInfoPlistURL(mBundle))
325 return loadRegularFile(info);
326 else
327 return NULL;
328 // by default, we take components from the executable image or files
329 default:
330 if (CFDataRef data = mExecRep->component(slot))
331 return data;
332 // falling through
333 // but the following always come from files
334 case cdResourceDirSlot:
335 if (const char *name = CodeDirectory::canonicalSlotName(slot))
336 return metaData(name);
337 else
338 return NULL;
339 }
340 }
341
342
343 //
344 // The binary identifier is taken directly from the main executable.
345 //
346 CFDataRef BundleDiskRep::identification()
347 {
348 return mExecRep->identification();
349 }
350
351
352 //
353 // Various aspects of our DiskRep personality.
354 //
355 CFURLRef BundleDiskRep::copyCanonicalPath()
356 {
357 if (CFURLRef url = CFBundleCopyBundleURL(mBundle))
358 return url;
359 CFError::throwMe();
360 }
361
362 string BundleDiskRep::mainExecutablePath()
363 {
364 return cfString(mMainExecutableURL);
365 }
366
367 string BundleDiskRep::resourcesRootPath()
368 {
369 return cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle));
370 }
371
372 void BundleDiskRep::adjustResources(ResourceBuilder &builder)
373 {
374 // exclude entire contents of meta directory
375 builder.addExclusion("^" BUNDLEDISKREP_DIRECTORY "$");
376 builder.addExclusion("^" CODERESOURCES_LINK "$"); // ancient-ish symlink into it
377
378 // exclude the store manifest directory
379 builder.addExclusion("^" STORE_RECEIPT_DIRECTORY "$");
380
381 // exclude the main executable file
382 string resources = resourcesRootPath();
383 if (resources.compare(resources.size() - 2, 2, "/.") == 0) // chop trailing /.
384 resources = resources.substr(0, resources.size()-2);
385 string executable = mainExecutablePath();
386 if (!executable.compare(0, resources.length(), resources, 0, resources.length())
387 && executable[resources.length()] == '/') // is proper directory prefix
388 builder.addExclusion(string("^")
389 + ResourceBuilder::escapeRE(executable.substr(resources.length()+1)) + "$", ResourceBuilder::softTarget);
390 }
391
392
393
394 Universal *BundleDiskRep::mainExecutableImage()
395 {
396 return mExecRep->mainExecutableImage();
397 }
398
399 void BundleDiskRep::prepareForSigning(SigningContext &context)
400 {
401 return mExecRep->prepareForSigning(context);
402 }
403
404 size_t BundleDiskRep::signingBase()
405 {
406 return mExecRep->signingBase();
407 }
408
409 size_t BundleDiskRep::signingLimit()
410 {
411 return mExecRep->signingLimit();
412 }
413
414 string BundleDiskRep::format()
415 {
416 return mFormat;
417 }
418
419 CFArrayRef BundleDiskRep::modifiedFiles()
420 {
421 CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles());
422 checkModifiedFile(files, cdCodeDirectorySlot);
423 checkModifiedFile(files, cdSignatureSlot);
424 checkModifiedFile(files, cdResourceDirSlot);
425 checkModifiedFile(files, cdTopDirectorySlot);
426 checkModifiedFile(files, cdEntitlementSlot);
427 checkModifiedFile(files, cdRepSpecificSlot);
428 for (CodeDirectory::Slot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; ++slot)
429 checkModifiedFile(files, slot);
430 return files;
431 }
432
433 void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot)
434 {
435 if (CFDataRef data = mExecRep->component(slot)) // provided by executable file
436 CFRelease(data);
437 else if (const char *resourceName = CodeDirectory::canonicalSlotName(slot)) {
438 string file = metaPath(resourceName);
439 if (::access(file.c_str(), F_OK) == 0)
440 CFArrayAppendValue(files, CFTempURL(file));
441 }
442 }
443
444 FileDesc &BundleDiskRep::fd()
445 {
446 return mExecRep->fd();
447 }
448
449 void BundleDiskRep::flush()
450 {
451 mExecRep->flush();
452 }
453
454
455 //
456 // Defaults for signing operations
457 //
458 string BundleDiskRep::recommendedIdentifier(const SigningContext &)
459 {
460 if (CFStringRef identifier = CFBundleGetIdentifier(mBundle))
461 return cfString(identifier);
462 if (CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle))
463 if (CFStringRef identifier = CFStringRef(CFDictionaryGetValue(infoDict, kCFBundleNameKey)))
464 return cfString(identifier);
465
466 // fall back to using the canonical path
467 return canonicalIdentifier(cfStringRelease(this->copyCanonicalPath()));
468 }
469
470 string BundleDiskRep::resourcesRelativePath()
471 {
472 // figure out the resource directory base. Clean up some gunk inserted by CFBundle in frameworks
473 string rbase = this->resourcesRootPath();
474 size_t pos = rbase.find("/./"); // gratuitously inserted by CFBundle in some frameworks
475 while (pos != std::string::npos) {
476 rbase = rbase.replace(pos, 2, "", 0);
477 pos = rbase.find("/./");
478 }
479 if (rbase.substr(rbase.length()-2, 2) == "/.") // produced by versioned bundle implicit "Current" case
480 rbase = rbase.substr(0, rbase.length()-2); // ... so take it off for this
481
482 // find the resources directory relative to the resource base
483 string resources = cfStringRelease(CFBundleCopyResourcesDirectoryURL(mBundle));
484 if (resources == rbase)
485 resources = "";
486 else if (resources.compare(0, rbase.length(), rbase, 0, rbase.length()) != 0) // Resources not in resource root
487 MacOSError::throwMe(errSecCSBadBundleFormat);
488 else
489 resources = resources.substr(rbase.length() + 1) + "/"; // differential path segment
490
491 return resources;
492 }
493
494 CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &ctx)
495 {
496 string resources = this->resourcesRelativePath();
497
498 // installer package rules
499 if (mInstallerPackage)
500 return cfmake<CFDictionaryRef>("{rules={"
501 "'^.*' = #T" // include everything, but...
502 "%s = {optional=#T, weight=1000}" // make localizations optional
503 "'^.*/.*\\.pkg/' = {omit=#T, weight=10000}" // and exclude all nested packages (by name)
504 "}}",
505 (string("^") + resources + ".*\\.lproj/").c_str()
506 );
507
508 // old (V1) executable bundle rules - compatible with before
509 if (ctx.signingFlags() & kSecCSSignV1) // *** must be exactly the same as before ***
510 return cfmake<CFDictionaryRef>("{rules={"
511 "'^version.plist$' = #T" // include version.plist
512 "%s = #T" // include Resources
513 "%s = {optional=#T, weight=1000}" // make localizations optional
514 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
515 "}}",
516 (string("^") + resources).c_str(),
517 (string("^") + resources + ".*\\.lproj/").c_str(),
518 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
519 );
520
521 // FMJ (everything is a resource) rules
522 if (ctx.signingFlags() & kSecCSSignOpaque) // Full Metal Jacket - everything is a resource file
523 return cfmake<CFDictionaryRef>("{rules={"
524 "'^.*' = #T" // everything is a resource
525 "'^Info\\.plist$' = {omit=#T,weight=10}" // explicitly exclude this for backward compatibility
526 "}}");
527
528 // new (V2) executable bundle rules
529 return cfmake<CFDictionaryRef>("{" // *** the new (V2) world ***
530 "rules={" // old (V1; legacy) version
531 "'^version.plist$' = #T" // include version.plist
532 "%s = #T" // include Resources
533 "%s = {optional=#T, weight=1000}" // make localizations optional
534 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
535 "},rules2={"
536 "'^.*' = #T" // include everything as a resource, with the following exceptions
537 "'^[^/]+$' = {nested=#T, weight=10}" // files directly in Contents
538 "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories
539 "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code)
540 "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files
541 "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told
542 "'^version\\.plist$' = {weight=20}" // include version.plist as resource
543 "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource
544 "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included
545 "%s = {weight=20}" // Resources override default nested (widgets)
546 "%s = {optional=#T, weight=1000}" // make localizations optional
547 "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files
548 "}}",
549
550 (string("^") + resources).c_str(),
551 (string("^") + resources + ".*\\.lproj/").c_str(),
552 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(),
553
554 (string("^") + resources).c_str(),
555 (string("^") + resources + ".*\\.lproj/").c_str(),
556 (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str()
557 );
558 }
559
560
561 CFArrayRef BundleDiskRep::allowedResourceOmissions()
562 {
563 return cfmake<CFArrayRef>("["
564 "'^(.*/)?\\.DS_Store$'"
565 "'^Info\\.plist$'"
566 "'^PkgInfo$'"
567 "%s"
568 "]",
569 (string("^") + this->resourcesRelativePath() + ".*\\.lproj/locversion.plist$").c_str()
570 );
571 }
572
573
574 const Requirements *BundleDiskRep::defaultRequirements(const Architecture *arch, const SigningContext &ctx)
575 {
576 return mExecRep->defaultRequirements(arch, ctx);
577 }
578
579 size_t BundleDiskRep::pageSize(const SigningContext &ctx)
580 {
581 return mExecRep->pageSize(ctx);
582 }
583
584
585 //
586 // Strict validation.
587 // Takes an array of CFNumbers of errors to tolerate.
588 //
589 void BundleDiskRep::strictValidate(const CodeDirectory* cd, const ToleratedErrors& tolerated, SecCSFlags flags)
590 {
591 std::vector<OSStatus> fatalErrors;
592 set_difference(mStrictErrors.begin(), mStrictErrors.end(), tolerated.begin(), tolerated.end(), back_inserter(fatalErrors));
593 if (!fatalErrors.empty())
594 MacOSError::throwMe(fatalErrors[0]);
595
596 // if app focus is requested and this doesn't look like an app, fail - but allow whitelist overrides
597 if (flags & kSecCSRestrictToAppLike)
598 if (!mAppLike)
599 if (tolerated.find(kSecCSRestrictToAppLike) == tolerated.end())
600 MacOSError::throwMe(errSecCSNotAppLike);
601
602 // now strict-check the main executable (which won't be an app-like object)
603 mExecRep->strictValidate(cd, tolerated, flags & ~kSecCSRestrictToAppLike);
604 }
605
606 void BundleDiskRep::recordStrictError(OSStatus error)
607 {
608 mStrictErrors.insert(error);
609 }
610
611
612 //
613 // Check framework root for unsafe symlinks and unsealed content.
614 //
615 void BundleDiskRep::validateFrameworkRoot(string root)
616 {
617 // build regex element that matches either the "Current" symlink, or the name of the current version
618 string current = "Current";
619 char currentVersion[PATH_MAX];
620 ssize_t len = ::readlink((root + "/Versions/Current").c_str(), currentVersion, sizeof(currentVersion)-1);
621 if (len > 0) {
622 currentVersion[len] = '\0';
623 current = string("(Current|") + ResourceBuilder::escapeRE(currentVersion) + ")";
624 }
625
626 DirValidator val;
627 val.require("^Versions$", DirValidator::directory | DirValidator::descend); // descend into Versions directory
628 val.require("^Versions/[^/]+$", DirValidator::directory); // require at least one version
629 val.require("^Versions/Current$", DirValidator::symlink, // require Current symlink...
630 "^(\\./)?(\\.\\.[^/]+|\\.?[^\\./][^/]*)$"); // ...must point to a version
631 val.allow("^(Versions/)?\\.DS_Store$", DirValidator::file | DirValidator::noexec); // allow .DS_Store files
632 val.allow("^[^/]+$", DirValidator::symlink, ^ string (const string &name, const string &target) {
633 // top-level symlinks must point to namesake in current version
634 return string("^(\\./)?Versions/") + current + "/" + ResourceBuilder::escapeRE(name) + "$";
635 });
636 // module.map must be regular non-executable file, or symlink to module.map in current version
637 val.allow("^module\\.map$", DirValidator::file | DirValidator::noexec | DirValidator::symlink,
638 string("^(\\./)?Versions/") + current + "/module\\.map$");
639
640 try {
641 val.validate(root, errSecCSUnsealedFrameworkRoot);
642 } catch (const MacOSError &err) {
643 recordStrictError(err.error);
644 }
645 }
646
647
648 //
649 // Writers
650 //
651 DiskRep::Writer *BundleDiskRep::writer()
652 {
653 return new Writer(this);
654 }
655
656 BundleDiskRep::Writer::Writer(BundleDiskRep *r)
657 : rep(r), mMadeMetaDirectory(false)
658 {
659 execWriter = rep->mExecRep->writer();
660 }
661
662
663 //
664 // Write a component.
665 // Note that this isn't concerned with Mach-O writing; this is handled at
666 // a much higher level. If we're called, we write to a file in the Bundle's meta directory.
667 //
668 void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data)
669 {
670 switch (slot) {
671 default:
672 if (!execWriter->attribute(writerLastResort)) // willing to take the data...
673 return execWriter->component(slot, data); // ... so hand it through
674 // execWriter doesn't want the data; store it as a resource file (below)
675 case cdResourceDirSlot:
676 // the resource directory always goes into a bundle file
677 if (const char *name = CodeDirectory::canonicalSlotName(slot)) {
678 rep->createMeta();
679 string path = rep->metaPath(name);
680 AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
681 fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data));
682 } else
683 MacOSError::throwMe(errSecCSBadBundleFormat);
684 }
685 }
686
687
688 //
689 // Remove all signature data
690 //
691 void BundleDiskRep::Writer::remove()
692 {
693 // remove signature from the executable
694 execWriter->remove();
695
696 // remove signature files from bundle
697 for (CodeDirectory::SpecialSlot slot = 0; slot < cdSlotCount; slot++)
698 remove(slot);
699 remove(cdSignatureSlot);
700 }
701
702 void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot)
703 {
704 if (const char *name = CodeDirectory::canonicalSlotName(slot))
705 if (::unlink(rep->metaPath(name).c_str()))
706 switch (errno) {
707 case ENOENT: // not found - that's okay
708 break;
709 default:
710 UnixError::throwMe();
711 }
712 }
713
714
715 void BundleDiskRep::Writer::flush()
716 {
717 execWriter->flush();
718 }
719
720
721 } // end namespace CodeSigning
722 } // end namespace Security