1/* 2 * Copyright (c) 2006 Apple Computer, 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 24// 25// machorep - DiskRep mix-in for handling Mach-O main executables 26// 27#include "machorep.h" 28#include "StaticCode.h" 29#include "reqmaker.h" 30 31 32namespace Security { 33namespace CodeSigning { 34 35using namespace UnixPlusPlus; 36 37 38// 39// Object management. 40// We open the main executable lazily, so nothing much happens on construction. 41// If the context specifies a file offset, we directly pick that Mach-O binary (only). 42// if it specifies an architecture, we try to pick that. Otherwise, we deliver the whole 43// Universal object (which will usually deliver the "native" architecture later). 44// 45MachORep::MachORep(const char *path, const Context *ctx) 46 : SingleDiskRep(path), mSigningData(NULL) 47{ 48 if (ctx) 49 if (ctx->offset) 50 mExecutable = new Universal(fd(), (size_t)ctx->offset, ctx->size); 51 else if (ctx->arch) { 52 auto_ptr<Universal> full(new Universal(fd())); 53 mExecutable = new Universal(fd(), full->archOffset(ctx->arch), full->archLength(ctx->arch)); 54 } else 55 mExecutable = new Universal(fd()); 56 else 57 mExecutable = new Universal(fd()); 58 59 assert(mExecutable); 60 CODESIGN_DISKREP_CREATE_MACHO(this, (char*)path, (void*)ctx); 61} 62 63MachORep::~MachORep() 64{ 65 delete mExecutable; 66 ::free(mSigningData); 67} 68 69 70// 71// Sniffer function for "plausible Mach-O binary" 72// 73bool MachORep::candidate(FileDesc &fd) 74{ 75 switch (Universal::typeOf(fd)) { 76 case MH_EXECUTE: 77 case MH_DYLIB: 78 case MH_DYLINKER: 79 case MH_BUNDLE: 80 case MH_KEXT_BUNDLE: 81 case MH_PRELOAD: 82 return true; // dynamic image; supported 83 case MH_OBJECT: 84 return false; // maybe later... 85 default: 86 return false; // not Mach-O (or too exotic) 87 } 88} 89 90 91 92// 93// Nowadays, the main executable object is created upon construction. 94// 95Universal *MachORep::mainExecutableImage() 96{ 97 return mExecutable; 98} 99 100 101// 102// Signing base is the start of the Mach-O architecture we're using 103// 104size_t MachORep::signingBase() 105{ 106 return mainExecutableImage()->archOffset(); 107} 108 109 110// 111// We choose the binary identifier for a Mach-O binary as follows: 112// - If the Mach-O headers have a UUID command, use the UUID. 113// - Otherwise, use the SHA-1 hash of the (entire) load commands. 114// 115CFDataRef MachORep::identification() 116{ 117 std::auto_ptr<MachO> macho(mainExecutableImage()->architecture()); 118 return identificationFor(macho.get()); 119} 120 121CFDataRef MachORep::identificationFor(MachO *macho) 122{ 123 // if there is a LC_UUID load command, use the UUID contained therein 124 if (const load_command *cmd = macho->findCommand(LC_UUID)) { 125 const uuid_command *uuidc = reinterpret_cast<const uuid_command *>(cmd); 126 char result[4 + sizeof(uuidc->uuid)]; 127 memcpy(result, "UUID", 4); 128 memcpy(result+4, uuidc->uuid, sizeof(uuidc->uuid)); 129 return makeCFData(result, sizeof(result)); 130 } 131 132 // otherwise, use the SHA-1 hash of the entire load command area 133 SHA1 hash; 134 hash(&macho->header(), sizeof(mach_header)); 135 hash(macho->loadCommands(), macho->commandLength()); 136 SHA1::Digest digest; 137 hash.finish(digest); 138 return makeCFData(digest, sizeof(digest)); 139} 140 141 142// 143// Retrieve a component from the executable. 144// This reads the entire signing SuperBlob when first called for an executable, 145// and then caches it for further use. 146// Note that we could read individual components directly off disk and only cache 147// the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected 148// to cache the pieces anyway. 149// 150CFDataRef MachORep::component(CodeDirectory::SpecialSlot slot) 151{ 152 switch (slot) { 153 case cdInfoSlot: 154 return infoPlist(); 155 default: 156 return embeddedComponent(slot); 157 } 158} 159 160 161// Retrieve a component from the embedded signature SuperBlob (if present). 162// This reads the entire signing SuperBlob when first called for an executable, 163// and then caches it for further use. 164// Note that we could read individual components directly off disk and only cache 165// the SuperBlob Index directory. Our caller (usually SecStaticCode) is expected 166// to cache the pieces anyway. But it's not clear that the resulting multiple I/O 167// calls wouldn't be slower in the end. 168// 169CFDataRef MachORep::embeddedComponent(CodeDirectory::SpecialSlot slot) 170{ 171 if (!mSigningData) { // fetch and cache 172 auto_ptr<MachO> macho(mainExecutableImage()->architecture()); 173 if (macho.get()) 174 if (const linkedit_data_command *cs = macho->findCodeSignature()) { 175 size_t offset = macho->flip(cs->dataoff); 176 size_t length = macho->flip(cs->datasize); 177 if ((mSigningData = EmbeddedSignatureBlob::readBlob(macho->fd(), macho->offset() + offset, length))) { 178 secdebug("machorep", "%zd signing bytes in %d blob(s) from %s(%s)", 179 mSigningData->length(), mSigningData->count(), 180 mainExecutablePath().c_str(), macho->architecture().name()); 181 } else { 182 secdebug("machorep", "failed to read signing bytes from %s(%s)", 183 mainExecutablePath().c_str(), macho->architecture().name()); 184 MacOSError::throwMe(errSecCSSignatureInvalid); 185 } 186 } 187 } 188 if (mSigningData) 189 return mSigningData->component(slot); 190 191 // not found 192 return NULL; 193} 194 195 196// 197// Extract an embedded Info.plist from the file. 198// Returns NULL if none is found. 199// 200CFDataRef MachORep::infoPlist() 201{ 202 CFRef<CFDataRef> info; 203 try { 204 auto_ptr<MachO> macho(mainExecutableImage()->architecture()); 205 if (const section *sect = macho->findSection("__TEXT", "__info_plist")) { 206 if (macho->is64()) { 207 const section_64 *sect64 = reinterpret_cast<const section_64 *>(sect); 208 info.take(macho->dataAt(macho->flip(sect64->offset), (size_t)macho->flip(sect64->size))); 209 } else { 210 info.take(macho->dataAt(macho->flip(sect->offset), macho->flip(sect->size))); 211 } 212 } 213 } catch (...) { 214 secdebug("machorep", "exception reading embedded Info.plist"); 215 } 216 return info.yield(); 217} 218 219 220// 221// Provide a (vaguely) human readable characterization of this code 222// 223string MachORep::format() 224{ 225 if (Universal *fat = mainExecutableImage()) { 226 Universal::Architectures archs; 227 fat->architectures(archs); 228 if (fat->isUniversal()) { 229 string s = "Mach-O universal ("; 230 for (Universal::Architectures::const_iterator it = archs.begin(); 231 it != archs.end(); ++it) { 232 if (it != archs.begin()) 233 s += " "; 234 s += it->displayName(); 235 } 236 return s + ")"; 237 } else { 238 assert(archs.size() == 1); 239 return string("Mach-O thin (") + archs.begin()->displayName() + ")"; 240 } 241 } else 242 return "Mach-O (unrecognized format)"; 243} 244 245 246// 247// Flush cached data 248// 249void MachORep::flush() 250{ 251 size_t offset = mExecutable->offset(); 252 size_t length = mExecutable->length(); 253 delete mExecutable; 254 mExecutable = NULL; 255 ::free(mSigningData); 256 mSigningData = NULL; 257 SingleDiskRep::flush(); 258 mExecutable = new Universal(fd(), offset, length); 259} 260 261 262// 263// Return a recommended unique identifier. 264// If our file has an embedded Info.plist, use the CFBundleIdentifier from that. 265// Otherwise, use the default. 266// 267string MachORep::recommendedIdentifier(const SigningContext &ctx) 268{ 269 if (CFDataRef info = infoPlist()) { 270 if (CFRef<CFDictionaryRef> dict = makeCFDictionaryFrom(info)) { 271 CFStringRef code = CFStringRef(CFDictionaryGetValue(dict, kCFBundleIdentifierKey)); 272 if (code && CFGetTypeID(code) != CFStringGetTypeID()) 273 MacOSError::throwMe(errSecCSBadDictionaryFormat); 274 if (code) 275 return cfString(code); 276 } else 277 MacOSError::throwMe(errSecCSBadDictionaryFormat); 278 } 279 280 // ah well. Use the default 281 return SingleDiskRep::recommendedIdentifier(ctx); 282} 283 284 285// 286// The default suggested requirements for Mach-O binaries are as follows: 287// Library requirement: Composed from dynamic load commands. 288// 289const Requirements *MachORep::defaultRequirements(const Architecture *arch, const SigningContext &ctx) 290{ 291 assert(arch); // enforced by signing infrastructure 292 Requirements::Maker maker; 293 294 // add library requirements from DYLIB commands (if any) 295 if (Requirement *libreq = libraryRequirements(arch, ctx)) 296 maker.add(kSecLibraryRequirementType, libreq); // takes ownership 297 298 // that's all 299 return maker.make(); 300} 301 302Requirement *MachORep::libraryRequirements(const Architecture *arch, const SigningContext &ctx) 303{ 304 auto_ptr<MachO> macho(mainExecutableImage()->architecture(*arch)); 305 Requirement::Maker maker; 306 Requirement::Maker::Chain chain(maker, opOr); 307 308 if (macho.get()) 309 if (const linkedit_data_command *ldep = macho->findLibraryDependencies()) { 310 size_t offset = macho->flip(ldep->dataoff); 311 size_t length = macho->flip(ldep->datasize); 312 if (LibraryDependencyBlob *deplist = LibraryDependencyBlob::readBlob(macho->fd(), macho->offset() + offset, length)) { 313 try { 314 secdebug("machorep", "%zd library dependency bytes in %d blob(s) from %s(%s)", 315 deplist->length(), deplist->count(), 316 mainExecutablePath().c_str(), macho->architecture().name()); 317 unsigned count = deplist->count(); 318 // we could walk through DYLIB load commands in parallel. We just don't need anything from them so far 319 for (unsigned n = 0; n < count; n++) { 320 const Requirement *req = NULL; 321 if (const BlobCore *dep = deplist->blob(n)) { 322 if ((req = Requirement::specific(dep))) { 323 // binary code requirement; good to go 324 } else if (const BlobWrapper *wrap = BlobWrapper::specific(dep)) { 325 // blob-wrapped text form - convert to binary requirement 326 std::string reqString = std::string((const char *)wrap->data(), wrap->length()); 327 CFRef<SecRequirementRef> areq; 328 MacOSError::check(SecRequirementCreateWithString(CFTempString(reqString), kSecCSDefaultFlags, &areq.aref())); 329 CFRef<CFDataRef> reqData; 330 MacOSError::check(SecRequirementCopyData(areq, kSecCSDefaultFlags, &reqData.aref())); 331 req = Requirement::specific((const BlobCore *)CFDataGetBytePtr(reqData)); 332 } else { 333 secdebug("machorep", "unexpected blob type 0x%x in slot %d of binary dependencies", dep->magic(), n); 334 continue; 335 } 336 chain.add(); 337 maker.copy(req); 338 } else 339 secdebug("machorep", "missing DR info for library index %d", n); 340 } 341 ::free(deplist); 342 } catch (...) { 343 ::free(deplist); 344 throw; 345 } 346 } 347 } 348 if (chain.empty()) 349 return NULL; 350 else 351 return maker.make(); 352} 353 354 355// 356// Default to system page size for segmented (paged) signatures 357// 358size_t MachORep::pageSize(const SigningContext &) 359{ 360 return segmentedPageSize; 361} 362 363 364// 365// Strict validation 366// 367void MachORep::strictValidate(const ToleratedErrors& tolerated) 368{ 369 if (mExecutable->isSuspicious() && tolerated.find(errSecCSBadMainExecutable) == tolerated.end()) 370 MacOSError::throwMe(errSecCSBadMainExecutable); 371} 372 373 374// 375// FileDiskRep::Writers 376// 377DiskRep::Writer *MachORep::writer() 378{ 379 return new Writer(this); 380} 381 382 383// 384// Write a component. 385// MachORep::Writers don't write to components directly; the signing code uses special 386// knowledge of the Mach-O format to build embedded signatures and blasts them directly 387// to disk. Thus this implementation will never be called (and, if called, will simply fail). 388// 389void MachORep::Writer::component(CodeDirectory::SpecialSlot slot, CFDataRef data) 390{ 391 assert(false); 392 MacOSError::throwMe(errSecCSInternalError); 393} 394 395 396} // end namespace CodeSigning 397} // end namespace Security 398