Chromium Code Reviews| Index: chrome/browser/safe_browsing/signature_evaluator_mac.mm |
| diff --git a/chrome/browser/safe_browsing/signature_evaluator_mac.mm b/chrome/browser/safe_browsing/signature_evaluator_mac.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..da67898455f0de9366ba4bf0a10eb92cfb85e854 |
| --- /dev/null |
| +++ b/chrome/browser/safe_browsing/signature_evaluator_mac.mm |
| @@ -0,0 +1,239 @@ |
| +// Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/safe_browsing/signature_evaluator_mac.h" |
| + |
| +#include <CoreFoundation/CoreFoundation.h> |
| +#include <Foundation/Foundation.h> |
| +#include <Security/Security.h> |
| +#include <sys/xattr.h> |
| + |
| +#include "base/mac/foundation_util.h" |
| +#include "base/mac/mac_util.h" |
| +#include "base/mac/scoped_cftyperef.h" |
| +#include "base/mac/scoped_nsobject.h" |
| +#include "base/strings/sys_string_conversions.h" |
| +#include "chrome/common/safe_browsing/binary_feature_extractor.h" |
| +#include "chrome/common/safe_browsing/csd.pb.h" |
| +#include "chrome/common/safe_browsing/mach_o_image_reader_mac.h" |
| + |
| +namespace safe_browsing { |
| + |
| +namespace { |
| + |
| +// OS X code signing data can be stored in extended attributes as well. This is |
| +// a list of the extended attributes slots currently used in Security.framework, |
| +// from codesign.h (see the kSecCS_* constants). |
| +const char* const xattrs[] = { |
| + "com.apple.cs.CodeDirectory", |
| + "com.apple.cs.CodeSignature", |
| + "com.apple.cs.CodeRequirements", |
| + "com.apple.cs.CodeResources", |
| + "com.apple.cs.CodeApplication", |
| + "com.apple.cs.CodeEntitlements", |
| +}; |
| + |
| +// Convenience function to get the appropriate path from a variety of NSObject |
| +// types. For resources, code signing seems to give back an NSURL in which |
| +// the path is relative to the bundle root. So in this case, we take the |
| +// relative component, otherwise we take the entire path. |
| +bool GetPathFromNSObject(id obj, std::string* output) { |
| + if (NSString* str = base::mac::ObjCCast<NSString>(obj)) { |
| + output->assign([str fileSystemRepresentation]); |
| + return true; |
| + } else if (NSURL* url = base::mac::ObjCCast<NSURL>(obj)) { |
|
grt (UTC plus 2)
2015/11/02 20:20:15
remove "else" here and below since each if() body
|
| + output->assign([[url path] fileSystemRepresentation]); |
| + return true; |
| + } else if (NSBundle* bundle = base::mac::ObjCCast<NSBundle>(obj)) { |
| + //TODO(kerrnel): remove this since we don't do nested code? |
| + output->assign([[bundle bundlePath] fileSystemRepresentation]); |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +// Extract the signature information from the mach-o or extended attributes. |
| +void ExtractSignatureInfo(const base::FilePath& path, |
| + ClientDownloadRequest_ImageHeaders* image_headers, |
| + ClientDownloadRequest_SignatureInfo* signature) { |
| + scoped_refptr<BinaryFeatureExtractor> bfe = new BinaryFeatureExtractor(); |
| + |
| + // TODO(kerrnel): if Chrome ever opts into the OS X "kill" semantics, this |
| + // call has to change. `ExtractImageFeatures` maps the file, which will |
| + // cause Chrome to be killed before it can report on the invalid file. |
| + // This call will need to read(2) the binary into a buffer. |
| + if (!bfe->ExtractImageFeatures( |
| + path, |
| + BinaryFeatureExtractor::kDefaultOptions, |
| + image_headers, |
| + signature->mutable_signed_data())) { |
| + // If this is not a mach-o file, search inside the extended attributes. |
| + for (const char* attr : xattrs) { |
| + ssize_t size = getxattr(path.value().c_str(), attr, nullptr, 0, 0, 0); |
| + if (size >= 0) { |
| + std::vector<uint8_t> xattr_data(size); |
| + ssize_t post_size = getxattr(path.value().c_str(), attr, |
| + &xattr_data[0], xattr_data.size(), 0, 0); |
| + if (post_size >= 0) { |
| + xattr_data.resize(post_size); |
| + ClientDownloadRequest_ExtendedAttr* xattr_msg = |
| + signature->add_xattr(); |
|
Robert Sesek
2015/11/02 19:42:12
nit: +2
grt (UTC plus 2)
2015/11/02 20:20:15
nit: indent
|
| + xattr_msg->set_key(attr); |
| + xattr_msg->set_value(xattr_data.data(), xattr_data.size()); |
| + } |
| + } |
| + } |
| + } |
| +} |
| + |
| +// Process the NSError information about any files that were altered. |
| +void ReportAlteredFiles( |
| + id detail, |
| + const base::FilePath& bundle_path, |
| + ClientIncidentReport_IncidentData_BinaryIntegrityIncident* incident) { |
| + if (NSArray* arr = base::mac::ObjCCast<NSArray>(detail)) { |
| + for (id obj in arr) |
| + ReportAlteredFiles(obj, bundle_path, incident); |
| + } else { |
| + std::string path_str; |
| + if (!GetPathFromNSObject(detail, &path_str)) |
| + return; |
| + std::string relative_path; |
| + base::FilePath path(path_str); |
| + // If the relative path calculation fails, at least take the basename. |
| + if (!MacSignatureEvaluator::GetRelativePathComponent(bundle_path, |
| + path, |
| + &relative_path)) { |
| + relative_path = path.BaseName().value(); |
| + } |
| + |
| + ClientIncidentReport_IncidentData_BinaryIntegrityIncident_ContainedFile* |
| + contained_file = incident->add_contained_file(); |
|
Robert Sesek
2015/11/02 19:42:12
nit: +2
|
| + contained_file->set_relative_path(relative_path); |
| + ExtractSignatureInfo(base::FilePath(path_str), |
| + contained_file->mutable_image_headers(), |
| + contained_file->mutable_signature()); |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +MacSignatureEvaluator::MacSignatureEvaluator( |
| + const base::FilePath& signed_object_path) |
| + : path_(signed_object_path), |
| + requirement_str_(), |
| + has_requirement_(false), |
| + code_(nullptr), |
| + requirement_(nullptr) {} |
| + |
| +MacSignatureEvaluator::MacSignatureEvaluator( |
| + const base::FilePath& signed_object_path, |
| + const std::string& requirement) |
| + : path_(signed_object_path), |
| + requirement_str_(requirement), |
| + has_requirement_(true), |
| + code_(nullptr), |
| + requirement_(nullptr) {} |
| + |
| +MacSignatureEvaluator::~MacSignatureEvaluator() {} |
| + |
| +bool MacSignatureEvaluator::GetRelativePathComponent( |
| + const base::FilePath& parent, |
| + const base::FilePath& child, |
| + std::string* out) { |
| + if (!parent.IsParent(child)) |
| + return false; |
| + |
| + std::vector<base::FilePath::StringType> parent_components; |
| + std::vector<base::FilePath::StringType> child_components; |
| + parent.GetComponents(&parent_components); |
| + child.GetComponents(&child_components); |
| + |
| + size_t i = 0; |
| + while (i < parent_components.size() && |
| + child_components[i] == parent_components[i]) { |
| + ++i; |
| + } |
| + |
| + while (i < child_components.size()) { |
| + out->append(child_components[i]); |
| + if (++i < child_components.size()) |
| + out->append("/"); |
| + } |
| + return true; |
| +} |
| + |
| +bool MacSignatureEvaluator::Initialize() { |
| + base::scoped_nsobject<NSURL> code_url([[NSURL alloc] |
| + initFileURLWithPath:base::SysUTF8ToNSString(path_.value())]); |
| + if (!code_url) |
| + return false; |
| + |
| + if (SecStaticCodeCreateWithPath(base::mac::NSToCFCast(code_url.get()), |
| + kSecCSDefaultFlags, |
| + code_.InitializeInto()) != errSecSuccess) { |
| + return false; |
| + } |
| + |
| + if (has_requirement_) { |
| + if (SecRequirementCreateWithString( |
| + base::mac::NSToCFCast(base::SysUTF8ToNSString(requirement_str_)), |
| + kSecCSDefaultFlags, requirement_.InitializeInto()) != |
| + errSecSuccess) { |
| + return false; |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +bool MacSignatureEvaluator::PerformEvaluation( |
| + ClientIncidentReport_IncidentData_BinaryIntegrityIncident* incident) { |
| + DCHECK(incident->contained_file_size() == 0); |
| + base::ScopedCFTypeRef<CFErrorRef> errors; |
| + OSStatus err = SecStaticCodeCheckValidityWithErrors( |
| + code_, kSecCSCheckAllArchitectures, requirement_, |
| + errors.InitializeInto()); |
| + if (err == errSecSuccess) |
| + return true; |
| + // Add the signature of the main binary to the incident report. |
| + incident->set_file_basename(path_.BaseName().value()); |
| + incident->set_sec_error(err); |
| + // We heuristically detect if we are in a bundle or not by checking if |
| + // the main executable is different from the path_. |
| + base::ScopedCFTypeRef<CFDictionaryRef> info_dict; |
| + if (SecCodeCopySigningInformation(code_, |
| + kSecCSDefaultFlags, |
| + info_dict.InitializeInto()) == |
| + errSecSuccess) { |
| + CFURLRef exec_url = base::mac::CFCastStrict<CFURLRef>( |
| + CFDictionaryGetValue(info_dict, kSecCodeInfoMainExecutable)); |
| + if (!exec_url) |
| + return false; |
| + |
| + base::FilePath exec_path( |
| + [[base::mac::CFToNSCast(exec_url) path] fileSystemRepresentation]); |
| + if (exec_path != path_) { |
| + ReportAlteredFiles(base::mac::CFToNSCast(exec_url), path_, incident); |
| + } else { |
| + // We may be examing a flat executable file, so extract any signature. |
| + ExtractSignatureInfo(path_, |
| + incident->mutable_image_headers(), |
| + incident->mutable_signature()); |
| + } |
| + } |
| + |
| + if (errors) { |
| + NSDictionary* info = [base::mac::CFToNSCast(errors.get()) userInfo]; |
| + static const CFStringRef keys[] = { |
| + kSecCFErrorResourceAltered, kSecCFErrorResourceMissing, |
| + }; |
| + for (CFStringRef key : keys) { |
| + if (id detail = [info objectForKey:base::mac::CFToNSCast(key)]) |
| + ReportAlteredFiles(detail, path_, incident); |
| + } |
| + } |
| + return false; |
| +} |
| + |
| +} // namespace safe_browsing |