1 // Copyright 2019 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/native_file_system/chrome_native_file_system_permission_context.h"
6 
7 #include <string>
8 #include <utility>
9 
10 #include "base/base_paths.h"
11 #include "base/bind.h"
12 #include "base/files/file_path.h"
13 #include "base/metrics/histogram_functions.h"
14 #include "base/path_service.h"
15 #include "base/task/thread_pool.h"
16 #include "base/util/values/values_util.h"
17 #include "build/build_config.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
20 #include "chrome/browser/native_file_system/native_file_system_permission_context_factory.h"
21 #include "chrome/browser/native_file_system/native_file_system_permission_request_manager.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
24 #include "chrome/browser/ui/native_file_system_dialogs.h"
25 #include "chrome/common/chrome_paths.h"
26 #include "components/content_settings/core/browser/host_content_settings_map.h"
27 #include "components/content_settings/core/common/content_settings.h"
28 #include "components/safe_browsing/buildflags.h"
29 #include "content/public/browser/browser_task_traits.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/render_frame_host.h"
32 #include "content/public/browser/render_process_host.h"
33 #include "content/public/browser/web_contents.h"
34 
35 namespace {
36 
37 using HandleType = content::NativeFileSystemPermissionContext::HandleType;
38 
39 // Dictionary key for the FILE_SYSTEM_LAST_PICKED_DIRECTORY website setting.
40 const char kLastPickedDirectoryKey[] = "default-path";
41 
ShowNativeFileSystemRestrictedDirectoryDialogOnUIThread(content::GlobalFrameRoutingId frame_id,const url::Origin & origin,const base::FilePath & path,HandleType handle_type,base::OnceCallback<void (ChromeNativeFileSystemPermissionContext::SensitiveDirectoryResult)> callback)42 void ShowNativeFileSystemRestrictedDirectoryDialogOnUIThread(
43     content::GlobalFrameRoutingId frame_id,
44     const url::Origin& origin,
45     const base::FilePath& path,
46     HandleType handle_type,
47     base::OnceCallback<
48         void(ChromeNativeFileSystemPermissionContext::SensitiveDirectoryResult)>
49         callback) {
50   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
51   content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(frame_id);
52   if (!rfh || !rfh->IsCurrent()) {
53     // Requested from a no longer valid render frame host.
54     std::move(callback).Run(ChromeNativeFileSystemPermissionContext::
55                                 SensitiveDirectoryResult::kAbort);
56     return;
57   }
58 
59   content::WebContents* web_contents =
60       content::WebContents::FromRenderFrameHost(rfh);
61   if (!web_contents) {
62     // Requested from a worker, or a no longer existing tab.
63     std::move(callback).Run(ChromeNativeFileSystemPermissionContext::
64                                 SensitiveDirectoryResult::kAbort);
65     return;
66   }
67 
68   ShowNativeFileSystemRestrictedDirectoryDialog(
69       origin, path, handle_type, std::move(callback), web_contents);
70 }
71 
72 // Sentinel used to indicate that no PathService key is specified for a path in
73 // the struct below.
74 constexpr const int kNoBasePathKey = -1;
75 
76 enum BlockType {
77   kBlockAllChildren,
78   kBlockNestedDirectories,
79   kDontBlockChildren
80 };
81 
82 const struct {
83   // base::BasePathKey value (or one of the platform specific extensions to it)
84   // for a path that should be blocked. Specify kNoBasePathKey if |path| should
85   // be used instead.
86   int base_path_key;
87 
88   // Explicit path to block instead of using |base_path_key|. Set to nullptr to
89   // use |base_path_key| on its own. If both |base_path_key| and |path| are set,
90   // |path| is treated relative to the path |base_path_key| resolves to.
91   const base::FilePath::CharType* path;
92 
93   // If this is set to kDontBlockChildren, only the given path and its parents
94   // are blocked. If this is set to kBlockAllChildren, all children of the given
95   // path are blocked as well. Finally if this is set to kBlockNestedDirectories
96   // access is allowed to individual files in the directory, but nested
97   // directories are still blocked.
98   // The BlockType of the nearest ancestor of a path to check is what ultimately
99   // determines if a path is blocked or not. If a blocked path is a descendent
100   // of another blocked path, then it may override the child-blocking policy of
101   // its ancestor. For example, if /home blocks all children, but
102   // /home/downloads does not, then /home/downloads/file.ext will *not* be
103   // blocked.
104   BlockType type;
105 } kBlockedPaths[] = {
106     // Don't allow users to share their entire home directory, entire desktop or
107     // entire documents folder, but do allow sharing anything inside those
108     // directories not otherwise blocked.
109     {base::DIR_HOME, nullptr, kDontBlockChildren},
110     {base::DIR_USER_DESKTOP, nullptr, kDontBlockChildren},
111     {chrome::DIR_USER_DOCUMENTS, nullptr, kDontBlockChildren},
112     // Similar restrictions for the downloads directory.
113     {chrome::DIR_DEFAULT_DOWNLOADS, nullptr, kDontBlockChildren},
114     {chrome::DIR_DEFAULT_DOWNLOADS_SAFE, nullptr, kDontBlockChildren},
115     // The Chrome installation itself should not be modified by the web.
116     {chrome::DIR_APP, nullptr, kBlockAllChildren},
117     // And neither should the configuration of at least the currently running
118     // Chrome instance (note that this does not take --user-data-dir command
119     // line overrides into account).
120     {chrome::DIR_USER_DATA, nullptr, kBlockAllChildren},
121     // ~/.ssh is pretty sensitive on all platforms, so block access to that.
122     {base::DIR_HOME, FILE_PATH_LITERAL(".ssh"), kBlockAllChildren},
123     // And limit access to ~/.gnupg as well.
124     {base::DIR_HOME, FILE_PATH_LITERAL(".gnupg"), kBlockAllChildren},
125 #if defined(OS_WIN)
126     // Some Windows specific directories to block, basically all apps, the
127     // operating system itself, as well as configuration data for apps.
128     {base::DIR_PROGRAM_FILES, nullptr, kBlockAllChildren},
129     {base::DIR_PROGRAM_FILESX86, nullptr, kBlockAllChildren},
130     {base::DIR_PROGRAM_FILES6432, nullptr, kBlockAllChildren},
131     {base::DIR_WINDOWS, nullptr, kBlockAllChildren},
132     {base::DIR_APP_DATA, nullptr, kBlockAllChildren},
133     {base::DIR_LOCAL_APP_DATA, nullptr, kBlockAllChildren},
134     {base::DIR_COMMON_APP_DATA, nullptr, kBlockAllChildren},
135     // Opening a file from an MTP device, such as a smartphone or a camera, is
136     // implemented by Windows as opening a file in the temporary internet files
137     // directory. To support that, allow opening files in that directory, but
138     // not whole directories.
139     {base::DIR_IE_INTERNET_CACHE, nullptr, kBlockNestedDirectories},
140 #endif
141 #if defined(OS_MAC)
142     // Similar Mac specific blocks.
143     {base::DIR_APP_DATA, nullptr, kBlockAllChildren},
144     {base::DIR_HOME, FILE_PATH_LITERAL("Library"), kBlockAllChildren},
145 #endif
146 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_BSD)
147     // On Linux also block access to devices via /dev, as well as security
148     // sensitive data in /sys and /proc.
149     {kNoBasePathKey, FILE_PATH_LITERAL("/dev"), kBlockAllChildren},
150     {kNoBasePathKey, FILE_PATH_LITERAL("/sys"), kBlockAllChildren},
151     {kNoBasePathKey, FILE_PATH_LITERAL("/proc"), kBlockAllChildren},
152     // And block all of ~/.config, matching the similar restrictions on mac
153     // and windows.
154     {base::DIR_HOME, FILE_PATH_LITERAL(".config"), kBlockAllChildren},
155     // Block ~/.dbus as well, just in case, although there probably isn't much a
156     // website can do with access to that directory and its contents.
157     {base::DIR_HOME, FILE_PATH_LITERAL(".dbus"), kBlockAllChildren},
158 #endif
159     // TODO(https://crbug.com/984641): Refine this list, for example add
160     // XDG_CONFIG_HOME when it is not set ~/.config?
161 };
162 
ShouldBlockAccessToPath(const base::FilePath & check_path,HandleType handle_type)163 bool ShouldBlockAccessToPath(const base::FilePath& check_path,
164                              HandleType handle_type) {
165   DCHECK(!check_path.empty());
166   DCHECK(check_path.IsAbsolute());
167 
168   base::FilePath nearest_ancestor;
169   int nearest_ancestor_path_key = kNoBasePathKey;
170   BlockType nearest_ancestor_block_type = kDontBlockChildren;
171   for (const auto& block : kBlockedPaths) {
172     base::FilePath blocked_path;
173     if (block.base_path_key != kNoBasePathKey) {
174       if (!base::PathService::Get(block.base_path_key, &blocked_path))
175         continue;
176       if (block.path)
177         blocked_path = blocked_path.Append(block.path);
178     } else {
179       DCHECK(block.path);
180       blocked_path = base::FilePath(block.path);
181     }
182 
183     if (check_path == blocked_path || check_path.IsParent(blocked_path)) {
184       VLOG(1) << "Blocking access to " << check_path
185               << " because it is a parent of " << blocked_path << " ("
186               << block.base_path_key << ")";
187       return true;
188     }
189 
190     if (blocked_path.IsParent(check_path) &&
191         (nearest_ancestor.empty() || nearest_ancestor.IsParent(blocked_path))) {
192       nearest_ancestor = blocked_path;
193       nearest_ancestor_path_key = block.base_path_key;
194       nearest_ancestor_block_type = block.type;
195     }
196   }
197 
198   // The path we're checking is not in a potentially blocked directory, or the
199   // nearest ancestor does not block access to its children. Grant access.
200   if (nearest_ancestor.empty() ||
201       nearest_ancestor_block_type == kDontBlockChildren) {
202     return false;
203   }
204 
205   // The path we're checking is a file, and the nearest ancestor only blocks
206   // access to directories. Grant access.
207   if (handle_type == HandleType::kFile &&
208       nearest_ancestor_block_type == kBlockNestedDirectories) {
209     return false;
210   }
211 
212   // The nearest ancestor blocks access to its children, so block access.
213   VLOG(1) << "Blocking access to " << check_path << " because it is inside "
214           << nearest_ancestor << " (" << nearest_ancestor_path_key << ")";
215   return true;
216 }
217 
218 // Returns a callback that calls the passed in |callback| by posting a task to
219 // the current sequenced task runner.
220 template <typename... ResultTypes>
221 base::OnceCallback<void(ResultTypes... results)>
BindResultCallbackToCurrentSequence(base::OnceCallback<void (ResultTypes...results)> callback)222 BindResultCallbackToCurrentSequence(
223     base::OnceCallback<void(ResultTypes... results)> callback) {
224   return base::BindOnce(
225       [](scoped_refptr<base::TaskRunner> task_runner,
226          base::OnceCallback<void(ResultTypes... results)> callback,
227          ResultTypes... results) {
228         task_runner->PostTask(FROM_HERE,
229                               base::BindOnce(std::move(callback), results...));
230       },
231       base::SequencedTaskRunnerHandle::Get(), std::move(callback));
232 }
233 
DoSafeBrowsingCheckOnUIThread(content::GlobalFrameRoutingId frame_id,std::unique_ptr<content::NativeFileSystemWriteItem> item,safe_browsing::CheckDownloadCallback callback)234 void DoSafeBrowsingCheckOnUIThread(
235     content::GlobalFrameRoutingId frame_id,
236     std::unique_ptr<content::NativeFileSystemWriteItem> item,
237     safe_browsing::CheckDownloadCallback callback) {
238   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
239   // Download Protection Service is not supported on Android.
240 #if BUILDFLAG(FULL_SAFE_BROWSING)
241   safe_browsing::SafeBrowsingService* sb_service =
242       g_browser_process->safe_browsing_service();
243   if (!sb_service || !sb_service->download_protection_service() ||
244       !sb_service->download_protection_service()->enabled()) {
245     std::move(callback).Run(safe_browsing::DownloadCheckResult::UNKNOWN);
246     return;
247   }
248 
249   if (!item->browser_context) {
250     content::RenderProcessHost* rph =
251         content::RenderProcessHost::FromID(frame_id.child_id);
252     if (!rph) {
253       std::move(callback).Run(safe_browsing::DownloadCheckResult::UNKNOWN);
254       return;
255     }
256     item->browser_context = rph->GetBrowserContext();
257   }
258 
259   if (!item->web_contents) {
260     content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(frame_id);
261     if (rfh)
262       item->web_contents = content::WebContents::FromRenderFrameHost(rfh);
263   }
264 
265   sb_service->download_protection_service()->CheckNativeFileSystemWrite(
266       std::move(item), std::move(callback));
267 #endif
268 }
269 
270 ChromeNativeFileSystemPermissionContext::AfterWriteCheckResult
InterpretSafeBrowsingResult(safe_browsing::DownloadCheckResult result)271 InterpretSafeBrowsingResult(safe_browsing::DownloadCheckResult result) {
272   using Result = safe_browsing::DownloadCheckResult;
273   switch (result) {
274     // Only allow downloads that are marked as SAFE or UNKNOWN by SafeBrowsing.
275     // All other types are going to be blocked. UNKNOWN could be the result of a
276     // failed safe browsing ping.
277     case Result::UNKNOWN:
278     case Result::SAFE:
279     case Result::WHITELISTED_BY_POLICY:
280       return ChromeNativeFileSystemPermissionContext::AfterWriteCheckResult::
281           kAllow;
282 
283     case Result::DANGEROUS:
284     case Result::UNCOMMON:
285     case Result::DANGEROUS_HOST:
286     case Result::POTENTIALLY_UNWANTED:
287     case Result::BLOCKED_PASSWORD_PROTECTED:
288     case Result::BLOCKED_TOO_LARGE:
289     case Result::BLOCKED_UNSUPPORTED_FILE_TYPE:
290       return ChromeNativeFileSystemPermissionContext::AfterWriteCheckResult::
291           kBlock;
292 
293     // This shouldn't be returned for Native File System write checks.
294     case Result::ASYNC_SCANNING:
295     case Result::SENSITIVE_CONTENT_WARNING:
296     case Result::SENSITIVE_CONTENT_BLOCK:
297     case Result::DEEP_SCANNED_SAFE:
298     case Result::PROMPT_FOR_SCANNING:
299       NOTREACHED();
300       return ChromeNativeFileSystemPermissionContext::AfterWriteCheckResult::
301           kAllow;
302   }
303   NOTREACHED();
304   return ChromeNativeFileSystemPermissionContext::AfterWriteCheckResult::kBlock;
305 }
306 
307 }  // namespace
308 
309 ChromeNativeFileSystemPermissionContext::Grants::Grants() = default;
310 ChromeNativeFileSystemPermissionContext::Grants::~Grants() = default;
311 ChromeNativeFileSystemPermissionContext::Grants::Grants(Grants&&) = default;
312 ChromeNativeFileSystemPermissionContext::Grants&
313 ChromeNativeFileSystemPermissionContext::Grants::operator=(Grants&&) = default;
314 
315 ChromeNativeFileSystemPermissionContext::
ChromeNativeFileSystemPermissionContext(content::BrowserContext * context)316     ChromeNativeFileSystemPermissionContext(content::BrowserContext* context) {
317   DETACH_FROM_SEQUENCE(sequence_checker_);
318   auto* profile = Profile::FromBrowserContext(context);
319   content_settings_ = base::WrapRefCounted(
320       HostContentSettingsMapFactory::GetForProfile(profile));
321 }
322 
323 ChromeNativeFileSystemPermissionContext::
324     ~ChromeNativeFileSystemPermissionContext() = default;
325 
326 ContentSetting
GetWriteGuardContentSetting(const url::Origin & origin)327 ChromeNativeFileSystemPermissionContext::GetWriteGuardContentSetting(
328     const url::Origin& origin) {
329   return content_settings()->GetContentSetting(
330       origin.GetURL(), origin.GetURL(),
331       ContentSettingsType::FILE_SYSTEM_WRITE_GUARD);
332 }
333 
334 ContentSetting
GetReadGuardContentSetting(const url::Origin & origin)335 ChromeNativeFileSystemPermissionContext::GetReadGuardContentSetting(
336     const url::Origin& origin) {
337   return content_settings()->GetContentSetting(
338       origin.GetURL(), origin.GetURL(),
339       ContentSettingsType::FILE_SYSTEM_READ_GUARD);
340 }
341 
CanObtainReadPermission(const url::Origin & origin)342 bool ChromeNativeFileSystemPermissionContext::CanObtainReadPermission(
343     const url::Origin& origin) {
344   return GetReadGuardContentSetting(origin) == CONTENT_SETTING_ASK ||
345          GetReadGuardContentSetting(origin) == CONTENT_SETTING_ALLOW;
346 }
347 
CanObtainWritePermission(const url::Origin & origin)348 bool ChromeNativeFileSystemPermissionContext::CanObtainWritePermission(
349     const url::Origin& origin) {
350   return GetWriteGuardContentSetting(origin) == CONTENT_SETTING_ASK ||
351          GetWriteGuardContentSetting(origin) == CONTENT_SETTING_ALLOW;
352 }
353 
ConfirmSensitiveDirectoryAccess(const url::Origin & origin,PathType path_type,const base::FilePath & path,HandleType handle_type,content::GlobalFrameRoutingId frame_id,base::OnceCallback<void (SensitiveDirectoryResult)> callback)354 void ChromeNativeFileSystemPermissionContext::ConfirmSensitiveDirectoryAccess(
355     const url::Origin& origin,
356     PathType path_type,
357     const base::FilePath& path,
358     HandleType handle_type,
359     content::GlobalFrameRoutingId frame_id,
360     base::OnceCallback<void(SensitiveDirectoryResult)> callback) {
361   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
362 
363   // TODO(https://crbug.com/1009970): Figure out what external paths should be
364   // blocked. We could resolve the external path to a local path, and check for
365   // blocked directories based on that, but that doesn't work well. Instead we
366   // should have a separate Chrome OS only code path to block for example the
367   // root of certain external file systems.
368   if (path_type == PathType::kExternal) {
369     std::move(callback).Run(SensitiveDirectoryResult::kAllowed);
370     return;
371   }
372 
373   base::ThreadPool::PostTaskAndReplyWithResult(
374       FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
375       base::BindOnce(&ShouldBlockAccessToPath, path, handle_type),
376       base::BindOnce(&ChromeNativeFileSystemPermissionContext::
377                          DidConfirmSensitiveDirectoryAccess,
378                      GetWeakPtr(), origin, path, handle_type, frame_id,
379                      std::move(callback)));
380 }
381 
PerformAfterWriteChecks(std::unique_ptr<content::NativeFileSystemWriteItem> item,content::GlobalFrameRoutingId frame_id,base::OnceCallback<void (AfterWriteCheckResult)> callback)382 void ChromeNativeFileSystemPermissionContext::PerformAfterWriteChecks(
383     std::unique_ptr<content::NativeFileSystemWriteItem> item,
384     content::GlobalFrameRoutingId frame_id,
385 
386     base::OnceCallback<void(AfterWriteCheckResult)> callback) {
387   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
388   content::GetUIThreadTaskRunner({})->PostTask(
389       FROM_HERE,
390       base::BindOnce(
391           &DoSafeBrowsingCheckOnUIThread, frame_id, std::move(item),
392           base::BindOnce(
393               [](scoped_refptr<base::TaskRunner> task_runner,
394                  base::OnceCallback<void(AfterWriteCheckResult result)>
395                      callback,
396                  safe_browsing::DownloadCheckResult result) {
397                 task_runner->PostTask(
398                     FROM_HERE,
399                     base::BindOnce(std::move(callback),
400                                    InterpretSafeBrowsingResult(result)));
401               },
402               base::SequencedTaskRunnerHandle::Get(), std::move(callback))));
403 }
404 
405 void ChromeNativeFileSystemPermissionContext::
DidConfirmSensitiveDirectoryAccess(const url::Origin & origin,const base::FilePath & path,HandleType handle_type,content::GlobalFrameRoutingId frame_id,base::OnceCallback<void (SensitiveDirectoryResult)> callback,bool should_block)406     DidConfirmSensitiveDirectoryAccess(
407         const url::Origin& origin,
408         const base::FilePath& path,
409         HandleType handle_type,
410         content::GlobalFrameRoutingId frame_id,
411         base::OnceCallback<void(SensitiveDirectoryResult)> callback,
412         bool should_block) {
413   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
414   if (!should_block) {
415     std::move(callback).Run(SensitiveDirectoryResult::kAllowed);
416     return;
417   }
418 
419   auto result_callback =
420       BindResultCallbackToCurrentSequence(std::move(callback));
421 
422   content::GetUIThreadTaskRunner({})->PostTask(
423       FROM_HERE,
424       base::BindOnce(&ShowNativeFileSystemRestrictedDirectoryDialogOnUIThread,
425                      frame_id, origin, path, handle_type,
426                      std::move(result_callback)));
427 }
428 
OriginHasReadAccess(const url::Origin & origin)429 bool ChromeNativeFileSystemPermissionContext::OriginHasReadAccess(
430     const url::Origin& origin) {
431   NOTREACHED();
432   return false;
433 }
434 
OriginHasWriteAccess(const url::Origin & origin)435 bool ChromeNativeFileSystemPermissionContext::OriginHasWriteAccess(
436     const url::Origin& origin) {
437   NOTREACHED();
438   return false;
439 }
440 
SetLastPickedDirectory(const url::Origin & origin,const base::FilePath & path)441 void ChromeNativeFileSystemPermissionContext::SetLastPickedDirectory(
442     const url::Origin& origin,
443     const base::FilePath& path) {
444   base::Value dict(base::Value::Type::DICTIONARY);
445   dict.SetKey(kLastPickedDirectoryKey, util::FilePathToValue(path));
446 
447   content_settings_->SetWebsiteSettingDefaultScope(
448       origin.GetURL(), origin.GetURL(),
449       ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY,
450       base::Value::ToUniquePtrValue(std::move(dict)));
451 }
452 
GetLastPickedDirectory(const url::Origin & origin)453 base::FilePath ChromeNativeFileSystemPermissionContext::GetLastPickedDirectory(
454     const url::Origin& origin) {
455   std::unique_ptr<base::Value> value = content_settings()->GetWebsiteSetting(
456       origin.GetURL(), origin.GetURL(),
457       ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, /*info=*/nullptr);
458   if (!value)
459     return base::FilePath();
460 
461   return util::ValueToFilePath(value->FindKey(kLastPickedDirectoryKey))
462       .value_or(base::FilePath());
463 }
464 
GetDefaultDirectory()465 base::FilePath ChromeNativeFileSystemPermissionContext::GetDefaultDirectory() {
466   base::FilePath default_path;
467   // On failure, |default_path| will remain empty.
468   base::PathService::Get(chrome::DIR_USER_DOCUMENTS, &default_path);
469   return default_path;
470 }
471