Commit ce678e79 authored by Victor Costan's avatar Victor Costan Committed by Commit Bot

sqlite: Create all databases with restrictive permissions.

Chrome's SQLite currently uses permissive (0644) POSIX access controls
for newly created databases, and //sql offers a method for opting into
restrictive (0600) permissions. This method is only used by the login
database. However, all Chrome databases are likely to have private data.
For example, the cookies database may contain long-lived OAuth tokens,
and can be just as valuable as the login database. The same argument
applies for the DOMStorage database.

This CL configures SQLite to use restrictive permissions by default, and
removes the method for opting into the restrictive permissions.

Change-Id: I5f0ce9e7f038081fad515cfc30c45ccccf7ff1b6
Reviewed-on: https://chromium-review.googlesource.com/1146295Reviewed-by: default avatarVaclav Brozek <vabr@chromium.org>
Reviewed-by: default avatarChris Mumford <cmumford@chromium.org>
Reviewed-by: default avatarRobert Sesek <rsesek@chromium.org>
Commit-Queue: Victor Costan <pwnall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577476}
parent e7c91b4f
...@@ -533,7 +533,6 @@ bool LoginDatabase::Init() { ...@@ -533,7 +533,6 @@ bool LoginDatabase::Init() {
db_.set_page_size(2048); db_.set_page_size(2048);
db_.set_cache_size(32); db_.set_cache_size(32);
db_.set_exclusive_locking(); db_.set_exclusive_locking();
db_.set_restrict_to_user();
db_.set_histogram_tag("Passwords"); db_.set_histogram_tag("Passwords");
if (!db_.Open(db_path_)) { if (!db_.Open(db_path_)) {
......
...@@ -203,6 +203,21 @@ void Connection::ResetErrorExpecter() { ...@@ -203,6 +203,21 @@ void Connection::ResetErrorExpecter() {
current_expecter_cb_ = nullptr; current_expecter_cb_ = nullptr;
} }
// static
base::FilePath Connection::JournalPath(const base::FilePath& db_path) {
return base::FilePath(db_path.value() + FILE_PATH_LITERAL("-journal"));
}
// static
base::FilePath Connection::WriteAheadLogPath(const base::FilePath& db_path) {
return base::FilePath(db_path.value() + FILE_PATH_LITERAL("-wal"));
}
// static
base::FilePath Connection::SharedMemoryFilePath(const base::FilePath& db_path) {
return base::FilePath(db_path.value() + FILE_PATH_LITERAL("-shm"));
}
Connection::StatementRef::StatementRef(Connection* connection, Connection::StatementRef::StatementRef(Connection* connection,
sqlite3_stmt* stmt, sqlite3_stmt* stmt,
bool was_valid) bool was_valid)
...@@ -245,7 +260,6 @@ Connection::Connection() ...@@ -245,7 +260,6 @@ Connection::Connection()
page_size_(0), page_size_(0),
cache_size_(0), cache_size_(0),
exclusive_locking_(false), exclusive_locking_(false),
restrict_to_user_(false),
transaction_nesting_(0), transaction_nesting_(0),
needs_rollback_(false), needs_rollback_(false),
in_memory_(false), in_memory_(false),
...@@ -562,8 +576,7 @@ bool Connection::RegisterIntentToUpload() const { ...@@ -562,8 +576,7 @@ bool Connection::RegisterIntentToUpload() const {
// Put the collection of diagnostic data next to the databases. In most // Put the collection of diagnostic data next to the databases. In most
// cases, this is the profile directory, but safe-browsing stores a Cookies // cases, this is the profile directory, but safe-browsing stores a Cookies
// file in the directory above the profile directory. // file in the directory above the profile directory.
base::FilePath breadcrumb_path( base::FilePath breadcrumb_path = db_path.DirName().AppendASCII("sqlite-diag");
db_path.DirName().Append(FILE_PATH_LITERAL("sqlite-diag")));
// Lock against multiple updates to the diagnostics file. This code should // Lock against multiple updates to the diagnostics file. This code should
// seldom be called in the first place, and when called it should seldom be // seldom be called in the first place, and when called it should seldom be
...@@ -1137,8 +1150,8 @@ void Connection::Poison() { ...@@ -1137,8 +1150,8 @@ void Connection::Poison() {
bool Connection::Delete(const base::FilePath& path) { bool Connection::Delete(const base::FilePath& path) {
base::AssertBlockingAllowed(); base::AssertBlockingAllowed();
base::FilePath journal_path(path.value() + FILE_PATH_LITERAL("-journal")); base::FilePath journal_path = Connection::JournalPath(path);
base::FilePath wal_path(path.value() + FILE_PATH_LITERAL("-wal")); base::FilePath wal_path = Connection::WriteAheadLogPath(path);
std::string journal_str = AsUTF8ForSQL(journal_path); std::string journal_str = AsUTF8ForSQL(journal_path);
std::string wal_str = AsUTF8ForSQL(wal_path); std::string wal_str = AsUTF8ForSQL(wal_path);
...@@ -1162,16 +1175,13 @@ bool Connection::Delete(const base::FilePath& path) { ...@@ -1162,16 +1175,13 @@ bool Connection::Delete(const base::FilePath& path) {
vfs->xDelete(vfs, path_str.c_str(), 0); vfs->xDelete(vfs, path_str.c_str(), 0);
int journal_exists = 0; int journal_exists = 0;
vfs->xAccess(vfs, journal_str.c_str(), SQLITE_ACCESS_EXISTS, vfs->xAccess(vfs, journal_str.c_str(), SQLITE_ACCESS_EXISTS, &journal_exists);
&journal_exists);
int wal_exists = 0; int wal_exists = 0;
vfs->xAccess(vfs, wal_str.c_str(), SQLITE_ACCESS_EXISTS, vfs->xAccess(vfs, wal_str.c_str(), SQLITE_ACCESS_EXISTS, &wal_exists);
&wal_exists);
int path_exists = 0; int path_exists = 0;
vfs->xAccess(vfs, path_str.c_str(), SQLITE_ACCESS_EXISTS, vfs->xAccess(vfs, path_str.c_str(), SQLITE_ACCESS_EXISTS, &path_exists);
&path_exists);
return !journal_exists && !wal_exists && !path_exists; return !journal_exists && !wal_exists && !path_exists;
} }
...@@ -1631,30 +1641,6 @@ bool Connection::OpenInternal(const std::string& file_name, ...@@ -1631,30 +1641,6 @@ bool Connection::OpenInternal(const std::string& file_name,
return false; return false;
} }
// TODO(shess): OS_WIN support?
#if defined(OS_POSIX)
if (restrict_to_user_) {
DCHECK_NE(file_name, std::string(":memory"));
base::FilePath file_path(file_name);
int mode = 0;
// TODO(shess): Arguably, failure to retrieve and change
// permissions should be fatal if the file exists.
if (base::GetPosixFilePermissions(file_path, &mode)) {
mode &= base::FILE_PERMISSION_USER_MASK;
base::SetPosixFilePermissions(file_path, mode);
// SQLite sets the permissions on these files from the main
// database on create. Set them here in case they already exist
// at this point. Failure to set these permissions should not
// be fatal unless the file doesn't exist.
base::FilePath journal_path(file_name + FILE_PATH_LITERAL("-journal"));
base::FilePath wal_path(file_name + FILE_PATH_LITERAL("-wal"));
base::SetPosixFilePermissions(journal_path, mode);
base::SetPosixFilePermissions(wal_path, mode);
}
}
#endif // defined(OS_POSIX)
// Enable extended result codes to provide more color on I/O errors. // Enable extended result codes to provide more color on I/O errors.
// Not having extended result codes is not a fatal problem, as // Not having extended result codes is not a fatal problem, as
// Chromium code does not attempt to handle I/O errors anyhow. The // Chromium code does not attempt to handle I/O errors anyhow. The
......
...@@ -108,12 +108,6 @@ class SQL_EXPORT Connection { ...@@ -108,12 +108,6 @@ class SQL_EXPORT Connection {
// This must be called before Open() to have an effect. // This must be called before Open() to have an effect.
void set_exclusive_locking() { exclusive_locking_ = true; } void set_exclusive_locking() { exclusive_locking_ = true; }
// Call to cause Open() to restrict access permissions of the
// database file to only the owner.
//
// This is only supported on OS_POSIX and is a noop on other platforms.
void set_restrict_to_user() { restrict_to_user_ = true; }
// Call to use alternative status-tracking for mmap. Usually this is tracked // Call to use alternative status-tracking for mmap. Usually this is tracked
// in the meta table, but some databases have no meta table. // in the meta table, but some databases have no meta table.
// TODO(shess): Maybe just have all databases use the alt option? // TODO(shess): Maybe just have all databases use the alt option?
...@@ -469,6 +463,39 @@ class SQL_EXPORT Connection { ...@@ -469,6 +463,39 @@ class SQL_EXPORT Connection {
clock_ = std::move(clock); clock_ = std::move(clock);
} }
// Computes the path of a database's rollback journal.
//
// The journal file is created at the beginning of the database's first
// transaction. The file may be removed and re-created between transactions,
// depending on whether the database is opened in exclusive mode, and on
// configuration options. The journal file does not exist when the database
// operates in WAL mode.
//
// This is intended for internal use and tests. To preserve our ability to
// iterate on our SQLite configuration, features must avoid relying on
// the existence of specific files.
static base::FilePath JournalPath(const base::FilePath& db_path);
// Computes the path of a database's write-ahead log (WAL).
//
// The WAL file exists while a database is opened in WAL mode.
//
// This is intended for internal use and tests. To preserve our ability to
// iterate on our SQLite configuration, features must avoid relying on
// the existence of specific files.
static base::FilePath WriteAheadLogPath(const base::FilePath& db_path);
// Computes the path of a database's shared memory (SHM) file.
//
// The SHM file is used to coordinate between multiple processes using the
// same database in WAL mode. Thus, this file only exists for databases using
// WAL and not opened in exclusive mode.
//
// This is intended for internal use and tests. To preserve our ability to
// iterate on our SQLite configuration, features must avoid relying on
// the existence of specific files.
static base::FilePath SharedMemoryFilePath(const base::FilePath& db_path);
private: private:
// For recovery module. // For recovery module.
friend class Recovery; friend class Recovery;
...@@ -718,7 +745,6 @@ class SQL_EXPORT Connection { ...@@ -718,7 +745,6 @@ class SQL_EXPORT Connection {
int page_size_; int page_size_;
int cache_size_; int cache_size_;
bool exclusive_locking_; bool exclusive_locking_;
bool restrict_to_user_;
// Holds references to all cached statements so they remain active. // Holds references to all cached statements so they remain active.
// //
......
...@@ -867,85 +867,74 @@ TEST_F(SQLConnectionTest, SetTempDirForSQL) { ...@@ -867,85 +867,74 @@ TEST_F(SQLConnectionTest, SetTempDirForSQL) {
} }
#endif // defined(OS_ANDROID) #endif // defined(OS_ANDROID)
TEST_F(SQLConnectionTest, Delete) { TEST_F(SQLConnectionTest, DeleteNonWal) {
EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
db().Close(); db().Close();
// Should have both a main database file and a journal file because // Should have both a main database file and a journal file because
// of journal_mode TRUNCATE. // of journal_mode TRUNCATE.
base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal")); base::FilePath journal_path = sql::Connection::JournalPath(db_path());
ASSERT_TRUE(GetPathExists(db_path())); ASSERT_TRUE(GetPathExists(db_path()));
ASSERT_TRUE(GetPathExists(journal)); ASSERT_TRUE(GetPathExists(journal_path));
sql::Connection::Delete(db_path()); sql::Connection::Delete(db_path());
EXPECT_FALSE(GetPathExists(db_path())); EXPECT_FALSE(GetPathExists(db_path()));
EXPECT_FALSE(GetPathExists(journal)); EXPECT_FALSE(GetPathExists(journal_path));
} }
// This test manually sets on disk permissions, these don't exist on Fuchsia. #if defined(OS_POSIX) // This test operates on POSIX file permissions.
#if defined(OS_POSIX) TEST_F(SQLConnectionTest, PosixFilePermissions) {
// Test that set_restrict_to_user() trims database permissions so that
// only the owner (and root) can read.
TEST_F(SQLConnectionTest, UserPermission) {
// If the bots all had a restrictive umask setting such that
// databases are always created with only the owner able to read
// them, then the code could break without breaking the tests.
// Temporarily provide a more permissive umask.
db().Close(); db().Close();
sql::Connection::Delete(db_path()); sql::Connection::Delete(db_path());
ASSERT_FALSE(GetPathExists(db_path())); ASSERT_FALSE(GetPathExists(db_path()));
// If the bots all had a restrictive umask setting such that databases are
// always created with only the owner able to read them, then the code could
// break without breaking the tests. Temporarily provide a more permissive
// umask.
ScopedUmaskSetter permissive_umask(S_IWGRP | S_IWOTH); ScopedUmaskSetter permissive_umask(S_IWGRP | S_IWOTH);
ASSERT_TRUE(db().Open(db_path())); ASSERT_TRUE(db().Open(db_path()));
// Cause the journal file to be created. If the default // Cause the journal file to be created. If the default journal_mode is
// journal_mode is changed back to DELETE, then parts of this test // changed back to DELETE, this test will need to be updated.
// will need to be updated.
EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); EXPECT_TRUE(db().Execute("CREATE TABLE x (x)"));
base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal"));
int mode; int mode;
// Given a permissive umask, the database is created with permissive
// read access for the database and journal.
ASSERT_TRUE(GetPathExists(db_path())); ASSERT_TRUE(GetPathExists(db_path()));
ASSERT_TRUE(GetPathExists(journal));
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode)); EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
ASSERT_NE((mode & base::FILE_PERMISSION_USER_MASK), mode); ASSERT_EQ(mode, 0600);
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(journal, &mode));
ASSERT_NE((mode & base::FILE_PERMISSION_USER_MASK), mode);
// Re-open with restricted permissions and verify that the modes {
// changed for both the main database and the journal. base::FilePath journal_path = sql::Connection::JournalPath(db_path());
db().Close(); DLOG(ERROR) << "journal_path: " << journal_path;
db().set_restrict_to_user(); ASSERT_TRUE(GetPathExists(journal_path));
ASSERT_TRUE(db().Open(db_path())); EXPECT_TRUE(base::GetPosixFilePermissions(journal_path, &mode));
ASSERT_TRUE(GetPathExists(db_path())); ASSERT_EQ(mode, 0600);
ASSERT_TRUE(GetPathExists(journal)); }
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode);
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(journal, &mode));
ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode);
// Delete and re-create the database, the restriction should still apply. // Reopen the database and turn on WAL mode.
db().Close(); db().Close();
sql::Connection::Delete(db_path()); sql::Connection::Delete(db_path());
ASSERT_FALSE(GetPathExists(db_path()));
ASSERT_TRUE(db().Open(db_path())); ASSERT_TRUE(db().Open(db_path()));
ASSERT_TRUE(GetPathExists(db_path())); ASSERT_TRUE(db().Execute("PRAGMA journal_mode = WAL"));
ASSERT_FALSE(GetPathExists(journal)); ASSERT_EQ("wal", ExecuteWithResult(&db(), "PRAGMA journal_mode"));
mode = base::FILE_PERMISSION_MASK;
EXPECT_TRUE(base::GetPosixFilePermissions(db_path(), &mode));
ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode);
// Verify that journal creation inherits the restriction. // The WAL file is created lazily on first change.
EXPECT_TRUE(db().Execute("CREATE TABLE x (x)")); ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
ASSERT_TRUE(GetPathExists(journal));
mode = base::FILE_PERMISSION_MASK; {
EXPECT_TRUE(base::GetPosixFilePermissions(journal, &mode)); base::FilePath wal_path = sql::Connection::WriteAheadLogPath(db_path());
ASSERT_EQ((mode & base::FILE_PERMISSION_USER_MASK), mode); ASSERT_TRUE(GetPathExists(wal_path));
EXPECT_TRUE(base::GetPosixFilePermissions(wal_path, &mode));
ASSERT_EQ(mode, 0600);
base::FilePath shm_path = sql::Connection::SharedMemoryFilePath(db_path());
ASSERT_TRUE(GetPathExists(shm_path));
EXPECT_TRUE(base::GetPosixFilePermissions(shm_path, &mode));
ASSERT_EQ(mode, 0600);
}
} }
#endif // defined(OS_POSIX) #endif // defined(OS_POSIX)
...@@ -1439,8 +1428,8 @@ TEST_F(SQLConnectionTest, CollectDiagnosticInfo) { ...@@ -1439,8 +1428,8 @@ TEST_F(SQLConnectionTest, CollectDiagnosticInfo) {
} }
TEST_F(SQLConnectionTest, RegisterIntentToUpload) { TEST_F(SQLConnectionTest, RegisterIntentToUpload) {
base::FilePath breadcrumb_path( base::FilePath breadcrumb_path =
db_path().DirName().Append(FILE_PATH_LITERAL("sqlite-diag"))); db_path().DirName().AppendASCII("sqlite-diag");
// No stale diagnostic store. // No stale diagnostic store.
ASSERT_TRUE(!base::PathExists(breadcrumb_path)); ASSERT_TRUE(!base::PathExists(breadcrumb_path));
......
...@@ -321,7 +321,7 @@ TEST_F(SQLiteFeaturesTest, TimeMachine) { ...@@ -321,7 +321,7 @@ TEST_F(SQLiteFeaturesTest, TimeMachine) {
ASSERT_TRUE(db().Execute("CREATE TABLE t (id INTEGER PRIMARY KEY)")); ASSERT_TRUE(db().Execute("CREATE TABLE t (id INTEGER PRIMARY KEY)"));
db().Close(); db().Close();
base::FilePath journal(db_path().value() + FILE_PATH_LITERAL("-journal")); base::FilePath journal = sql::Connection::JournalPath(db_path());
ASSERT_TRUE(GetPathExists(db_path())); ASSERT_TRUE(GetPathExists(db_path()));
ASSERT_TRUE(GetPathExists(journal)); ASSERT_TRUE(GetPathExists(journal));
...@@ -450,7 +450,7 @@ TEST_F(SQLiteFeaturesTest, SmartAutoVacuum) { ...@@ -450,7 +450,7 @@ TEST_F(SQLiteFeaturesTest, SmartAutoVacuum) {
// additional work into Chromium shutdown. Verify that SQLite supports a config // additional work into Chromium shutdown. Verify that SQLite supports a config
// option to not checkpoint on close. // option to not checkpoint on close.
TEST_F(SQLiteFeaturesTest, WALNoClose) { TEST_F(SQLiteFeaturesTest, WALNoClose) {
base::FilePath wal_path(db_path().value() + FILE_PATH_LITERAL("-wal")); base::FilePath wal_path = sql::Connection::WriteAheadLogPath(db_path());
// Turn on WAL mode, then verify that the mode changed (WAL is supported). // Turn on WAL mode, then verify that the mode changed (WAL is supported).
ASSERT_TRUE(db().Execute("PRAGMA journal_mode = WAL")); ASSERT_TRUE(db().Execute("PRAGMA journal_mode = WAL"));
......
...@@ -66,6 +66,12 @@ config("chromium_sqlite3_compile_options") { ...@@ -66,6 +66,12 @@ config("chromium_sqlite3_compile_options") {
# TODO(pwnall): Upstream the ability to use this define. # TODO(pwnall): Upstream the ability to use this define.
"SQLITE_MMAP_READ_ONLY=1", "SQLITE_MMAP_READ_ONLY=1",
# The default POSIX permissions for a newly created SQLite database.
#
# If unspecified, this defaults to 0644. All the data stored by Chrome is
# private, so our databases use stricter settings.
"SQLITE_DEFAULT_FILE_PERMISSIONS=0600",
# SQLite uses a lookaside buffer to improve performance of small mallocs. # SQLite uses a lookaside buffer to improve performance of small mallocs.
# Chrome already depends on small mallocs being efficient, so we disable # Chrome already depends on small mallocs being efficient, so we disable
# this to avoid the extra memory overhead. # this to avoid the extra memory overhead.
...@@ -137,9 +143,6 @@ config("chromium_sqlite3_compile_options") { ...@@ -137,9 +143,6 @@ config("chromium_sqlite3_compile_options") {
# Uses isnan() in the C99 standard library. # Uses isnan() in the C99 standard library.
"SQLITE_HAVE_ISNAN", "SQLITE_HAVE_ISNAN",
# TODO(pwnall): Add SQLITE_OMIT_COMPLETE when we import a SQLite release
# including https://www.sqlite.org/src/info/c3e816cca4ddf096
] ]
# On OSX, SQLite has extra logic for detecting the use of network # On OSX, SQLite has extra logic for detecting the use of network
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment