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