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