Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
484 lines (423 sloc) 14.7 KB
/*
* 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 "StaticCode.h"
#include "reqmaker.h"
#include <security_utilities/logging.h>
#include <security_utilities/cfmunge.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) {
auto_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()) {
auto_ptr<MachO> macho(mainExecutableImage()->architecture());
if (const version_min_command *version = macho->findMinVersion()) {
uint32_t limit = 0;
switch (macho->flip(version->cmd)) {
case LC_VERSION_MIN_MACOSX:
limit = (10 << 16 | 11 << 8 | 4 << 0);
break;
#if 0 /* need updated libMIS before we can do this switch */
case LC_VERSION_MIN_IPHONEOS:
limit = (9 << 16 | 3 << 8);
break;
case LC_VERSION_MIN_WATCHOS:
limit = (2 << 16 | 2 << 8);
break;
case LC_VERSION_MIN_TVOS:
limit = (9 << 16 | 2 << 8);
break;
default:
break;
#else
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_WATCHOS:
case LC_VERSION_MIN_TVOS:
return;
default:
break;
#endif
}
if (macho->flip(version->version) >= limit) {
// young enough not to need SHA-1 legacy support
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()
{
auto_ptr<MachO> macho(mExecutable->architecture());
return macho->signingExtent();
}
//
// 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::auto_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 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 (!mSigningData) { // fetch and cache
auto_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);
}
}
}
if (mSigningData)
return mSigningData->component(slot);
// not found
return NULL;
}
//
// Extract an embedded Info.plist from the file.
// Returns NULL if none is found.
//
CFDataRef MachORep::infoPlist()
{
CFRef<CFDataRef> info;
try {
auto_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::diskRepInformation()
{
auto_ptr<MachO> macho (mainExecutableImage()->architecture());
CFRef<CFDictionaryRef> info;
if (const version_min_command *version = macho->findMinVersion()) {
info.take(cfmake<CFMutableDictionaryRef>("{%O = %d,%O = %d,%O = %d}",
kSecCodeInfoDiskRepOSPlatform, macho->flip(version->cmd),
kSecCodeInfoDiskRepOSVersionMin, macho->flip(version->version),
kSecCodeInfoDiskRepOSSDKVersion, macho->flip(version->sdk)));
if (macho->flip(version->cmd) == LC_VERSION_MIN_MACOSX &&
macho->flip(version->sdk) < (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)
{
auto_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);
}
} // end namespace CodeSigning
} // end namespace Security
You can’t perform that action at this time.