Demonstrate the basic functionality of the File System
[chromium-blink-merge.git] / chrome / common / mac / cfbundle_blocker.mm
blobd8dd31af680d229e4944599d706ae67ecfb966b2
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/common/mac/cfbundle_blocker.h"
7 #include <CoreFoundation/CoreFoundation.h>
8 #import <Foundation/Foundation.h>
10 #include "base/logging.h"
11 #include "base/mac/mac_util.h"
12 #include "base/mac/scoped_cftyperef.h"
13 #include "base/mac/scoped_nsautorelease_pool.h"
14 #import "base/mac/scoped_nsobject.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "third_party/mach_override/mach_override.h"
18 extern "C" {
20 // _CFBundleLoadExecutableAndReturnError is the internal implementation that
21 // results in a dylib being loaded via dlopen. Both CFBundleLoadExecutable and
22 // CFBundleLoadExecutableAndReturnError are funneled into this routine. Other
23 // CFBundle functions may also call directly into here, perhaps due to
24 // inlining their calls to CFBundleLoadExecutable.
26 // See CF-476.19/CFBundle.c (10.5.8), CF-550.43/CFBundle.c (10.6.8), and
27 // CF-635/Bundle.c (10.7.0) and the disassembly of the shipping object code.
29 // Because this is a private function not declared by
30 // <CoreFoundation/CoreFoundation.h>, provide a declaration here.
31 Boolean _CFBundleLoadExecutableAndReturnError(CFBundleRef bundle,
32                                               Boolean force_global,
33                                               CFErrorRef* error);
35 }  // extern "C"
37 namespace chrome {
38 namespace common {
39 namespace mac {
41 namespace {
43 // Returns an autoreleased array of paths that contain plug-ins that should be
44 // forbidden to load. Each element of the array will be a string containing
45 // an absolute pathname ending in '/'.
46 NSArray* BlockedPaths() {
47   NSMutableArray* blocked_paths;
49   {
50     base::mac::ScopedNSAutoreleasePool autorelease_pool;
52     // ~/Library, /Library, and /Network/Library. Things in /System/Library
53     // aren't blacklisted.
54     NSArray* blocked_prefixes =
55        NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
56                                            NSUserDomainMask |
57                                                NSLocalDomainMask |
58                                                NSNetworkDomainMask,
59                                            YES);
61     // Everything in the suffix list has a trailing slash so as to only block
62     // loading things contained in these directories.
63     NSString* const blocked_suffixes[] = {
64 #if !defined(__LP64__)
65       // Contextual menu manager plug-ins are unavailable to 64-bit processes.
66       // http://developer.apple.com/library/mac/releasenotes/Cocoa/AppKitOlderNotes.html#NSMenu
67       // Contextual menu plug-ins are loaded when a contextual menu is opened,
68       // for example, from within
69       // +[NSMenu popUpContextMenu:withEvent:forView:].
70       @"Contextual Menu Items/",
72       // Input managers are deprecated, would only be loaded under specific
73       // circumstances, and are entirely unavailable to 64-bit processes.
74       // http://developer.apple.com/library/mac/releasenotes/Cocoa/AppKitOlderNotes.html#NSInputManager
75       // Input managers are loaded when the NSInputManager class is
76       // initialized.
77       @"InputManagers/",
78 #endif  // __LP64__
80       // Don't load third-party scripting additions either. Scripting
81       // additions are loaded by AppleScript from within AEProcessAppleEvent
82       // in response to an Apple Event.
83       @"ScriptingAdditions/"
85       // This list is intentionally incomplete. For example, it doesn't block
86       // printer drivers or Internet plug-ins.
87     };
89     NSUInteger blocked_paths_count = [blocked_prefixes count] *
90                                      arraysize(blocked_suffixes);
92     // Not autoreleased here, because the enclosing pool is scoped too
93     // narrowly.
94     blocked_paths =
95         [[NSMutableArray alloc] initWithCapacity:blocked_paths_count];
97     // Build a flat list by adding each suffix to each prefix.
98     for (NSString* blocked_prefix in blocked_prefixes) {
99       for (size_t blocked_suffix_index = 0;
100            blocked_suffix_index < arraysize(blocked_suffixes);
101            ++blocked_suffix_index) {
102         NSString* blocked_suffix = blocked_suffixes[blocked_suffix_index];
103         NSString* blocked_path =
104             [blocked_prefix stringByAppendingPathComponent:blocked_suffix];
106         [blocked_paths addObject:blocked_path];
107       }
108     }
110     DCHECK_EQ([blocked_paths count], blocked_paths_count);
111   }
113   return [blocked_paths autorelease];
116 // Returns true if bundle_path identifies a path within a blocked directory.
117 // Blocked directories are those returned by BlockedPaths().
118 bool IsBundlePathBlocked(NSString* bundle_path) {
119   static NSArray* blocked_paths = [BlockedPaths() retain];
121   for (NSString* blocked_path in blocked_paths) {
122     NSUInteger blocked_path_length = [blocked_path length];
124     // Do a case-insensitive comparison because most users will be on
125     // case-insensitive HFS+ filesystems and it's cheaper than asking the
126     // disk. This is like [bundle_path hasPrefix:blocked_path] but is
127     // case-insensitive.
128     if ([bundle_path length] >= blocked_path_length &&
129         [bundle_path compare:blocked_path
130                      options:NSCaseInsensitiveSearch
131                        range:NSMakeRange(0, blocked_path_length)] ==
132         NSOrderedSame) {
133       // If bundle_path is inside blocked_path (it has blocked_path as a
134       // prefix), refuse to load it.
135       return true;
136     }
137   }
139   // bundle_path is not inside any blocked_path from blocked_paths.
140   return false;
143 typedef Boolean (*_CFBundleLoadExecutableAndReturnError_Type)(CFBundleRef,
144                                                               Boolean,
145                                                               CFErrorRef*);
147 // Call this to execute the original implementation of
148 // _CFBundleLoadExecutableAndReturnError.
149 _CFBundleLoadExecutableAndReturnError_Type
150     g_original_underscore_cfbundle_load_executable_and_return_error;
152 Boolean ChromeCFBundleLoadExecutableAndReturnError(CFBundleRef bundle,
153                                                    Boolean force_global,
154                                                    CFErrorRef* error) {
155   base::mac::ScopedNSAutoreleasePool autorelease_pool;
157   DCHECK(g_original_underscore_cfbundle_load_executable_and_return_error);
159   base::ScopedCFTypeRef<CFURLRef> url_cf(CFBundleCopyBundleURL(bundle));
160   base::scoped_nsobject<NSString> path(base::mac::CFToNSCast(
161       CFURLCopyFileSystemPath(url_cf, kCFURLPOSIXPathStyle)));
163   NSString* bundle_id = base::mac::CFToNSCast(CFBundleGetIdentifier(bundle));
165   NSDictionary* bundle_dictionary =
166       base::mac::CFToNSCast(CFBundleGetInfoDictionary(bundle));
167   NSString* version = [bundle_dictionary objectForKey:
168       base::mac::CFToNSCast(kCFBundleVersionKey)];
169   if (![version isKindOfClass:[NSString class]]) {
170     // Deal with pranksters.
171     version = nil;
172   }
174   if (IsBundlePathBlocked(path) && !IsBundleAllowed(bundle_id, version)) {
175     NSString* bundle_id_print = bundle_id ? bundle_id : @"(nil)";
176     NSString* version_print = version ? version : @"(nil)";
178     // Provide a hint for the user (or module developer) to figure out
179     // that the bundle was blocked.
180     LOG(INFO) << "Blocking attempt to load bundle "
181               << [bundle_id_print UTF8String]
182               << " version "
183               << [version_print UTF8String]
184               << " at "
185               << [path fileSystemRepresentation];
187     if (error) {
188       base::ScopedCFTypeRef<CFStringRef> app_bundle_id(
189           base::SysUTF8ToCFStringRef(base::mac::BaseBundleID()));
191       // 0xb10c10ad = "block load"
192       const CFIndex kBundleLoadBlocked = 0xb10c10ad;
194       NSMutableDictionary* error_dict =
195           [NSMutableDictionary dictionaryWithCapacity:4];
196       if (bundle_id) {
197         [error_dict setObject:bundle_id forKey:@"bundle_id"];
198       }
199       if (version) {
200         [error_dict setObject:version forKey:@"version"];
201       }
202       if (path) {
203         [error_dict setObject:path forKey:@"path"];
204       }
205       NSURL* url_ns = base::mac::CFToNSCast(url_cf);
206       NSString* url_absolute_string = [url_ns absoluteString];
207       if (url_absolute_string) {
208         [error_dict setObject:url_absolute_string forKey:@"url"];
209       }
211       *error = CFErrorCreate(NULL,
212                              app_bundle_id,
213                              kBundleLoadBlocked,
214                              base::mac::NSToCFCast(error_dict));
215     }
217     return FALSE;
218   }
220   // Not blocked. Call through to the original implementation.
221   return g_original_underscore_cfbundle_load_executable_and_return_error(
222       bundle, force_global, error);
225 }  // namespace
227 void EnableCFBundleBlocker() {
228   mach_error_t err = mach_override_ptr(
229       reinterpret_cast<void*>(_CFBundleLoadExecutableAndReturnError),
230       reinterpret_cast<void*>(ChromeCFBundleLoadExecutableAndReturnError),
231       reinterpret_cast<void**>(
232           &g_original_underscore_cfbundle_load_executable_and_return_error));
233   if (err != err_none) {
234     DLOG(WARNING) << "mach_override _CFBundleLoadExecutableAndReturnError: "
235                   << err;
236   }
239 namespace {
241 struct AllowedBundle {
242   // The bundle identifier to permit. These are matched with a case-sensitive
243   // literal comparison. "Children" of the declared bundle ID are permitted:
244   // if bundle_id here is @"org.chromium", it would match both @"org.chromium"
245   // and @"org.chromium.Chromium".
246   NSString* bundle_id;
248   // If bundle_id should only be permitted as of a certain minimum version,
249   // this string defines that version, which will be compared to the bundle's
250   // version with a numeric comparison. If bundle_id may be permitted at any
251   // version, set minimum_version to nil.
252   NSString* minimum_version;
255 }  // namespace
257 bool IsBundleAllowed(NSString* bundle_id, NSString* version) {
258   // The list of bundles that are allowed to load. Before adding an entry to
259   // this list, be sure that it's well-behaved. Specifically, anything that
260   // uses mach_override
261   // (https://github.com/rentzsch/mach_star/tree/master/mach_override) must
262   // use version 51ae3d199463fa84548f466d649f0821d579fdaf (July 22, 2011) or
263   // newer. Products added to the list must not cause crashes. Entries should
264   // include the name of the product, URL, and the name and e-mail address of
265   // someone responsible for the product's engineering. To add items to this
266   // list, file a bug at http://crbug.com/new using the "Defect on Mac OS"
267   // template, and provide the bundle ID (or IDs) and minimum CFBundleVersion
268   // that's safe for Chrome to load, along with the necessary product and
269   // contact information. Whitelisted bundles in this list may be removed if
270   // they are found to cause instability or otherwise behave badly. With
271   // proper contact information, Chrome developers may try to contact
272   // maintainers to resolve any problems.
273   const AllowedBundle kAllowedBundles[] = {
274     // Google Authenticator BT
275     // Dave MacLachlan <dmaclach@google.com>
276     { @"com.google.osax.Google_Authenticator_BT", nil },
278     // Default Folder X, http://www.stclairsoft.com/DefaultFolderX/
279     // Jon Gotow <gotow@stclairsoft.com>
280     { @"com.stclairsoft.DefaultFolderX", @"4.4.3" },
282     // MySpeed, http://www.enounce.com/myspeed
283     // Edward Bianchi <ejbianchi@enounce.com>
284     { @"com.enounce.MySpeed.osax", @"1201" },
286     // SIMBL (fork), https://github.com/albertz/simbl
287     // Albert Zeyer <albzey@googlemail.com>
288     { @"net.culater.SIMBL", nil },
290     // Smart Scroll, http://marcmoini.com/sx_en.html
291     // Marc Moini <marc@a9ff.com>
292     { @"com.marcmoini.SmartScroll", @"3.9" },
293   };
295   for (size_t index = 0; index < arraysize(kAllowedBundles); ++index) {
296     const AllowedBundle& allowed_bundle = kAllowedBundles[index];
297     NSString* allowed_bundle_id = allowed_bundle.bundle_id;
298     NSUInteger allowed_bundle_id_length = [allowed_bundle_id length];
300     // Permit bundle identifiers that are exactly equal to the allowed
301     // identifier, as well as "children" of the allowed identifier.
302     if ([bundle_id isEqualToString:allowed_bundle_id] ||
303         ([bundle_id length] > allowed_bundle_id_length &&
304          [bundle_id characterAtIndex:allowed_bundle_id_length] == '.' &&
305          [bundle_id hasPrefix:allowed_bundle_id])) {
306       NSString* minimum_version = allowed_bundle.minimum_version;
307       if (!minimum_version) {
308         // If the rule didn't declare any version requirement, the bundle is
309         // allowed to load.
310         return true;
311       }
313       if (!version) {
314         // If there wasn't any version but one was required, the bundle isn't
315         // allowed to load.
316         return false;
317       }
319       // A numeric search is appropriate for comparing version numbers.
320       NSComparisonResult result = [version compare:minimum_version
321                                            options:NSNumericSearch];
322       return result != NSOrderedAscending;
323     }
324   }
326   // Nothing matched.
327   return false;
330 }  // namespace mac
331 }  // namespace common
332 }  // namespace chrome