X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/e3d460c9de4426da6c630c3ae3f46173a99f82d8..7e6b461318c8a779d91381531435a68ee4e8b6ed:/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp?ds=inline diff --git a/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp b/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp index 571c33e1..7c490970 100644 --- a/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp +++ b/OSX/libsecurity_codesigning/lib/bundlediskrep.cpp @@ -23,6 +23,7 @@ #include "bundlediskrep.h" #include "filediskrep.h" #include "dirscanner.h" +#include "notarization.h" #include #include #include @@ -47,11 +48,12 @@ static std::string findDistFile(const std::string &directory); // We make a CFBundleRef immediately, but everything else is lazy // BundleDiskRep::BundleDiskRep(const char *path, const Context *ctx) - : mBundle(CFBundleCreate(NULL, CFTempURL(path))) + : mBundle(_CFBundleCreateUnique(NULL, CFTempURL(path))), forcePlatform(false) { if (!mBundle) MacOSError::throwMe(errSecCSBadBundleFormat); setup(ctx); + forcePlatform = mExecRep->appleInternalForcePlatform(); CODESIGN_DISKREP_CREATE_BUNDLE_PATH(this, (char*)path, (void*)ctx, mExecRep); } @@ -59,6 +61,7 @@ BundleDiskRep::BundleDiskRep(CFBundleRef ref, const Context *ctx) { mBundle = ref; // retains setup(ctx); + forcePlatform = mExecRep->appleInternalForcePlatform(); CODESIGN_DISKREP_CREATE_BUNDLE_REF(this, ref, (void*)ctx, mExecRep); } @@ -74,7 +77,7 @@ void BundleDiskRep::checkMoved(CFURLRef oldPath, CFURLRef newPath) // to their "Current" version binary in the main bundle if (realpath(cfString(oldPath).c_str(), cOld) == NULL || realpath(cfString(newPath).c_str(), cNew) == NULL) - MacOSError::throwMe(errSecCSInternalError); + MacOSError::throwMe(errSecCSAmbiguousBundleFormat); if (strcmp(cOld, cNew) != 0) recordStrictError(errSecCSAmbiguousBundleFormat); @@ -83,6 +86,7 @@ void BundleDiskRep::checkMoved(CFURLRef oldPath, CFURLRef newPath) // common construction code void BundleDiskRep::setup(const Context *ctx) { + mComponentsFromExecValid = false; // not yet known mInstallerPackage = false; // default mAppLike = false; // pessimism first bool appDisqualified = false; // found reason to disqualify as app @@ -93,6 +97,8 @@ void BundleDiskRep::setup(const Context *ctx) // validate the bundle root; fish around for the desired framework version string root = cfStringRelease(copyCanonicalPath()); + if (filehasExtendedAttribute(root, XATTR_FINDERINFO_NAME)) + recordStrictError(errSecCSInvalidAssociatedFileData); string contents = root + "/Contents"; string supportFiles = root + "/Support Files"; string version = root + "/Versions/" @@ -111,7 +117,7 @@ void BundleDiskRep::setup(const Context *ctx) // treat like a shallow bundle; do not allow Versions arbitration appDisqualified = true; } else if (::access(version.c_str(), F_OK) == 0) { // versioned bundle - if (CFBundleRef versionBundle = CFBundleCreate(NULL, CFTempURL(version))) + if (CFBundleRef versionBundle = _CFBundleCreateUnique(NULL, CFTempURL(version))) mBundle.take(versionBundle); // replace top bundle ref else MacOSError::throwMe(errSecCSStaticCodeNotFound); @@ -145,8 +151,7 @@ void BundleDiskRep::setup(const Context *ctx) mMainExecutableURL = mainExec; mExecRep = DiskRep::bestFileGuess(this->mainExecutablePath(), ctx); - if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) - recordStrictError(errSecCSRegularFile); + checkPlainFile(mExecRep->fd(), this->mainExecutablePath()); CFDictionaryRef infoDict = CFBundleGetInfoDictionary(mBundle); bool isAppBundle = false; if (infoDict) @@ -169,8 +174,7 @@ void BundleDiskRep::setup(const Context *ctx) if (!mMainExecutableURL) MacOSError::throwMe(errSecCSBadBundleFormat); mExecRep = new FileDiskRep(this->mainExecutablePath().c_str()); - if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) - recordStrictError(errSecCSRegularFile); + checkPlainFile(mExecRep->fd(), this->mainExecutablePath()); mFormat = "widget bundle"; mAppLike = true; return; @@ -181,8 +185,7 @@ void BundleDiskRep::setup(const Context *ctx) // focus on the Info.plist (which we know exists) as the nominal "main executable" file mMainExecutableURL = infoURL; mExecRep = new FileDiskRep(this->mainExecutablePath().c_str()); - if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) - recordStrictError(errSecCSRegularFile); + checkPlainFile(mExecRep->fd(), this->mainExecutablePath()); if (packageVersion) { mInstallerPackage = true; mFormat = "installer package bundle"; @@ -197,8 +200,7 @@ void BundleDiskRep::setup(const Context *ctx) if (!distFile.empty()) { mMainExecutableURL = makeCFURL(distFile); mExecRep = new FileDiskRep(this->mainExecutablePath().c_str()); - if (!mExecRep->fd().isPlainFile(this->mainExecutablePath())) - recordStrictError(errSecCSRegularFile); + checkPlainFile(mExecRep->fd(), this->mainExecutablePath()); mInstallerPackage = true; mFormat = "installer package bundle"; return; @@ -244,27 +246,6 @@ static std::string findDistFile(const std::string &directory) } -// -// Create a path to a bundle signing resource, by name. -// If the BUNDLEDISKREP_DIRECTORY directory exists in the bundle's support directory, files -// will be read and written there. Otherwise, they go directly into the support directory. -// -string BundleDiskRep::metaPath(const char *name) -{ - if (mMetaPath.empty()) { - string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle)); - mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY; - if (::access(mMetaPath.c_str(), F_OK) == 0) { - mMetaExists = true; - } else { - mMetaPath = support; - mMetaExists = false; - } - } - return mMetaPath + "/" + name; -} - - // // Try to create the meta-file directory in our bundle. // Does nothing if the directory already exists. @@ -272,7 +253,7 @@ string BundleDiskRep::metaPath(const char *name) // void BundleDiskRep::createMeta() { - string meta = metaPath(BUNDLEDISKREP_DIRECTORY); + string meta = metaPath(NULL); if (!mMetaExists) { if (::mkdir(meta.c_str(), 0755) == 0) { copyfile(cfStringRelease(copyCanonicalPath()).c_str(), meta.c_str(), NULL, COPYFILE_SECURITY); @@ -282,7 +263,45 @@ void BundleDiskRep::createMeta() UnixError::throwMe(); } } + +// +// Create a path to a bundle signing resource, by name. +// This is in the BUNDLEDISKREP_DIRECTORY directory in the bundle's support directory. +// +string BundleDiskRep::metaPath(const char *name) +{ + if (mMetaPath.empty()) { + string support = cfStringRelease(CFBundleCopySupportFilesDirectoryURL(mBundle)); + mMetaPath = support + "/" BUNDLEDISKREP_DIRECTORY; + mMetaExists = ::access(mMetaPath.c_str(), F_OK) == 0; + } + if (name) + return mMetaPath + "/" + name; + else + return mMetaPath; +} + +CFDataRef BundleDiskRep::metaData(const char *name) +{ + if (CFRef url = makeCFURL(metaPath(name))) { + return cfLoadFile(url); + } else { + secnotice("bundlediskrep", "no metapath for %s", name); + return NULL; + } +} + +CFDataRef BundleDiskRep::metaData(CodeDirectory::SpecialSlot slot) +{ + if (const char *name = CodeDirectory::canonicalSlotName(slot)) + return metaData(name); + else + return NULL; +} + + + // // Load's a CFURL and makes sure that it is a regular file and not a symlink (or fifo, etc.) // @@ -296,14 +315,13 @@ CFDataRef BundleDiskRep::loadRegularFile(CFURLRef url) AutoFileDesc fd(path); - if (!fd.isPlainFile(path)) - recordStrictError(errSecCSRegularFile); + checkPlainFile(fd, path); data = cfLoadFile(fd, fd.fileSize()); if (!data) { - secdebug(__PRETTY_FUNCTION__, "failed to load %s", cfString(url).c_str()); - MacOSError::throwMe(errSecCSInternalError); + secinfo("bundlediskrep", "failed to load %s", cfString(url).c_str()); + MacOSError::throwMe(errSecCSInvalidSymlink); } return data; @@ -313,8 +331,10 @@ CFDataRef BundleDiskRep::loadRegularFile(CFURLRef url) // Load and return a component, by slot number. // Info.plist components come from the bundle, always (we don't look // for Mach-O embedded versions). +// ResourceDirectory always comes from bundle files. // Everything else comes from the embedded blobs of a Mach-O image, or from -// files located in the Contents directory of the bundle. +// files located in the Contents directory of the bundle; but we must be consistent +// (no half-and-half situations). // CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot) { @@ -325,17 +345,75 @@ CFDataRef BundleDiskRep::component(CodeDirectory::SpecialSlot slot) return loadRegularFile(info); else return NULL; - // by default, we take components from the executable image or files - default: - if (CFDataRef data = mExecRep->component(slot)) - return data; - // falling through - // but the following always come from files case cdResourceDirSlot: - if (const char *name = CodeDirectory::canonicalSlotName(slot)) - return metaData(name); - else - return NULL; + mUsedComponents.insert(slot); + return metaData(slot); + // by default, we take components from the executable image or files (but not both) + default: + if (CFRef data = mExecRep->component(slot)) { + componentFromExec(true); + return data.yield(); + } + if (CFRef data = metaData(slot)) { + componentFromExec(false); + mUsedComponents.insert(slot); + return data.yield(); + } + return NULL; + } +} + +BundleDiskRep::RawComponentMap BundleDiskRep::createRawComponents() +{ + RawComponentMap map; + + /* Those are the slots known to BundleDiskReps. + * Unlike e.g. MachOReps, we cannot handle unknown slots, + * as we won't know their slot <-> filename mapping. + */ + int const slots[] = { + cdCodeDirectorySlot, cdSignatureSlot, cdResourceDirSlot, + cdTopDirectorySlot, cdEntitlementSlot, cdEntitlementDERSlot, + cdRepSpecificSlot}; + + for (int slot = 0; slot < (int)(sizeof(slots)/sizeof(slots[0])); ++slot) { + /* Here, we only handle metaData slots, i.e. slots that + * are explicit files in the _CodeSignature directory. + * Main executable slots (if the main executable is a + * EditableDiskRep) are handled when editing the + * main executable's rep explicitly. + * There is also an Info.plist slot, which is not a + * real part of the code signature. + */ + CFRef data = metaData(slot); + + if (data) { + map[slot] = data; + } + } + + for (CodeDirectory::Slot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; ++slot) { + CFRef data = metaData(slot); + + if (data) { + map[slot] = data; + } + } + + return map; +} + +// Check that all components of this BundleDiskRep come from either the main +// executable or the _CodeSignature directory (not mix-and-match). +void BundleDiskRep::componentFromExec(bool fromExec) +{ + if (!mComponentsFromExecValid) { + // first use; set latch + mComponentsFromExecValid = true; + mComponentsFromExec = fromExec; + } else if (mComponentsFromExec != fromExec) { + // subsequent use: check latch + MacOSError::throwMe(errSecCSSignatureFailed); } } @@ -411,6 +489,16 @@ size_t BundleDiskRep::signingLimit() return mExecRep->signingLimit(); } +size_t BundleDiskRep::execSegBase(const Architecture *arch) +{ + return mExecRep->execSegBase(arch); +} + +size_t BundleDiskRep::execSegLimit(const Architecture *arch) +{ + return mExecRep->execSegLimit(arch); +} + string BundleDiskRep::format() { return mFormat; @@ -418,16 +506,18 @@ string BundleDiskRep::format() CFArrayRef BundleDiskRep::modifiedFiles() { - CFMutableArrayRef files = CFArrayCreateMutableCopy(NULL, 0, mExecRep->modifiedFiles()); + CFRef execFiles = mExecRep->modifiedFiles(); + CFRef files = CFArrayCreateMutableCopy(NULL, 0, execFiles); checkModifiedFile(files, cdCodeDirectorySlot); checkModifiedFile(files, cdSignatureSlot); checkModifiedFile(files, cdResourceDirSlot); checkModifiedFile(files, cdTopDirectorySlot); checkModifiedFile(files, cdEntitlementSlot); + checkModifiedFile(files, cdEntitlementDERSlot); checkModifiedFile(files, cdRepSpecificSlot); for (CodeDirectory::Slot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; ++slot) checkModifiedFile(files, slot); - return files; + return files.yield(); } void BundleDiskRep::checkModifiedFile(CFMutableArrayRef files, CodeDirectory::SpecialSlot slot) @@ -451,6 +541,10 @@ void BundleDiskRep::flush() mExecRep->flush(); } +CFDictionaryRef BundleDiskRep::diskRepInformation() +{ + return mExecRep->diskRepInformation(); +} // // Defaults for signing operations @@ -524,37 +618,73 @@ CFDictionaryRef BundleDiskRep::defaultResourceRules(const SigningContext &ctx) "'^.*' = #T" // everything is a resource "'^Info\\.plist$' = {omit=#T,weight=10}" // explicitly exclude this for backward compatibility "}}"); - - // new (V2) executable bundle rules - return cfmake("{" // *** the new (V2) world *** - "rules={" // old (V1; legacy) version - "'^version.plist$' = #T" // include version.plist - "%s = #T" // include Resources - "%s = {optional=#T, weight=1000}" // make localizations optional - "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files - "},rules2={" - "'^.*' = #T" // include everything as a resource, with the following exceptions - "'^[^/]+$' = {nested=#T, weight=10}" // files directly in Contents - "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories - "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code) - "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files - "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told - "'^version\\.plist$' = {weight=20}" // include version.plist as resource - "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource - "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included - "%s = {weight=20}" // Resources override default nested (widgets) - "%s = {optional=#T, weight=1000}" // make localizations optional - "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files - "}}", - - (string("^") + resources).c_str(), - (string("^") + resources + ".*\\.lproj/").c_str(), - (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(), - - (string("^") + resources).c_str(), - (string("^") + resources + ".*\\.lproj/").c_str(), - (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str() - ); + + // new (V2) executable bundle rules + if (!resources.empty()) { + return cfmake("{" // *** the new (V2) world *** + "rules={" // old (V1; legacy) version + "'^version.plist$' = #T" // include version.plist + "%s = #T" // include Resources + "%s = {optional=#T, weight=1000}" // make localizations optional + "%s = {weight=1010}" // ... except for Base.lproj which really isn't optional at all + "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files + "},rules2={" + "'^.*' = #T" // include everything as a resource, with the following exceptions + "'^[^/]+$' = {nested=#T, weight=10}" // files directly in Contents + "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=10}" // dynamic repositories + "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code) + "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files + "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told + "'^version\\.plist$' = {weight=20}" // include version.plist as resource + "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource + "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included + "%s = {weight=20}" // Resources override default nested (widgets) + "%s = {optional=#T, weight=1000}" // make localizations optional + "%s = {weight=1010}" // ... except for Base.lproj which really isn't optional at all + "%s = {omit=#T, weight=1100}" // exclude all locversion.plist files + "}}", + + (string("^") + resources).c_str(), + (string("^") + resources + ".*\\.lproj/").c_str(), + (string("^") + resources + "Base\\.lproj/").c_str(), + (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str(), + + (string("^") + resources).c_str(), + (string("^") + resources + ".*\\.lproj/").c_str(), + (string("^") + resources + "Base\\.lproj/").c_str(), + (string("^") + resources + ".*\\.lproj/locversion.plist$").c_str() + ); + } else { + /* This is a bundle format without a Resources directory, which means we need to omit + * Resources-directory specific rules that would create conflicts. */ + + /* We also declare that flat bundles do not use any nested code rules. + * Embedded, where flat bundles are used, currently does not support them, + * and we have no plans for allowing the replacement of nested code there. + * Should anyone actually intend to use nested code rules in a flat + * bundle, they are free to create their own rules. */ + + return cfmake("{" // *** the new (V2) world *** + "rules={" // old (V1; legacy) version + "'^version.plist$' = #T" // include version.plist + "'^.*' = #T" // include Resources + "'^.*\\.lproj/' = {optional=#T, weight=1000}" // make localizations optional + "'^Base\\.lproj/' = {weight=1010}" // ... except for Base.lproj which really isn't optional at all + "'^.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}" // exclude all locversion.plist files + "},rules2={" + "'^.*' = #T" // include everything as a resource, with the following exceptions + "'.*\\.dSYM($|/)' = {weight=11}" // but allow dSYM directories in code locations (parallel to their code) + "'^(.*/)?\\.DS_Store$' = {omit=#T,weight=2000}" // ignore .DS_Store files + "'^Info\\.plist$' = {omit=#T, weight=20}" // excluded automatically now, but old systems need to be told + "'^version\\.plist$' = {weight=20}" // include version.plist as resource + "'^embedded\\.provisionprofile$' = {weight=20}" // include embedded.provisionprofile as resource + "'^PkgInfo$' = {omit=#T, weight=20}" // traditionally not included + "'^.*\\.lproj/' = {optional=#T, weight=1000}" // make localizations optional + "'^Base\\.lproj/' = {weight=1010}" // ... except for Base.lproj which really isn't optional at all + "'^.*\\.lproj/locversion.plist$' = {omit=#T, weight=1100}" // exclude all locversion.plist files + "}}" + ); + } } @@ -588,6 +718,22 @@ size_t BundleDiskRep::pageSize(const SigningContext &ctx) // void BundleDiskRep::strictValidate(const CodeDirectory* cd, const ToleratedErrors& tolerated, SecCSFlags flags) { + strictValidateStructure(cd, tolerated, flags); + + // now strict-check the main executable (which won't be an app-like object) + mExecRep->strictValidate(cd, tolerated, flags & ~kSecCSRestrictToAppLike); +} + +void BundleDiskRep::strictValidateStructure(const CodeDirectory* cd, const ToleratedErrors& tolerated, SecCSFlags flags) +{ + // scan our metadirectory (_CodeSignature) for unwanted guests + if (!(flags & kSecCSQuickCheck)) + validateMetaDirectory(cd); + + // check accumulated strict errors and report them + if (!(flags & kSecCSRestrictSidebandData)) // tolerate resource forks etc. + mStrictErrors.erase(errSecCSInvalidAssociatedFileData); + std::vector fatalErrors; set_difference(mStrictErrors.begin(), mStrictErrors.end(), tolerated.begin(), tolerated.end(), back_inserter(fatalErrors)); if (!fatalErrors.empty()) @@ -598,9 +744,6 @@ void BundleDiskRep::strictValidate(const CodeDirectory* cd, const ToleratedError if (!mAppLike) if (tolerated.find(kSecCSRestrictToAppLike) == tolerated.end()) MacOSError::throwMe(errSecCSNotAppLike); - - // now strict-check the main executable (which won't be an app-like object) - mExecRep->strictValidate(cd, tolerated, flags & ~kSecCSRestrictToAppLike); } void BundleDiskRep::recordStrictError(OSStatus error) @@ -609,6 +752,45 @@ void BundleDiskRep::recordStrictError(OSStatus error) } +void BundleDiskRep::validateMetaDirectory(const CodeDirectory* cd) +{ + // we know the resource directory will be checked after this call, so we'll give it a pass here + if (cd->slotIsPresent(-cdResourceDirSlot)) + mUsedComponents.insert(cdResourceDirSlot); + + // make a set of allowed (regular) filenames in this directory + std::set allowedFiles; + for (auto it = mUsedComponents.begin(); it != mUsedComponents.end(); ++it) { + switch (*it) { + case cdInfoSlot: + break; // always from Info.plist, not from here + default: + if (const char *name = CodeDirectory::canonicalSlotName(*it)) { + allowedFiles.insert(name); + } + break; + } + } + DirScanner scan(mMetaPath); + if (scan.initialized()) { + while (struct dirent* ent = scan.getNext()) { + if (!scan.isRegularFile(ent)) + MacOSError::throwMe(errSecCSUnsealedAppRoot); // only regular files allowed + if (allowedFiles.find(ent->d_name) == allowedFiles.end()) { // not in expected set of files + if (strcmp(ent->d_name, kSecCS_SIGNATUREFILE) == 0) { + // special case - might be empty and unused (adhoc signature) + AutoFileDesc fd(metaPath(kSecCS_SIGNATUREFILE)); + if (fd.fileSize() == 0) + continue; // that's okay, then + } + // not on list of needed files; it's a freeloading rogue! + recordStrictError(errSecCSUnsealedAppRoot); // funnel through strict set so GKOpaque can override it + } + } + } +} + + // // Check framework root for unsafe symlinks and unsealed content. // @@ -644,6 +826,23 @@ void BundleDiskRep::validateFrameworkRoot(string root) } } + +// +// Check a file descriptor for harmlessness. This is a strict check (only). +// +void BundleDiskRep::checkPlainFile(FileDesc fd, const std::string& path) +{ + if (!fd.isPlainFile(path)) + recordStrictError(errSecCSRegularFile); + checkForks(fd); +} + +void BundleDiskRep::checkForks(FileDesc fd) +{ + if (fd.hasExtendedAttribute(XATTR_RESOURCEFORK_NAME) || fd.hasExtendedAttribute(XATTR_FINDERINFO_NAME)) + recordStrictError(errSecCSInvalidAssociatedFileData); +} + // // Writers @@ -677,8 +876,49 @@ void BundleDiskRep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef if (const char *name = CodeDirectory::canonicalSlotName(slot)) { rep->createMeta(); string path = rep->metaPath(name); + +#if TARGET_OS_OSX + // determine AFSC status if we are told to preserve compression + bool conductCompression = false; + cmpInfo cInfo; + if (this->getPreserveAFSC()) { + struct stat statBuffer; + if (stat(path.c_str(), &statBuffer) == 0) { + if (queryCompressionInfo(path.c_str(), &cInfo) == 0) { + if (cInfo.compressionType != 0 && cInfo.compressedSize > 0) { + conductCompression = true; + } + } + } + } +#endif + AutoFileDesc fd(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); fd.writeAll(CFDataGetBytePtr(data), CFDataGetLength(data)); + fd.close(); + +#if TARGET_OS_OSX + // if the original file was compressed, compress the new file after move + if (conductCompression) { + CFMutableDictionaryRef options = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFStringRef val = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), cInfo.compressionType); + CFDictionarySetValue(options, kAFSCCompressionTypes, val); + CFRelease(val); + + CompressionQueueContext compressionQueue = CreateCompressionQueue(NULL, NULL, NULL, NULL, options); + + if (!CompressFile(compressionQueue, path.c_str(), NULL)) { + secinfo("bundlediskrep", "%p Failed to queue compression of file %s", this, path.c_str()); + MacOSError::throwMe(errSecCSInternalError); + } + + FinishCompressionAndCleanUp(compressionQueue); + compressionQueue = NULL; + CFRelease(options); + } +#endif + + mWrittenFiles.insert(name); } else MacOSError::throwMe(errSecCSBadBundleFormat); } @@ -715,8 +955,30 @@ void BundleDiskRep::Writer::remove(CodeDirectory::SpecialSlot slot) void BundleDiskRep::Writer::flush() { execWriter->flush(); + purgeMetaDirectory(); } +// purge _CodeSignature of all left-over files from any previous signature +void BundleDiskRep::Writer::purgeMetaDirectory() +{ + DirScanner scan(rep->mMetaPath); + if (scan.initialized()) { + while (struct dirent* ent = scan.getNext()) { + if (!scan.isRegularFile(ent)) + MacOSError::throwMe(errSecCSUnsealedAppRoot); // only regular files allowed + if (mWrittenFiles.find(ent->d_name) == mWrittenFiles.end()) { // we didn't write this! + scan.unlink(ent, 0); + } + } + } + +} + +void BundleDiskRep::registerStapledTicket() +{ + string root = cfStringRelease(copyCanonicalPath()); + registerStapledTicketInBundle(root); +} } // end namespace CodeSigning } // end namespace Security