OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/safe_browsing/download_protection_service.h" | 5 #include "chrome/browser/safe_browsing/download_protection_service.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/memory/scoped_ptr.h" | 8 #include "base/memory/scoped_ptr.h" |
9 #include "base/metrics/histogram.h" | 9 #include "base/metrics/histogram.h" |
10 #include "base/stl_util.h" | 10 #include "base/stl_util.h" |
11 #include "base/string_util.h" | |
11 #include "chrome/browser/safe_browsing/safe_browsing_service.h" | 12 #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
13 #include "chrome/browser/safe_browsing/signature_util.h" | |
12 #include "chrome/common/net/http_return.h" | 14 #include "chrome/common/net/http_return.h" |
13 #include "chrome/common/safe_browsing/csd.pb.h" | 15 #include "chrome/common/safe_browsing/csd.pb.h" |
14 #include "content/browser/browser_thread.h" | 16 #include "content/browser/browser_thread.h" |
17 #include "content/browser/download/download_item.h" | |
18 #include "content/public/common/url_fetcher_delegate.h" | |
15 #include "content/common/net/url_fetcher.h" | 19 #include "content/common/net/url_fetcher.h" |
16 #include "net/base/load_flags.h" | 20 #include "net/base/load_flags.h" |
17 #include "net/url_request/url_request_context_getter.h" | 21 #include "net/url_request/url_request_context_getter.h" |
18 #include "net/url_request/url_request_status.h" | 22 #include "net/url_request/url_request_status.h" |
19 | 23 |
20 namespace safe_browsing { | 24 namespace safe_browsing { |
21 | 25 |
22 const char DownloadProtectionService::kDownloadRequestUrl[] = | 26 const char DownloadProtectionService::kDownloadRequestUrl[] = |
23 "https://sb-ssl.google.com/safebrowsing/clientreport/download"; | 27 "https://sb-ssl.google.com/safebrowsing/clientreport/download"; |
24 | 28 |
29 namespace { | |
30 bool IsBinaryFile(const FilePath& file) { | |
31 return (file.MatchesExtension(FILE_PATH_LITERAL(".exe")) || | |
32 file.MatchesExtension(FILE_PATH_LITERAL(".cab")) || | |
33 file.MatchesExtension(FILE_PATH_LITERAL(".msi"))); | |
34 } | |
35 } // namespace | |
36 | |
25 DownloadProtectionService::DownloadInfo::DownloadInfo() | 37 DownloadProtectionService::DownloadInfo::DownloadInfo() |
26 : total_bytes(0), user_initiated(false) {} | 38 : total_bytes(0), user_initiated(false) {} |
27 | 39 |
28 DownloadProtectionService::DownloadInfo::~DownloadInfo() {} | 40 DownloadProtectionService::DownloadInfo::~DownloadInfo() {} |
29 | 41 |
30 DownloadProtectionService::DownloadProtectionService( | 42 // static |
31 SafeBrowsingService* sb_service, | 43 DownloadProtectionService::DownloadInfo |
32 net::URLRequestContextGetter* request_context_getter) | 44 DownloadProtectionService::DownloadInfo::FromDownloadItem( |
33 : sb_service_(sb_service), | 45 const DownloadItem& item) { |
34 request_context_getter_(request_context_getter), | 46 DownloadInfo download_info; |
35 enabled_(false) {} | 47 download_info.local_file = item.full_path(); |
36 | 48 download_info.download_url_chain = item.url_chain(); |
37 DownloadProtectionService::~DownloadProtectionService() { | 49 download_info.referrer_url = item.referrer_url(); |
38 STLDeleteContainerPairFirstPointers(download_requests_.begin(), | 50 // TODO(bryner): Fill in the hash (we shouldn't compute it again) |
39 download_requests_.end()); | 51 download_info.total_bytes = item.total_bytes(); |
40 download_requests_.clear(); | 52 // TODO(bryner): Populate user_initiated |
53 return download_info; | |
41 } | 54 } |
42 | 55 |
43 void DownloadProtectionService::SetEnabled(bool enabled) { | 56 class DownloadProtectionService::CheckClientDownloadRequest |
44 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | 57 : public base::RefCountedThreadSafe< |
45 BrowserThread::PostTask( | 58 DownloadProtectionService::CheckClientDownloadRequest, |
46 BrowserThread::IO, | 59 BrowserThread::DeleteOnUIThread>, |
47 FROM_HERE, | 60 public content::URLFetcherDelegate { |
48 base::Bind(&DownloadProtectionService::SetEnabledOnIOThread, | 61 public: |
49 this, enabled)); | 62 CheckClientDownloadRequest(const DownloadInfo& info, |
50 } | 63 const CheckDownloadCallback& callback, |
64 DownloadProtectionService* service, | |
65 SafeBrowsingService* sb_service) | |
66 : info_(info), | |
67 callback_(callback), | |
68 service_(service), | |
69 sb_service_(sb_service), | |
70 pingback_enabled_(service_->enabled()), | |
71 canceled_(false) { | |
72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
73 } | |
51 | 74 |
52 void DownloadProtectionService::SetEnabledOnIOThread(bool enabled) { | 75 void Start() { |
53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 76 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
54 if (enabled == enabled_) { | 77 // TODO(noelutz): implement some cache to make sure we don't issue the same |
55 return; | 78 // request over and over again if a user downloads the same binary multiple |
56 } | 79 // times. |
57 enabled_ = enabled; | 80 if (info_.download_url_chain.empty()) { |
58 if (!enabled_) { | 81 RecordStats(REASON_INVALID_URL); |
59 for (std::map<const URLFetcher*, CheckDownloadCallback>::iterator it = | 82 PostFinishTask(SAFE); |
60 download_requests_.begin(); | |
61 it != download_requests_.end(); ++it) { | |
62 it->second.Run(SAFE); | |
63 } | |
64 STLDeleteContainerPairFirstPointers(download_requests_.begin(), | |
65 download_requests_.end()); | |
66 download_requests_.clear(); | |
67 } | |
68 } | |
69 | |
70 void DownloadProtectionService::OnURLFetchComplete(const URLFetcher* source) { | |
71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
72 scoped_ptr<const URLFetcher> s(source); // will delete the URLFetcher object. | |
73 if (download_requests_.find(source) != download_requests_.end()) { | |
74 CheckDownloadCallback callback = download_requests_[source]; | |
75 download_requests_.erase(source); | |
76 if (!enabled_) { | |
77 // SafeBrowsing got disabled. We can't do anything. Note: the request | |
78 // object will be deleted. | |
79 RecordStats(REASON_SB_DISABLED); | |
80 return; | 83 return; |
81 } | 84 } |
85 const GURL& final_url = info_.download_url_chain.back(); | |
86 if (!final_url.is_valid() || final_url.is_empty() || | |
87 !final_url.SchemeIs("http")) { | |
88 RecordStats(REASON_INVALID_URL); | |
89 PostFinishTask(SAFE); | |
90 return; // For now we only support HTTP download URLs. | |
91 } | |
92 | |
93 if (!IsBinaryFile(info_.local_file)) { | |
94 RecordStats(REASON_NOT_BINARY_FILE); | |
95 PostFinishTask(SAFE); | |
96 return; | |
97 } | |
98 | |
99 // Compute features from the file contents. Note that we record histograms | |
100 // based on the result, so this runs regardless of whether the pingbacks | |
101 // are enabled. Since we do blocking I/O, this happens on the file thread. | |
102 BrowserThread::PostTask( | |
103 BrowserThread::FILE, | |
104 FROM_HERE, | |
105 base::Bind(&CheckClientDownloadRequest::ExtractFileFeatures, this)); | |
106 } | |
107 | |
108 // Canceling a request will cause us to always report the result as SAFE. | |
109 void Cancel() { | |
110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
111 canceled_ = true; | |
112 } | |
113 | |
114 // From the content::URLFetcher interface. | |
115 virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE { | |
116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
117 DCHECK_EQ(source, fetcher_.get()); | |
118 VLOG(2) << "Received a response for URL: " | |
119 << info_.download_url_chain.back() << ": success=" | |
120 << source->status().is_success() << " response_code=" | |
121 << source->response_code(); | |
82 DownloadCheckResultReason reason = REASON_MAX; | 122 DownloadCheckResultReason reason = REASON_MAX; |
83 reason = REASON_SERVER_PING_FAILED; | 123 reason = REASON_SERVER_PING_FAILED; |
84 if (source->status().is_success() && | 124 if (source->status().is_success() && |
85 RC_REQUEST_OK == source->response_code()) { | 125 RC_REQUEST_OK == source->response_code()) { |
86 std::string data; | 126 std::string data; |
87 source->GetResponseAsString(&data); | 127 source->GetResponseAsString(&data); |
88 if (data.size() > 0) { | 128 if (data.size() > 0) { |
89 // For now no matter what we'll always say the download is safe. | 129 // For now no matter what we'll always say the download is safe. |
90 // TODO(noelutz): Parse the response body to see exactly what's going | 130 // TODO(noelutz): Parse the response body to see exactly what's going |
91 // on. | 131 // on. |
92 reason = REASON_INVALID_RESPONSE_PROTO; | 132 reason = REASON_INVALID_RESPONSE_PROTO; |
93 } | 133 } |
94 } | 134 } |
135 | |
136 if (reason != REASON_MAX) { | |
137 RecordStats(reason); | |
138 } | |
139 FinishRequest(SAFE); | |
140 } | |
141 | |
142 private: | |
143 friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; | |
144 friend class DeleteTask<CheckClientDownloadRequest>; | |
145 | |
146 virtual ~CheckClientDownloadRequest() { | |
147 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
148 } | |
149 | |
150 void ExtractFileFeatures() { | |
151 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); | |
152 bool is_signed; | |
153 if (safe_browsing::signature_util::IsSigned(info_.local_file)) { | |
154 VLOG(2) << "Downloaded a signed binary: " << info_.local_file.value(); | |
155 is_signed = true; | |
156 } else { | |
157 VLOG(2) << "Downloaded an unsigned binary: " << info_.local_file.value(); | |
158 is_signed = false; | |
159 } | |
160 UMA_HISTOGRAM_BOOLEAN("SBClientDownload.SignedBinaryDownload", is_signed); | |
161 | |
162 // TODO(noelutz): DownloadInfo should also contain the IP address of every | |
163 // URL in the redirect chain. We also should check whether the download | |
164 // URL is hosted on the internal network. | |
165 BrowserThread::PostTask( | |
166 BrowserThread::IO, | |
167 FROM_HERE, | |
168 base::Bind(&CheckClientDownloadRequest::CheckWhitelists, this)); | |
169 } | |
170 | |
171 void CheckWhitelists() { | |
172 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | |
173 DownloadCheckResultReason reason = REASON_MAX; | |
174 if (!pingback_enabled_ || !sb_service_.get()) { | |
175 reason = REASON_SB_DISABLED; | |
176 } else { | |
177 for (size_t i = 0; i < info_.download_url_chain.size(); ++i) { | |
178 const GURL& url = info_.download_url_chain[i]; | |
179 if (url.is_valid() && sb_service_->MatchDownloadWhitelistUrl(url)) { | |
180 reason = REASON_WHITELISTED_URL; | |
181 break; | |
182 } | |
183 } | |
184 if (info_.referrer_url.is_valid() && | |
185 sb_service_->MatchDownloadWhitelistUrl(info_.referrer_url)) { | |
186 reason = REASON_WHITELISTED_REFERRER; | |
187 } | |
188 } | |
189 if (reason != REASON_MAX) { | |
190 RecordStats(reason); | |
191 PostFinishTask(SAFE); | |
192 return; | |
193 } | |
194 | |
195 // TODO(noelutz): check signature and CA against whitelist. | |
196 | |
197 // The URLFetcher is owned by the UI thread, so post a message to | |
198 // start the pigback. | |
95 BrowserThread::PostTask( | 199 BrowserThread::PostTask( |
96 BrowserThread::UI, | 200 BrowserThread::UI, |
97 FROM_HERE, | 201 FROM_HERE, |
98 base::Bind(&DownloadProtectionService::EndCheckClientDownload, | 202 base::Bind(&CheckClientDownloadRequest::SendRequest, this)); |
99 this, SAFE, reason, callback)); | 203 } |
100 } else { | 204 |
101 NOTREACHED(); | 205 void SendRequest() { |
206 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
207 ClientDownloadRequest request; | |
208 request.set_url(info_.download_url_chain.back().spec()); | |
209 request.mutable_digests()->set_sha256(info_.sha256_hash); | |
210 request.set_length(info_.total_bytes); | |
211 for (size_t i = 0; i < info_.download_url_chain.size(); ++i) { | |
212 ClientDownloadRequest::Resource* resource = request.add_resources(); | |
213 resource->set_url(info_.download_url_chain[i].spec()); | |
214 if (i == info_.download_url_chain.size() - 1) { | |
215 // The last URL in the chain is the download URL. | |
216 resource->set_type(ClientDownloadRequest::DOWNLOAD_URL); | |
217 resource->set_referrer(info_.referrer_url.spec()); | |
218 } else { | |
219 resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT); | |
220 } | |
221 // TODO(noelutz): fill out the remote IP addresses. | |
222 } | |
223 request.set_user_initiated(info_.user_initiated); | |
224 std::string request_data; | |
225 if (!request.SerializeToString(&request_data)) { | |
226 RecordStats(REASON_INVALID_REQUEST_PROTO); | |
227 FinishRequest(SAFE); | |
228 return; | |
229 } | |
230 | |
231 VLOG(2) << "Sending a request for URL: " | |
232 << info_.download_url_chain.back(); | |
233 fetcher_.reset(URLFetcher::Create(0 /* ID used for testing */, | |
234 GURL(kDownloadRequestUrl), | |
235 URLFetcher::POST, | |
236 this)); | |
237 fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE); | |
238 fetcher_->set_request_context(service_->request_context_getter_.get()); | |
noelutz
2011/10/25 21:43:50
Check if the request is cancelled before accessing
Brian Ryner
2011/10/25 22:03:07
Done.
| |
239 fetcher_->set_upload_data("application/octet-stream", request_data); | |
240 fetcher_->Start(); | |
241 } | |
242 | |
243 void PostFinishTask(DownloadCheckResult result) { | |
244 BrowserThread::PostTask( | |
245 BrowserThread::UI, | |
246 FROM_HERE, | |
247 base::Bind(&CheckClientDownloadRequest::FinishRequest, this, result)); | |
248 } | |
249 | |
250 void FinishRequest(DownloadCheckResult result) { | |
251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
252 callback_.Run(canceled_ ? SAFE : result); | |
253 service_->RequestFinished(this); | |
noelutz
2011/10/25 21:43:50
Same here. I'm pretty sure it's possible for serv
Brian Ryner
2011/10/25 22:03:07
Ah, I guess you mean in the ShutDown() case? Done
noelutz
2011/10/25 22:13:42
Yeah the ShutDown case.
| |
254 } | |
255 | |
256 void RecordStats(DownloadCheckResultReason reason) { | |
257 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats", | |
258 reason, | |
259 REASON_MAX); | |
260 } | |
261 | |
262 DownloadInfo info_; | |
263 CheckDownloadCallback callback_; | |
264 DownloadProtectionService* service_; | |
265 scoped_refptr<SafeBrowsingService> sb_service_; | |
266 bool pingback_enabled_; | |
267 bool canceled_; | |
268 scoped_ptr<URLFetcher> fetcher_; | |
269 | |
270 DISALLOW_COPY_AND_ASSIGN(CheckClientDownloadRequest); | |
271 }; | |
272 | |
273 DownloadProtectionService::DownloadProtectionService( | |
274 SafeBrowsingService* sb_service, | |
275 net::URLRequestContextGetter* request_context_getter) | |
276 : sb_service_(sb_service), | |
277 request_context_getter_(request_context_getter), | |
278 enabled_(false) {} | |
279 | |
280 DownloadProtectionService::~DownloadProtectionService() { | |
281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
282 // The SafeBrowsingService owns this class, and any pending request will | |
283 // have an outstanding reference to the SafeBrowsingService. Therefore, | |
284 // at this point there must not be any active requests. | |
285 DCHECK(download_requests_.empty()); | |
noelutz
2011/10/25 21:43:50
I think you need to cancel requests here.
Brian Ryner
2011/10/25 22:03:07
So normally if we cancel a request, we still rely
noelutz
2011/10/25 22:13:42
Not necessarily but I believe there is a bug here.
| |
286 } | |
287 | |
288 void DownloadProtectionService::SetEnabled(bool enabled) { | |
289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
290 if (enabled == enabled_) { | |
291 return; | |
292 } | |
293 enabled_ = enabled; | |
294 if (!enabled_) { | |
295 for (std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it = | |
296 download_requests_.begin(); | |
297 it != download_requests_.end(); ++it) { | |
298 (*it)->Cancel(); | |
299 } | |
102 } | 300 } |
103 } | 301 } |
104 | 302 |
105 bool DownloadProtectionService::CheckClientDownload( | 303 void DownloadProtectionService::CheckClientDownload( |
106 const DownloadInfo& info, | 304 const DownloadProtectionService::DownloadInfo& info, |
107 const CheckDownloadCallback& callback) { | 305 const CheckDownloadCallback& callback) { |
108 // TODO(noelutz): implement some cache to make sure we don't issue the same | 306 scoped_refptr<CheckClientDownloadRequest> request( |
109 // request over and over again if a user downloads the same binary multiple | 307 new CheckClientDownloadRequest(info, callback, this, sb_service_)); |
110 // times. | 308 download_requests_.insert(request); |
111 if (info.download_url_chain.empty()) { | 309 request->Start(); |
112 RecordStats(REASON_INVALID_URL); | |
113 return true; | |
114 } | |
115 const GURL& final_url = info.download_url_chain.back(); | |
116 if (!final_url.is_valid() || final_url.is_empty() || | |
117 !final_url.SchemeIs("http")) { | |
118 RecordStats(REASON_INVALID_URL); | |
119 return true; // For now we only support HTTP download URLs. | |
120 } | |
121 // TODO(noelutz): DownloadInfo should also contain the IP address of every | |
122 // URL in the redirect chain. We also should check whether the download URL | |
123 // is hosted on the internal network. | |
124 BrowserThread::PostTask( | |
125 BrowserThread::IO, | |
126 FROM_HERE, | |
127 base::Bind(&DownloadProtectionService::StartCheckClientDownload, | |
128 this, info, callback)); | |
129 return false; | |
130 } | 310 } |
131 | 311 |
132 void DownloadProtectionService::StartCheckClientDownload( | 312 void DownloadProtectionService::RequestFinished( |
133 const DownloadInfo& info, | 313 CheckClientDownloadRequest* request) { |
134 const CheckDownloadCallback& callback) { | 314 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
135 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); | 315 std::set<scoped_refptr<CheckClientDownloadRequest> >::iterator it = |
136 if (!enabled_ || !sb_service_.get()) { | 316 download_requests_.find(request); |
137 // This is a hard fail. We won't even call the callback in this case. | 317 DCHECK(it != download_requests_.end()); |
138 RecordStats(REASON_SB_DISABLED); | 318 download_requests_.erase(*it); |
139 return; | |
140 } | |
141 DownloadCheckResultReason reason = REASON_MAX; | |
142 for (size_t i = 0; i < info.download_url_chain.size(); ++i) { | |
143 if (sb_service_->MatchDownloadWhitelistUrl(info.download_url_chain[i])) { | |
144 reason = REASON_WHITELISTED_URL; | |
145 break; | |
146 } | |
147 } | |
148 if (sb_service_->MatchDownloadWhitelistUrl(info.referrer_url)) { | |
149 reason = REASON_WHITELISTED_REFERRER; | |
150 } | |
151 // TODO(noelutz): check signature and CA against whitelist. | |
152 | |
153 ClientDownloadRequest request; | |
154 request.set_url(info.download_url_chain.back().spec()); | |
155 request.mutable_digests()->set_sha256(info.sha256_hash); | |
156 request.set_length(info.total_bytes); | |
157 for (size_t i = 0; i < info.download_url_chain.size(); ++i) { | |
158 ClientDownloadRequest::Resource* resource = request.add_resources(); | |
159 resource->set_url(info.download_url_chain[i].spec()); | |
160 if (i == info.download_url_chain.size() - 1) { | |
161 // The last URL in the chain is the download URL. | |
162 resource->set_type(ClientDownloadRequest::DOWNLOAD_URL); | |
163 resource->set_referrer(info.referrer_url.spec()); | |
164 } else { | |
165 resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT); | |
166 } | |
167 // TODO(noelutz): fill out the remote IP addresses. | |
168 } | |
169 request.set_user_initiated(info.user_initiated); | |
170 std::string request_data; | |
171 if (!request.SerializeToString(&request_data)) { | |
172 reason = REASON_INVALID_REQUEST_PROTO; | |
173 } | |
174 | |
175 if (reason != REASON_MAX) { | |
176 // We stop here because the download is considered safe. | |
177 BrowserThread::PostTask( | |
178 BrowserThread::UI, | |
179 FROM_HERE, | |
180 base::Bind(&DownloadProtectionService::EndCheckClientDownload, | |
181 this, SAFE, reason, callback)); | |
182 return; | |
183 } | |
184 | |
185 URLFetcher* fetcher = URLFetcher::Create(0 /* ID used for testing */, | |
186 GURL(kDownloadRequestUrl), | |
187 URLFetcher::POST, | |
188 this); | |
189 download_requests_[fetcher] = callback; | |
190 fetcher->set_load_flags(net::LOAD_DISABLE_CACHE); | |
191 fetcher->set_request_context(request_context_getter_.get()); | |
192 fetcher->set_upload_data("application/octet-stream", request_data); | |
193 fetcher->Start(); | |
194 } | 319 } |
195 | 320 |
196 void DownloadProtectionService::EndCheckClientDownload( | |
197 DownloadCheckResult result, | |
198 DownloadCheckResultReason reason, | |
199 const CheckDownloadCallback& callback) { | |
200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
201 RecordStats(reason); | |
202 callback.Run(result); | |
203 } | |
204 | |
205 void DownloadProtectionService::RecordStats(DownloadCheckResultReason reason) { | |
206 UMA_HISTOGRAM_ENUMERATION("SBClientDownload.CheckDownloadStats", | |
207 reason, | |
208 REASON_MAX); | |
209 } | |
210 } // namespace safe_browsing | 321 } // namespace safe_browsing |
OLD | NEW |