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