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.
4
5 #include "chrome/browser/safe_browsing/client_side_detection_service.h"
6
7 #include "base/command_line.h"
8 #include "base/file_path.h"
9 #include "base/file_util_proxy.h"
10 #include "base/logging.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/message_loop.h"
13 #include "base/metrics/histogram.h"
14 #include "base/platform_file.h"
15 #include "base/stl_util-inl.h"
16 #include "base/task.h"
17 #include "base/time.h"
18 #include "chrome/common/net/http_return.h"
19 #include "chrome/common/net/url_fetcher.h"
20 #include "chrome/common/safe_browsing/csd.pb.h"
21 #include "content/browser/browser_thread.h"
22 #include "googleurl/src/gurl.h"
23 #include "net/base/load_flags.h"
24 #include "net/url_request/url_request_context_getter.h"
25 #include "net/url_request/url_request_status.h"
26
27 namespace safe_browsing {
28
29 const int ClientSideDetectionService::kMaxReportsPerInterval = 3;
30
31 const base::TimeDelta ClientSideDetectionService::kReportsInterval =
32 base::TimeDelta::FromDays(1);
33 const base::TimeDelta ClientSideDetectionService::kNegativeCacheInterval =
34 base::TimeDelta::FromDays(1);
35 const base::TimeDelta ClientSideDetectionService::kPositiveCacheInterval =
36 base::TimeDelta::FromMinutes(30);
37
38 const char ClientSideDetectionService::kClientReportPhishingUrl[] =
39 "https://sb-ssl.google.com/safebrowsing/clientreport/phishing";
40 // Note: when updatng the model version, don't forget to change the filename
41 // in chrome/common/chrome_constants.cc as well, or else existing users won't
42 // download the new model.
43 //
44 // TODO(bryner): add version metadata so that clients can download new models
45 // without needing a new model filename.
46 const char ClientSideDetectionService::kClientModelUrl[] =
47 "https://ssl.gstatic.com/safebrowsing/csd/client_model_v1.pb";
48
49 struct ClientSideDetectionService::ClientReportInfo {
50 scoped_ptr<ClientReportPhishingRequestCallback> callback;
51 GURL phishing_url;
52 };
53
CacheState(bool phish,base::Time time)54 ClientSideDetectionService::CacheState::CacheState(bool phish, base::Time time)
55 : is_phishing(phish),
56 timestamp(time) {}
57
ClientSideDetectionService(const FilePath & model_path,net::URLRequestContextGetter * request_context_getter)58 ClientSideDetectionService::ClientSideDetectionService(
59 const FilePath& model_path,
60 net::URLRequestContextGetter* request_context_getter)
61 : model_path_(model_path),
62 model_status_(UNKNOWN_STATUS),
63 model_file_(base::kInvalidPlatformFileValue),
64 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
65 ALLOW_THIS_IN_INITIALIZER_LIST(callback_factory_(this)),
66 request_context_getter_(request_context_getter) {}
67
~ClientSideDetectionService()68 ClientSideDetectionService::~ClientSideDetectionService() {
69 method_factory_.RevokeAll();
70 STLDeleteContainerPairPointers(client_phishing_reports_.begin(),
71 client_phishing_reports_.end());
72 client_phishing_reports_.clear();
73 STLDeleteElements(&open_callbacks_);
74 CloseModelFile();
75 }
76
77 /* static */
Create(const FilePath & model_path,net::URLRequestContextGetter * request_context_getter)78 ClientSideDetectionService* ClientSideDetectionService::Create(
79 const FilePath& model_path,
80 net::URLRequestContextGetter* request_context_getter) {
81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
82 scoped_ptr<ClientSideDetectionService> service(
83 new ClientSideDetectionService(model_path, request_context_getter));
84 if (!service->InitializePrivateNetworks()) {
85 UMA_HISTOGRAM_COUNTS("SBClientPhishing.InitPrivateNetworksFailed", 1);
86 return NULL;
87 }
88
89 // We try to open the model file right away and start fetching it if
90 // it does not already exist on disk.
91 base::FileUtilProxy::CreateOrOpenCallback* cb =
92 service.get()->callback_factory_.NewCallback(
93 &ClientSideDetectionService::OpenModelFileDone);
94 if (!base::FileUtilProxy::CreateOrOpen(
95 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
96 model_path,
97 base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
98 cb)) {
99 delete cb;
100 return NULL;
101 }
102
103 // Delete the previous-version model file.
104 // TODO(bryner): Remove this for M14.
105 base::FileUtilProxy::Delete(
106 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
107 model_path.DirName().AppendASCII("Safe Browsing Phishing Model"),
108 false /* not recursive */,
109 NULL /* not interested in result */);
110 return service.release();
111 }
112
GetModelFile(OpenModelDoneCallback * callback)113 void ClientSideDetectionService::GetModelFile(OpenModelDoneCallback* callback) {
114 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
115 MessageLoop::current()->PostTask(
116 FROM_HERE,
117 method_factory_.NewRunnableMethod(
118 &ClientSideDetectionService::StartGetModelFile, callback));
119 }
120
SendClientReportPhishingRequest(ClientPhishingRequest * verdict,ClientReportPhishingRequestCallback * callback)121 void ClientSideDetectionService::SendClientReportPhishingRequest(
122 ClientPhishingRequest* verdict,
123 ClientReportPhishingRequestCallback* callback) {
124 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
125 MessageLoop::current()->PostTask(
126 FROM_HERE,
127 method_factory_.NewRunnableMethod(
128 &ClientSideDetectionService::StartClientReportPhishingRequest,
129 verdict, callback));
130 }
131
IsPrivateIPAddress(const std::string & ip_address) const132 bool ClientSideDetectionService::IsPrivateIPAddress(
133 const std::string& ip_address) const {
134 net::IPAddressNumber ip_number;
135 if (!net::ParseIPLiteralToNumber(ip_address, &ip_number)) {
136 DLOG(WARNING) << "Unable to parse IP address: " << ip_address;
137 // Err on the side of safety and assume this might be private.
138 return true;
139 }
140
141 for (std::vector<AddressRange>::const_iterator it =
142 private_networks_.begin();
143 it != private_networks_.end(); ++it) {
144 if (net::IPNumberMatchesPrefix(ip_number, it->first, it->second)) {
145 return true;
146 }
147 }
148 return false;
149 }
150
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)151 void ClientSideDetectionService::OnURLFetchComplete(
152 const URLFetcher* source,
153 const GURL& url,
154 const net::URLRequestStatus& status,
155 int response_code,
156 const ResponseCookies& cookies,
157 const std::string& data) {
158 if (source == model_fetcher_.get()) {
159 HandleModelResponse(source, url, status, response_code, cookies, data);
160 } else if (client_phishing_reports_.find(source) !=
161 client_phishing_reports_.end()) {
162 HandlePhishingVerdict(source, url, status, response_code, cookies, data);
163 } else {
164 NOTREACHED();
165 }
166 }
167
SetModelStatus(ModelStatus status)168 void ClientSideDetectionService::SetModelStatus(ModelStatus status) {
169 DCHECK_NE(READY_STATUS, model_status_);
170 model_status_ = status;
171 if (READY_STATUS == status || ERROR_STATUS == status) {
172 for (size_t i = 0; i < open_callbacks_.size(); ++i) {
173 open_callbacks_[i]->Run(model_file_);
174 }
175 STLDeleteElements(&open_callbacks_);
176 } else {
177 NOTREACHED();
178 }
179 }
180
OpenModelFileDone(base::PlatformFileError error_code,base::PassPlatformFile file,bool created)181 void ClientSideDetectionService::OpenModelFileDone(
182 base::PlatformFileError error_code,
183 base::PassPlatformFile file,
184 bool created) {
185 DCHECK(!created);
186 if (base::PLATFORM_FILE_OK == error_code) {
187 // The model file already exists. There is no need to fetch the model.
188 model_file_ = file.ReleaseValue();
189 SetModelStatus(READY_STATUS);
190 } else if (base::PLATFORM_FILE_ERROR_NOT_FOUND == error_code) {
191 // We need to fetch the model since it does not exist yet.
192 model_fetcher_.reset(URLFetcher::Create(0 /* ID is not used */,
193 GURL(kClientModelUrl),
194 URLFetcher::GET,
195 this));
196 model_fetcher_->set_request_context(request_context_getter_.get());
197 model_fetcher_->Start();
198 } else {
199 // It is not clear what we should do in this case. For now we simply fail.
200 // Hopefully, we'll be able to read the model during the next browser
201 // restart.
202 SetModelStatus(ERROR_STATUS);
203 }
204 }
205
CreateModelFileDone(base::PlatformFileError error_code,base::PassPlatformFile file,bool created)206 void ClientSideDetectionService::CreateModelFileDone(
207 base::PlatformFileError error_code,
208 base::PassPlatformFile file,
209 bool created) {
210 model_file_ = file.ReleaseValue();
211 base::FileUtilProxy::WriteCallback* cb = callback_factory_.NewCallback(
212 &ClientSideDetectionService::WriteModelFileDone);
213 if (!created ||
214 base::PLATFORM_FILE_OK != error_code ||
215 !base::FileUtilProxy::Write(
216 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
217 model_file_,
218 0 /* offset */, tmp_model_string_->data(), tmp_model_string_->size(),
219 cb)) {
220 delete cb;
221 // An error occurred somewhere. We close the model file if necessary and
222 // then run all the pending callbacks giving them an invalid model file.
223 CloseModelFile();
224 SetModelStatus(ERROR_STATUS);
225 }
226 }
227
WriteModelFileDone(base::PlatformFileError error_code,int bytes_written)228 void ClientSideDetectionService::WriteModelFileDone(
229 base::PlatformFileError error_code,
230 int bytes_written) {
231 if (base::PLATFORM_FILE_OK == error_code) {
232 SetModelStatus(READY_STATUS);
233 } else {
234 // TODO(noelutz): maybe we should retry writing the model since we
235 // did already fetch the model?
236 CloseModelFile();
237 SetModelStatus(ERROR_STATUS);
238 }
239 // Delete the model string that we kept around while we were writing the
240 // string to disk - we don't need it anymore.
241 tmp_model_string_.reset();
242 }
243
CloseModelFile()244 void ClientSideDetectionService::CloseModelFile() {
245 if (model_file_ != base::kInvalidPlatformFileValue) {
246 base::FileUtilProxy::Close(
247 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
248 model_file_,
249 NULL);
250 }
251 model_file_ = base::kInvalidPlatformFileValue;
252 }
253
StartGetModelFile(OpenModelDoneCallback * callback)254 void ClientSideDetectionService::StartGetModelFile(
255 OpenModelDoneCallback* callback) {
256 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
257 if (UNKNOWN_STATUS == model_status_) {
258 // Store the callback which will be called once we know the status of the
259 // model file.
260 open_callbacks_.push_back(callback);
261 } else {
262 // The model is either in READY or ERROR state which means we can
263 // call the callback right away.
264 callback->Run(model_file_);
265 delete callback;
266 }
267 }
268
StartClientReportPhishingRequest(ClientPhishingRequest * verdict,ClientReportPhishingRequestCallback * callback)269 void ClientSideDetectionService::StartClientReportPhishingRequest(
270 ClientPhishingRequest* verdict,
271 ClientReportPhishingRequestCallback* callback) {
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
273 scoped_ptr<ClientPhishingRequest> request(verdict);
274 scoped_ptr<ClientReportPhishingRequestCallback> cb(callback);
275
276 std::string request_data;
277 if (!request->SerializeToString(&request_data)) {
278 UMA_HISTOGRAM_COUNTS("SBClientPhishing.RequestNotSerialized", 1);
279 VLOG(1) << "Unable to serialize the CSD request. Proto file changed?";
280 cb->Run(GURL(request->url()), false);
281 return;
282 }
283
284 URLFetcher* fetcher = URLFetcher::Create(0 /* ID is not used */,
285 GURL(kClientReportPhishingUrl),
286 URLFetcher::POST,
287 this);
288
289 // Remember which callback and URL correspond to the current fetcher object.
290 ClientReportInfo* info = new ClientReportInfo;
291 info->callback.swap(cb); // takes ownership of the callback.
292 info->phishing_url = GURL(request->url());
293 client_phishing_reports_[fetcher] = info;
294
295 fetcher->set_load_flags(net::LOAD_DISABLE_CACHE);
296 fetcher->set_request_context(request_context_getter_.get());
297 fetcher->set_upload_data("application/octet-stream", request_data);
298 fetcher->Start();
299
300 // Record that we made a request
301 phishing_report_times_.push(base::Time::Now());
302 }
303
HandleModelResponse(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)304 void ClientSideDetectionService::HandleModelResponse(
305 const URLFetcher* source,
306 const GURL& url,
307 const net::URLRequestStatus& status,
308 int response_code,
309 const ResponseCookies& cookies,
310 const std::string& data) {
311 if (status.is_success() && RC_REQUEST_OK == response_code) {
312 // Copy the model because it has to be accessible after this function
313 // returns. Once we have written the model to a file we will delete the
314 // temporary model string. TODO(noelutz): don't store the model to disk if
315 // it's invalid.
316 tmp_model_string_.reset(new std::string(data));
317 base::FileUtilProxy::CreateOrOpenCallback* cb =
318 callback_factory_.NewCallback(
319 &ClientSideDetectionService::CreateModelFileDone);
320 if (!base::FileUtilProxy::CreateOrOpen(
321 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE),
322 model_path_,
323 base::PLATFORM_FILE_CREATE_ALWAYS |
324 base::PLATFORM_FILE_WRITE |
325 base::PLATFORM_FILE_READ,
326 cb)) {
327 delete cb;
328 SetModelStatus(ERROR_STATUS);
329 }
330 } else {
331 SetModelStatus(ERROR_STATUS);
332 }
333 }
334
HandlePhishingVerdict(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)335 void ClientSideDetectionService::HandlePhishingVerdict(
336 const URLFetcher* source,
337 const GURL& url,
338 const net::URLRequestStatus& status,
339 int response_code,
340 const ResponseCookies& cookies,
341 const std::string& data) {
342 ClientPhishingResponse response;
343 scoped_ptr<ClientReportInfo> info(client_phishing_reports_[source]);
344 if (status.is_success() && RC_REQUEST_OK == response_code &&
345 response.ParseFromString(data)) {
346 // Cache response, possibly flushing an old one.
347 cache_[info->phishing_url] =
348 make_linked_ptr(new CacheState(response.phishy(), base::Time::Now()));
349 info->callback->Run(info->phishing_url, response.phishy());
350 } else {
351 DLOG(ERROR) << "Unable to get the server verdict for URL: "
352 << info->phishing_url << " status: " << status.status() << " "
353 << "response_code:" << response_code;
354 info->callback->Run(info->phishing_url, false);
355 }
356 client_phishing_reports_.erase(source);
357 delete source;
358 }
359
IsInCache(const GURL & url)360 bool ClientSideDetectionService::IsInCache(const GURL& url) {
361 UpdateCache();
362
363 return cache_.find(url) != cache_.end();
364 }
365
GetValidCachedResult(const GURL & url,bool * is_phishing)366 bool ClientSideDetectionService::GetValidCachedResult(const GURL& url,
367 bool* is_phishing) {
368 UpdateCache();
369
370 PhishingCache::iterator it = cache_.find(url);
371 if (it == cache_.end()) {
372 return false;
373 }
374
375 // We still need to check if the result is valid.
376 const CacheState& cache_state = *it->second;
377 if (cache_state.is_phishing ?
378 cache_state.timestamp > base::Time::Now() - kPositiveCacheInterval :
379 cache_state.timestamp > base::Time::Now() - kNegativeCacheInterval) {
380 *is_phishing = cache_state.is_phishing;
381 return true;
382 }
383 return false;
384 }
385
UpdateCache()386 void ClientSideDetectionService::UpdateCache() {
387 // Since we limit the number of requests but allow pass-through for cache
388 // refreshes, we don't want to remove elements from the cache if they
389 // could be used for this purpose even if we will not use the entry to
390 // satisfy the request from the cache.
391 base::TimeDelta positive_cache_interval =
392 std::max(kPositiveCacheInterval, kReportsInterval);
393 base::TimeDelta negative_cache_interval =
394 std::max(kNegativeCacheInterval, kReportsInterval);
395
396 // Remove elements from the cache that will no longer be used.
397 for (PhishingCache::iterator it = cache_.begin(); it != cache_.end();) {
398 const CacheState& cache_state = *it->second;
399 if (cache_state.is_phishing ?
400 cache_state.timestamp > base::Time::Now() - positive_cache_interval :
401 cache_state.timestamp > base::Time::Now() - negative_cache_interval) {
402 ++it;
403 } else {
404 cache_.erase(it++);
405 }
406 }
407 }
408
OverReportLimit()409 bool ClientSideDetectionService::OverReportLimit() {
410 return GetNumReports() > kMaxReportsPerInterval;
411 }
412
GetNumReports()413 int ClientSideDetectionService::GetNumReports() {
414 base::Time cutoff = base::Time::Now() - kReportsInterval;
415
416 // Erase items older than cutoff because we will never care about them again.
417 while (!phishing_report_times_.empty() &&
418 phishing_report_times_.front() < cutoff) {
419 phishing_report_times_.pop();
420 }
421
422 // Return the number of elements that are above the cutoff.
423 return phishing_report_times_.size();
424 }
425
InitializePrivateNetworks()426 bool ClientSideDetectionService::InitializePrivateNetworks() {
427 static const char* const kPrivateNetworks[] = {
428 "10.0.0.0/8",
429 "127.0.0.0/8",
430 "172.16.0.0/12",
431 "192.168.0.0/16",
432 // IPv6 address ranges
433 "fc00::/7",
434 "fec0::/10",
435 "::1/128",
436 };
437
438 for (size_t i = 0; i < arraysize(kPrivateNetworks); ++i) {
439 net::IPAddressNumber ip_number;
440 size_t prefix_length;
441 if (net::ParseCIDRBlock(kPrivateNetworks[i], &ip_number, &prefix_length)) {
442 private_networks_.push_back(std::make_pair(ip_number, prefix_length));
443 } else {
444 DLOG(FATAL) << "Unable to parse IP address range: "
445 << kPrivateNetworks[i];
446 return false;
447 }
448 }
449 return true;
450 }
451
452 } // namespace safe_browsing
453