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