• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/proxy_resolution/proxy_resolver_mac.h"
6 
7 #include <CoreFoundation/CoreFoundation.h>
8 
9 #include <memory>
10 
11 #include "base/apple/foundation_util.h"
12 #include "base/apple/scoped_cftyperef.h"
13 #include "base/check.h"
14 #include "base/lazy_instance.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/synchronization/lock.h"
18 #include "base/threading/thread_checker.h"
19 #include "build/build_config.h"
20 #include "net/base/net_errors.h"
21 #include "net/base/proxy_server.h"
22 #include "net/proxy_resolution/proxy_info.h"
23 #include "net/proxy_resolution/proxy_list.h"
24 #include "net/proxy_resolution/proxy_resolver.h"
25 #include "net/proxy_resolution/proxy_server_util_mac.h"
26 #include "url/gurl.h"
27 
28 #if BUILDFLAG(IS_IOS)
29 #include <CFNetwork/CFProxySupport.h>
30 #else
31 #include <CoreServices/CoreServices.h>
32 #endif
33 
34 #if LEAK_SANITIZER
35 #include <sanitizer/lsan_interface.h>
36 #endif
37 
38 namespace net {
39 
40 class NetworkAnonymizationKey;
41 
42 namespace {
43 
44 // A lock shared by all ProxyResolverMac instances. It is used to synchronize
45 // the events of multiple CFNetworkExecuteProxyAutoConfigurationURL run loop
46 // sources. These events are:
47 // 1. Adding the source to the run loop.
48 // 2. Handling the source result.
49 // 3. Removing the source from the run loop.
50 static base::LazyInstance<base::Lock>::Leaky g_cfnetwork_pac_runloop_lock =
51     LAZY_INSTANCE_INITIALIZER;
52 
53 // Forward declaration of the callback function used by the
54 // SynchronizedRunLoopObserver class.
55 void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,
56                                  CFRunLoopActivity activity,
57                                  void* info);
58 
59 // Utility function to map a CFProxyType to a ProxyServer::Scheme.
60 // If the type is unknown, returns ProxyServer::SCHEME_INVALID.
GetProxyServerScheme(CFStringRef proxy_type)61 ProxyServer::Scheme GetProxyServerScheme(CFStringRef proxy_type) {
62   if (CFEqual(proxy_type, kCFProxyTypeNone))
63     return ProxyServer::SCHEME_DIRECT;
64   if (CFEqual(proxy_type, kCFProxyTypeHTTP))
65     return ProxyServer::SCHEME_HTTP;
66   if (CFEqual(proxy_type, kCFProxyTypeHTTPS)) {
67     // The "HTTPS" on the Mac side here means "proxy applies to https://" URLs;
68     // the proxy itself is still expected to be an HTTP proxy.
69     return ProxyServer::SCHEME_HTTP;
70   }
71   if (CFEqual(proxy_type, kCFProxyTypeSOCKS)) {
72     // We can't tell whether this was v4 or v5. We will assume it is
73     // v5 since that is the only version OS X supports.
74     return ProxyServer::SCHEME_SOCKS5;
75   }
76   return ProxyServer::SCHEME_INVALID;
77 }
78 
79 // Callback for CFNetworkExecuteProxyAutoConfigurationURL. |client| is a pointer
80 // to a CFTypeRef.  This stashes either |error| or |proxies| in that location.
ResultCallback(void * client,CFArrayRef proxies,CFErrorRef error)81 void ResultCallback(void* client, CFArrayRef proxies, CFErrorRef error) {
82   DCHECK((proxies != nullptr) == (error == nullptr));
83 
84   CFTypeRef* result_ptr = reinterpret_cast<CFTypeRef*>(client);
85   DCHECK(result_ptr != nullptr);
86   DCHECK(*result_ptr == nullptr);
87 
88   if (error != nullptr) {
89     *result_ptr = CFRetain(error);
90   } else {
91     *result_ptr = CFRetain(proxies);
92   }
93   CFRunLoopStop(CFRunLoopGetCurrent());
94 }
95 
96 #pragma mark - SynchronizedRunLoopObserver
97 // A run loop observer that guarantees that no two run loop sources protected
98 // by the same lock will be fired concurrently in different threads.
99 // The observer does not prevent the parallel execution of the sources but only
100 // synchronizes the run loop events associated with the sources. In the context
101 // of proxy resolver, the observer is used to synchronize the execution of the
102 // callbacks function that handles the result of
103 // CFNetworkExecuteProxyAutoConfigurationURL execution.
104 class SynchronizedRunLoopObserver final {
105  public:
106   // Creates the instance of an observer that will synchronize the sources
107   // using a given |lock|.
108   SynchronizedRunLoopObserver(base::Lock& lock);
109 
110   SynchronizedRunLoopObserver(const SynchronizedRunLoopObserver&) = delete;
111   SynchronizedRunLoopObserver& operator=(const SynchronizedRunLoopObserver&) =
112       delete;
113 
114   // Destructor.
115   ~SynchronizedRunLoopObserver();
116   // Adds the observer to the current run loop for a given run loop mode.
117   // This method should always be paired with |RemoveFromCurrentRunLoop|.
118   void AddToCurrentRunLoop(const CFStringRef mode);
119   // Removes the observer from the current run loop for a given run loop mode.
120   // This method should always be paired with |AddToCurrentRunLoop|.
121   void RemoveFromCurrentRunLoop(const CFStringRef mode);
122   // Callback function that is called when an observable run loop event occurs.
123   void RunLoopObserverCallBack(CFRunLoopObserverRef observer,
124                                CFRunLoopActivity activity);
125 
126  private:
127   // Lock to use to synchronize the run loop sources.
128   base::Lock& lock_;
129   // Indicates whether the current observer holds the lock. It is used to
130   // avoid double locking and releasing.
131   bool lock_acquired_ = false;
132   // The underlying CFRunLoopObserverRef structure wrapped by this instance.
133   base::apple::ScopedCFTypeRef<CFRunLoopObserverRef> observer_;
134   // Validates that all methods of this class are executed on the same thread.
135   base::ThreadChecker thread_checker_;
136 };
137 
SynchronizedRunLoopObserver(base::Lock & lock)138 SynchronizedRunLoopObserver::SynchronizedRunLoopObserver(base::Lock& lock)
139     : lock_(lock) {
140   CFRunLoopObserverContext observer_context = {0, this, nullptr, nullptr,
141                                                nullptr};
142   observer_.reset(CFRunLoopObserverCreate(
143       kCFAllocatorDefault,
144       kCFRunLoopBeforeSources | kCFRunLoopBeforeWaiting | kCFRunLoopExit, true,
145       0, RunLoopObserverCallBackFunc, &observer_context));
146 }
147 
~SynchronizedRunLoopObserver()148 SynchronizedRunLoopObserver::~SynchronizedRunLoopObserver() {
149   DCHECK(thread_checker_.CalledOnValidThread());
150   DCHECK(!lock_acquired_);
151 }
152 
AddToCurrentRunLoop(const CFStringRef mode)153 void SynchronizedRunLoopObserver::AddToCurrentRunLoop(const CFStringRef mode) {
154   DCHECK(thread_checker_.CalledOnValidThread());
155   CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer_.get(), mode);
156 }
157 
RemoveFromCurrentRunLoop(const CFStringRef mode)158 void SynchronizedRunLoopObserver::RemoveFromCurrentRunLoop(
159     const CFStringRef mode) {
160   DCHECK(thread_checker_.CalledOnValidThread());
161   CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer_.get(), mode);
162 }
163 
RunLoopObserverCallBack(CFRunLoopObserverRef observer,CFRunLoopActivity activity)164 void SynchronizedRunLoopObserver::RunLoopObserverCallBack(
165     CFRunLoopObserverRef observer,
166     CFRunLoopActivity activity) NO_THREAD_SAFETY_ANALYSIS {
167   DCHECK(thread_checker_.CalledOnValidThread());
168   // Acquire the lock when a source has been signaled and going to be fired.
169   // In the context of the proxy resolver that happens when the proxy for a
170   // given URL has been resolved and the callback function that handles the
171   // result is going to be fired.
172   // Release the lock when all source events have been handled.
173   //
174   // NO_THREAD_SAFETY_ANALYSIS: Runtime dependent locking.
175   switch (activity) {
176     case kCFRunLoopBeforeSources:
177       if (!lock_acquired_) {
178         lock_.Acquire();
179         lock_acquired_ = true;
180       }
181       break;
182     case kCFRunLoopBeforeWaiting:
183     case kCFRunLoopExit:
184       if (lock_acquired_) {
185         lock_acquired_ = false;
186         lock_.Release();
187       }
188       break;
189   }
190 }
191 
RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void * info)192 void RunLoopObserverCallBackFunc(CFRunLoopObserverRef observer,
193                                  CFRunLoopActivity activity,
194                                  void* info) {
195   // Forward the call to the instance of SynchronizedRunLoopObserver
196   // that is associated with the current CF run loop observer.
197   SynchronizedRunLoopObserver* observerInstance =
198       (SynchronizedRunLoopObserver*)info;
199   observerInstance->RunLoopObserverCallBack(observer, activity);
200 }
201 
202 #pragma mark - ProxyResolverMac
203 class ProxyResolverMac : public ProxyResolver {
204  public:
205   explicit ProxyResolverMac(const scoped_refptr<PacFileData>& script_data);
206   ~ProxyResolverMac() override;
207 
208   // ProxyResolver methods:
209   int GetProxyForURL(const GURL& url,
210                      const NetworkAnonymizationKey& network_anonymization_key,
211                      ProxyInfo* results,
212                      CompletionOnceCallback callback,
213                      std::unique_ptr<Request>* request,
214                      const NetLogWithSource& net_log) override;
215 
216  private:
217   const scoped_refptr<PacFileData> script_data_;
218 };
219 
ProxyResolverMac(const scoped_refptr<PacFileData> & script_data)220 ProxyResolverMac::ProxyResolverMac(
221     const scoped_refptr<PacFileData>& script_data)
222     : script_data_(script_data) {}
223 
224 ProxyResolverMac::~ProxyResolverMac() = default;
225 
226 // Gets the proxy information for a query URL from a PAC. Implementation
227 // inspired by http://developer.apple.com/samplecode/CFProxySupportTool/
GetProxyForURL(const GURL & query_url,const NetworkAnonymizationKey & network_anonymization_key,ProxyInfo * results,CompletionOnceCallback,std::unique_ptr<Request> *,const NetLogWithSource & net_log)228 int ProxyResolverMac::GetProxyForURL(
229     const GURL& query_url,
230     const NetworkAnonymizationKey& network_anonymization_key,
231     ProxyInfo* results,
232     CompletionOnceCallback /*callback*/,
233     std::unique_ptr<Request>* /*request*/,
234     const NetLogWithSource& net_log) {
235   // OS X's system resolver does not support WebSocket URLs in proxy.pac, as of
236   // version 10.13.5. See https://crbug.com/862121.
237   GURL mutable_query_url = query_url;
238   if (query_url.SchemeIsWSOrWSS()) {
239     GURL::Replacements replacements;
240     replacements.SetSchemeStr(query_url.SchemeIsCryptographic() ? "https"
241                                                                 : "http");
242     mutable_query_url = query_url.ReplaceComponents(replacements);
243   }
244 
245   base::apple::ScopedCFTypeRef<CFStringRef> query_ref(
246       base::SysUTF8ToCFStringRef(mutable_query_url.spec()));
247   base::apple::ScopedCFTypeRef<CFURLRef> query_url_ref(
248       CFURLCreateWithString(kCFAllocatorDefault, query_ref.get(), nullptr));
249   if (!query_url_ref.get())
250     return ERR_FAILED;
251   base::apple::ScopedCFTypeRef<CFStringRef> pac_ref(base::SysUTF8ToCFStringRef(
252       script_data_->type() == PacFileData::TYPE_AUTO_DETECT
253           ? std::string()
254           : script_data_->url().spec()));
255   base::apple::ScopedCFTypeRef<CFURLRef> pac_url_ref(
256       CFURLCreateWithString(kCFAllocatorDefault, pac_ref.get(), nullptr));
257   if (!pac_url_ref.get())
258     return ERR_FAILED;
259 
260   // Work around <rdar://problem/5530166>. This dummy call to
261   // CFNetworkCopyProxiesForURL initializes some state within CFNetwork that is
262   // required by CFNetworkExecuteProxyAutoConfigurationURL.
263 
264   base::apple::ScopedCFTypeRef<CFDictionaryRef> empty_dictionary(
265       CFDictionaryCreate(nullptr, nullptr, nullptr, 0, nullptr, nullptr));
266   base::apple::ScopedCFTypeRef<CFArrayRef> dummy_result(
267       CFNetworkCopyProxiesForURL(query_url_ref.get(), empty_dictionary.get()));
268 
269   // We cheat here. We need to act as if we were synchronous, so we pump the
270   // runloop ourselves. Our caller moved us to a new thread anyway, so this is
271   // OK to do. (BTW, CFNetworkExecuteProxyAutoConfigurationURL returns a
272   // runloop source we need to release despite its name.)
273 
274   CFTypeRef result = nullptr;
275   CFStreamClientContext context = {0, &result, nullptr, nullptr, nullptr};
276   base::apple::ScopedCFTypeRef<CFRunLoopSourceRef> runloop_source(
277       CFNetworkExecuteProxyAutoConfigurationURL(
278           pac_url_ref.get(), query_url_ref.get(), ResultCallback, &context));
279 #if LEAK_SANITIZER
280   // CFNetworkExecuteProxyAutoConfigurationURL leaks the returned
281   // CFRunLoopSourceRef. Filed as FB12170226.
282   __lsan_ignore_object(runloop_source.get());
283 #endif
284   if (!runloop_source)
285     return ERR_FAILED;
286 
287   const CFStringRef private_runloop_mode =
288       CFSTR("org.chromium.ProxyResolverMac");
289 
290   // Add the run loop observer to synchronize events of
291   // CFNetworkExecuteProxyAutoConfigurationURL sources. See the definition of
292   // |g_cfnetwork_pac_runloop_lock|.
293   SynchronizedRunLoopObserver observer(g_cfnetwork_pac_runloop_lock.Get());
294   observer.AddToCurrentRunLoop(private_runloop_mode);
295 
296   // Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources
297   // are added to the run loop concurrently.
298   {
299     base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get());
300     CFRunLoopAddSource(CFRunLoopGetCurrent(), runloop_source.get(),
301                        private_runloop_mode);
302   }
303 
304   CFRunLoopRunInMode(private_runloop_mode, DBL_MAX, false);
305 
306   // Make sure that no CFNetworkExecuteProxyAutoConfigurationURL sources
307   // are removed from the run loop concurrently.
308   {
309     base::AutoLock lock(g_cfnetwork_pac_runloop_lock.Get());
310     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runloop_source.get(),
311                           private_runloop_mode);
312   }
313   observer.RemoveFromCurrentRunLoop(private_runloop_mode);
314 
315   DCHECK(result);
316 
317   if (CFGetTypeID(result) == CFErrorGetTypeID()) {
318     // TODO(avi): do something better than this
319     CFRelease(result);
320     return ERR_FAILED;
321   }
322   base::apple::ScopedCFTypeRef<CFArrayRef> proxy_array_ref(
323       base::apple::CFCastStrict<CFArrayRef>(result));
324   DCHECK(proxy_array_ref);
325 
326   ProxyList proxy_list;
327 
328   CFIndex proxy_array_count = CFArrayGetCount(proxy_array_ref.get());
329   for (CFIndex i = 0; i < proxy_array_count; ++i) {
330     CFDictionaryRef proxy_dictionary =
331         base::apple::CFCastStrict<CFDictionaryRef>(
332             CFArrayGetValueAtIndex(proxy_array_ref.get(), i));
333     DCHECK(proxy_dictionary);
334 
335     // The dictionary may have the following keys:
336     // - kCFProxyTypeKey : The type of the proxy
337     // - kCFProxyHostNameKey
338     // - kCFProxyPortNumberKey : The meat we're after.
339     // - kCFProxyUsernameKey
340     // - kCFProxyPasswordKey : Despite the existence of these keys in the
341     //                         documentation, they're never populated. Even if a
342     //                         username/password were to be set in the network
343     //                         proxy system preferences, we'd need to fetch it
344     //                         from the Keychain ourselves. CFProxy is such a
345     //                         tease.
346     // - kCFProxyAutoConfigurationURLKey : If the PAC file specifies another
347     //                                     PAC file, I'm going home.
348 
349     CFStringRef proxy_type = base::apple::GetValueFromDictionary<CFStringRef>(
350         proxy_dictionary, kCFProxyTypeKey);
351     ProxyServer proxy_server = ProxyDictionaryToProxyServer(
352         GetProxyServerScheme(proxy_type), proxy_dictionary, kCFProxyHostNameKey,
353         kCFProxyPortNumberKey);
354     if (!proxy_server.is_valid())
355       continue;
356 
357     proxy_list.AddProxyServer(proxy_server);
358   }
359 
360   if (!proxy_list.IsEmpty())
361     results->UseProxyList(proxy_list);
362   // Else do nothing (results is already guaranteed to be in the default state).
363 
364   return OK;
365 }
366 
367 }  // namespace
368 
ProxyResolverFactoryMac()369 ProxyResolverFactoryMac::ProxyResolverFactoryMac()
370     : ProxyResolverFactory(false /*expects_pac_bytes*/) {
371 }
372 
CreateProxyResolver(const scoped_refptr<PacFileData> & pac_script,std::unique_ptr<ProxyResolver> * resolver,CompletionOnceCallback callback,std::unique_ptr<Request> * request)373 int ProxyResolverFactoryMac::CreateProxyResolver(
374     const scoped_refptr<PacFileData>& pac_script,
375     std::unique_ptr<ProxyResolver>* resolver,
376     CompletionOnceCallback callback,
377     std::unique_ptr<Request>* request) {
378   *resolver = std::make_unique<ProxyResolverMac>(pac_script);
379   return OK;
380 }
381 
382 }  // namespace net
383