summaryrefslogtreecommitdiffstats
path: root/chrome/browser/file_system
diff options
context:
space:
mode:
authorkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-09 02:42:24 +0000
committerkinuko@chromium.org <kinuko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-09 02:42:24 +0000
commited7be3bf80811a41e967177aa40cd78dca96c7e0 (patch)
tree04ebf3bb82905ff525c8b507a809b37c8fcab191 /chrome/browser/file_system
parentba4f1137a6f051c668857f850b00373a7adc0253 (diff)
downloadchromium_src-ed7be3bf80811a41e967177aa40cd78dca96c7e0.zip
chromium_src-ed7be3bf80811a41e967177aa40cd78dca96c7e0.tar.gz
chromium_src-ed7be3bf80811a41e967177aa40cd78dca96c7e0.tar.bz2
Check restricted names/chars for FileSystem API
http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions (We do the same check in the renderer, but would be better to do this in the browser (too)) Also addressed the memory leak issue. BUG=56433,58474 TEST=FileSystemHostContextTest.IsRestrictedName Review URL: http://codereview.chromium.org/3525018 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@62063 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/file_system')
-rw-r--r--chrome/browser/file_system/file_system_dispatcher_host.cc17
-rw-r--r--chrome/browser/file_system/file_system_dispatcher_host.h6
-rw-r--r--chrome/browser/file_system/file_system_host_context.cc50
-rw-r--r--chrome/browser/file_system/file_system_host_context.h8
-rw-r--r--chrome/browser/file_system/file_system_host_context_unittest.cc140
5 files changed, 198 insertions, 23 deletions
diff --git a/chrome/browser/file_system/file_system_dispatcher_host.cc b/chrome/browser/file_system/file_system_dispatcher_host.cc
index 28ffe03..503e85b 100644
--- a/chrome/browser/file_system/file_system_dispatcher_host.cc
+++ b/chrome/browser/file_system/file_system_dispatcher_host.cc
@@ -161,6 +161,7 @@ void FileSystemDispatcherHost::OnMove(
int request_id, const FilePath& src_path, const FilePath& dest_path) {
if (!CheckValidFileSystemPath(src_path, request_id) ||
!CheckValidFileSystemPath(dest_path, request_id) ||
+ !CheckIfFilePathIsSafe(dest_path, request_id) ||
!CheckQuotaForPath(dest_path, FileSystemQuota::kUnknownSize, request_id))
return;
@@ -171,6 +172,7 @@ void FileSystemDispatcherHost::OnCopy(
int request_id, const FilePath& src_path, const FilePath& dest_path) {
if (!CheckValidFileSystemPath(src_path, request_id) ||
!CheckValidFileSystemPath(dest_path, request_id) ||
+ !CheckIfFilePathIsSafe(dest_path, request_id) ||
!CheckQuotaForPath(dest_path, FileSystemQuota::kUnknownSize, request_id))
return;
@@ -195,6 +197,7 @@ void FileSystemDispatcherHost::OnCreate(
int request_id, const FilePath& path, bool exclusive,
bool is_directory, bool recursive) {
if (!CheckValidFileSystemPath(path, request_id) ||
+ !CheckIfFilePathIsSafe(path, request_id) ||
!CheckQuotaForPath(path, 0L, request_id))
return;
if (is_directory)
@@ -226,6 +229,7 @@ void FileSystemDispatcherHost::OnWrite(
const GURL& blob_url,
int64 offset) {
if (!CheckValidFileSystemPath(path, request_id) ||
+ !CheckIfFilePathIsSafe(path, request_id) ||
!CheckQuotaForPath(path, FileSystemQuota::kUnknownSize, request_id))
return;
GetNewOperation(request_id)->Write(
@@ -246,7 +250,8 @@ void FileSystemDispatcherHost::OnTouchFile(
const FilePath& path,
const base::Time& last_access_time,
const base::Time& last_modified_time) {
- if (!CheckValidFileSystemPath(path, request_id))
+ if (!CheckValidFileSystemPath(path, request_id) ||
+ !CheckIfFilePathIsSafe(path, request_id))
return;
GetNewOperation(request_id)->TouchFile(
path, last_access_time, last_modified_time);
@@ -307,6 +312,16 @@ bool FileSystemDispatcherHost::CheckQuotaForPath(
return true;
}
+bool FileSystemDispatcherHost::CheckIfFilePathIsSafe(
+ const FilePath& path, int request_id) {
+ if (context_->IsRestrictedFileName(path.BaseName())) {
+ Send(new ViewMsg_FileSystem_DidFail(
+ request_id, base::PLATFORM_FILE_ERROR_SECURITY));
+ return false;
+ }
+ return true;
+}
+
fileapi::FileSystemOperation* FileSystemDispatcherHost::GetNewOperation(
int request_id) {
BrowserFileSystemCallbackDispatcher* dispatcher =
diff --git a/chrome/browser/file_system/file_system_dispatcher_host.h b/chrome/browser/file_system/file_system_dispatcher_host.h
index 33e6011..9387ba8 100644
--- a/chrome/browser/file_system/file_system_dispatcher_host.h
+++ b/chrome/browser/file_system/file_system_dispatcher_host.h
@@ -91,6 +91,12 @@ class FileSystemDispatcherHost
// and returns false.
bool CheckQuotaForPath(const FilePath& path, int64 growth, int request_id);
+ // Checks if a given |path| does not contain any restricted names/chars
+ // for new files. Returns true if the given |path| is safe.
+ // Otherwise it sends back a security error code to the dispatcher and
+ // returns false.
+ bool CheckIfFilePathIsSafe(const FilePath& path, int request_id);
+
// The sender to be used for sending out IPC messages.
IPC::Message::Sender* message_sender_;
diff --git a/chrome/browser/file_system/file_system_host_context.cc b/chrome/browser/file_system/file_system_host_context.cc
index e7b1f5c..37c2b8c 100644
--- a/chrome/browser/file_system/file_system_host_context.cc
+++ b/chrome/browser/file_system/file_system_host_context.cc
@@ -30,6 +30,19 @@ const char FileSystemHostContext::kTemporaryName[] = "Temporary";
namespace {
+// Restricted names.
+// http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions
+static const char* const kRestrictedNames[] = {
+ "con", "prn", "aux", "nul",
+ "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
+ "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
+};
+
+// Restricted chars.
+static const FilePath::CharType kRestrictedChars[] = {
+ '/', '\\', '<', '>', ':', '?', '*', '"', '|',
+};
+
static inline std::string FilePathStringToASCII(
const FilePath::StringType& path_string) {
#if defined(OS_WIN)
@@ -46,9 +59,9 @@ FileSystemHostContext::FileSystemHostContext(
: base_path_(data_path.Append(kFileSystemDirectory)),
is_incognito_(is_incognito),
quota_manager_(new fileapi::FileSystemQuota()) {
- const CommandLine& command_line = *CommandLine::ForCurrentProcess();
- allow_file_access_from_files_ = command_line.HasSwitch(
- switches::kAllowFileAccessFromFiles);
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ allow_file_access_from_files_ = command_line.HasSwitch(
+ switches::kAllowFileAccessFromFiles);
}
bool FileSystemHostContext::GetFileSystemRootPath(
@@ -104,6 +117,7 @@ bool FileSystemHostContext::CheckValidFileSystemPath(
// must be kPersistent or kTemporary.
if (!IsStringASCII(components[1]))
return false;
+
std::string ascii_type_component = FilePathStringToASCII(components[1]);
if (ascii_type_component != kPersistentName &&
ascii_type_component != kTemporaryName)
@@ -149,6 +163,36 @@ bool FileSystemHostContext::IsAllowedScheme(const GURL& url) const {
(url.SchemeIsFile() && allow_file_access_from_files_);
}
+bool FileSystemHostContext::IsRestrictedFileName(
+ const FilePath& filename) const {
+ if (filename.value().size() == 0)
+ return false;
+
+ if (IsWhitespace(filename.value()[filename.value().size() - 1]) ||
+ filename.value()[filename.value().size() - 1] == '.')
+ return true;
+
+ std::string filename_lower = StringToLowerASCII(
+ FilePathStringToASCII(filename.value()));
+
+ for (size_t i = 0; i < arraysize(kRestrictedNames); ++i) {
+ // Exact match.
+ if (filename_lower == kRestrictedNames[i])
+ return true;
+ // Starts with "RESTRICTED_NAME.".
+ if (filename_lower.find(std::string(kRestrictedNames[i]) + ".") == 0)
+ return true;
+ }
+
+ for (size_t i = 0; i < arraysize(kRestrictedChars); ++i) {
+ if (filename.value().find(kRestrictedChars[i]) !=
+ FilePath::StringType::npos)
+ return true;
+ }
+
+ return false;
+}
+
bool FileSystemHostContext::CheckOriginQuota(const GURL& url, int64 growth) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// If allow-file-access-from-files flag is explicitly given and the scheme
diff --git a/chrome/browser/file_system/file_system_host_context.h b/chrome/browser/file_system/file_system_host_context.h
index a1141c0..be5b5bd 100644
--- a/chrome/browser/file_system/file_system_host_context.h
+++ b/chrome/browser/file_system/file_system_host_context.h
@@ -6,7 +6,6 @@
#define CHROME_BROWSER_FILE_SYSTEM_FILE_SYSTEM_HOST_CONTEXT_H_
#include "base/file_path.h"
-#include "base/gtest_prod_util.h"
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "chrome/browser/chrome_thread.h"
@@ -33,7 +32,7 @@ class FileSystemHostContext
FilePath* root_path,
std::string* name) const;
- // Check if the given |path| is in the FileSystem base directory.
+ // Checks if a given |path| is in the FileSystem base directory.
bool CheckValidFileSystemPath(const FilePath& path) const;
// Retrieves the origin URL for the given |path| and populates
@@ -45,6 +44,9 @@ class FileSystemHostContext
// filesystem.
bool IsAllowedScheme(const GURL& url) const;
+ // Checks if a given |filename| contains any restricted names/chars in it.
+ bool IsRestrictedFileName(const FilePath& filename) const;
+
// Quota related methods.
bool CheckOriginQuota(const GURL& url, int64 growth);
void SetOriginQuotaUnlimited(const GURL& url);
@@ -66,8 +68,6 @@ class FileSystemHostContext
scoped_ptr<fileapi::FileSystemQuota> quota_manager_;
- FRIEND_TEST_ALL_PREFIXES(FileSystemHostContextTest, GetOriginFromPath);
-
DISALLOW_IMPLICIT_CONSTRUCTORS(FileSystemHostContext);
};
diff --git a/chrome/browser/file_system/file_system_host_context_unittest.cc b/chrome/browser/file_system/file_system_host_context_unittest.cc
index f91bb04..2d58588 100644
--- a/chrome/browser/file_system/file_system_host_context_unittest.cc
+++ b/chrome/browser/file_system/file_system_host_context_unittest.cc
@@ -5,7 +5,7 @@
#include "base/basictypes.h"
#include "base/file_path.h"
#include "base/scoped_ptr.h"
-#include "chrome/browser/file_system/file_system_host_context.h"
+#include "base/scoped_temp_dir.h"
#include "chrome/browser/file_system/file_system_host_context.h"
#include "googleurl/src/gurl.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -56,16 +56,108 @@ const struct CheckValidPathTest {
{ FILE_PATH_LITERAL("a/b/../c/.."), false, },
};
+const struct IsRestrictedNameTest {
+ FilePath::StringType name;
+ bool expected_dangerous;
+} kIsRestrictedNameTestCases[] = {
+ // Name that has restricted names in it.
+ { FILE_PATH_LITERAL("con"), true, },
+ { FILE_PATH_LITERAL("Con.txt"), true, },
+ { FILE_PATH_LITERAL("Prn.png"), true, },
+ { FILE_PATH_LITERAL("AUX"), true, },
+ { FILE_PATH_LITERAL("nUl."), true, },
+ { FILE_PATH_LITERAL("coM1"), true, },
+ { FILE_PATH_LITERAL("COM3.com"), true, },
+ { FILE_PATH_LITERAL("cOM7"), true, },
+ { FILE_PATH_LITERAL("com9"), true, },
+ { FILE_PATH_LITERAL("lpT1"), true, },
+ { FILE_PATH_LITERAL("LPT4.com"), true, },
+ { FILE_PATH_LITERAL("lPT8"), true, },
+ { FILE_PATH_LITERAL("lPT9"), true, },
+ // Similar but safe cases.
+ { FILE_PATH_LITERAL("con3"), false, },
+ { FILE_PATH_LITERAL("PrnImage.png"), false, },
+ { FILE_PATH_LITERAL("AUXX"), false, },
+ { FILE_PATH_LITERAL("NULL"), false, },
+ { FILE_PATH_LITERAL("coM0"), false, },
+ { FILE_PATH_LITERAL("COM.com"), false, },
+ { FILE_PATH_LITERAL("lpT0"), false, },
+ { FILE_PATH_LITERAL("LPT.com"), false, },
+
+ // Ends with period or whitespace.
+ { FILE_PATH_LITERAL("b "), true, },
+ { FILE_PATH_LITERAL("b\t"), true, },
+ { FILE_PATH_LITERAL("b\n"), true, },
+ { FILE_PATH_LITERAL("b\r\n"), true, },
+ { FILE_PATH_LITERAL("b."), true, },
+ { FILE_PATH_LITERAL("b.."), true, },
+ // Similar but safe cases.
+ { FILE_PATH_LITERAL("b c"), false, },
+ { FILE_PATH_LITERAL("b\tc"), false, },
+ { FILE_PATH_LITERAL("b\nc"), false, },
+ { FILE_PATH_LITERAL("b\r\nc"), false, },
+ { FILE_PATH_LITERAL("b c d e f"), false, },
+ { FILE_PATH_LITERAL("b.c"), false, },
+ { FILE_PATH_LITERAL("b..c"), false, },
+
+ // Name that has restricted chars in it.
+ { FILE_PATH_LITERAL("a\\b"), true, },
+ { FILE_PATH_LITERAL("a/b"), true, },
+ { FILE_PATH_LITERAL("a<b"), true, },
+ { FILE_PATH_LITERAL("a>b"), true, },
+ { FILE_PATH_LITERAL("a:b"), true, },
+ { FILE_PATH_LITERAL("a?b"), true, },
+ { FILE_PATH_LITERAL("a|b"), true, },
+ { FILE_PATH_LITERAL("ab\\"), true, },
+ { FILE_PATH_LITERAL("ab/.txt"), true, },
+ { FILE_PATH_LITERAL("ab<.txt"), true, },
+ { FILE_PATH_LITERAL("ab>.txt"), true, },
+ { FILE_PATH_LITERAL("ab:.txt"), true, },
+ { FILE_PATH_LITERAL("ab?.txt"), true, },
+ { FILE_PATH_LITERAL("ab|.txt"), true, },
+ { FILE_PATH_LITERAL("\\ab"), true, },
+ { FILE_PATH_LITERAL("/ab"), true, },
+ { FILE_PATH_LITERAL("<ab"), true, },
+ { FILE_PATH_LITERAL(">ab"), true, },
+ { FILE_PATH_LITERAL(":ab"), true, },
+ { FILE_PATH_LITERAL("?ab"), true, },
+ { FILE_PATH_LITERAL("|ab"), true, },
+};
+
} // namespace
-TEST(FileSystemHostContextTest, GetRootPath) {
+class FileSystemHostContextTest : public testing::Test {
+ public:
+ FileSystemHostContextTest()
+ : io_thread_(ChromeThread::IO, &message_loop_),
+ data_path_(kTestDataPath) {
+ }
+
+ void TearDown() {
+ message_loop_.RunAllPending();
+ }
+
+ ~FileSystemHostContextTest() {
+ message_loop_.RunAllPending();
+ }
+
+ scoped_refptr<FileSystemHostContext> GetHostContext(bool incognite) {
+ return new FileSystemHostContext(data_path_, incognite);
+ }
+
+ private:
+ MessageLoopForIO message_loop_;
+ BrowserThread io_thread_;
+ FilePath data_path_;
+};
+
+TEST_F(FileSystemHostContextTest, GetRootPath) {
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "RootPath #" << i << " "
<< kRootPathTestCases[i].expected_path);
- scoped_refptr<FileSystemHostContext> context(
- new FileSystemHostContext(FilePath(kTestDataPath),
- kRootPathTestCases[i].off_the_record));
+ scoped_refptr<FileSystemHostContext> context = GetHostContext(
+ kRootPathTestCases[i].off_the_record);
FilePath root_path;
bool result = context->GetFileSystemRootPath(
@@ -81,23 +173,41 @@ TEST(FileSystemHostContextTest, GetRootPath) {
}
}
-TEST(FileSystemHostContextTest, CheckValidPath) {
+TEST_F(FileSystemHostContextTest, CheckValidPath) {
+ scoped_refptr<FileSystemHostContext> context = GetHostContext(false);
+ FilePath root_path;
+ EXPECT_TRUE(context->GetFileSystemRootPath(
+ GURL("http://foo.com/"), fileapi::kFileSystemTypePersistent,
+ &root_path, NULL));
+
+ // The root path must be valid, but upper directories or directories
+ // that are not in our temporary or persistent directory must be
+ // evaluated invalid.
+ EXPECT_TRUE(context->CheckValidFileSystemPath(root_path));
+ EXPECT_FALSE(context->CheckValidFileSystemPath(root_path.DirName()));
+ EXPECT_FALSE(context->CheckValidFileSystemPath(
+ root_path.DirName().DirName()));
+ EXPECT_FALSE(context->CheckValidFileSystemPath(
+ root_path.DirName().AppendASCII("ArbitraryName")));
+
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kCheckValidPathTestCases); ++i) {
SCOPED_TRACE(testing::Message() << "CheckValidPath #" << i << " "
<< kCheckValidPathTestCases[i].path);
-
- scoped_refptr<FileSystemHostContext> context(
- new FileSystemHostContext(FilePath(kTestDataPath), false));
-
- FilePath root_path;
- EXPECT_TRUE(context->GetFileSystemRootPath(
- GURL("http://foo.com/"), fileapi::kFileSystemTypePersistent,
- &root_path, NULL));
FilePath path(kCheckValidPathTestCases[i].path);
if (!path.IsAbsolute())
path = root_path.Append(path);
-
EXPECT_EQ(kCheckValidPathTestCases[i].expected_valid,
context->CheckValidFileSystemPath(path));
}
}
+
+TEST_F(FileSystemHostContextTest, IsRestrictedName) {
+ scoped_refptr<FileSystemHostContext> context = GetHostContext(false);
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kIsRestrictedNameTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "IsRestrictedName #" << i << " "
+ << kIsRestrictedNameTestCases[i].name);
+ FilePath name(kIsRestrictedNameTestCases[i].name);
+ EXPECT_EQ(kIsRestrictedNameTestCases[i].expected_dangerous,
+ context->IsRestrictedFileName(name));
+ }
+}