Commit 73e4c36f authored by skerner@google.com's avatar skerner@google.com

Add external extensions json source in proper mac location.

The old path will be deprecated once developers have migrated.

BUG=67203
TEST=FileUtilTest.IsPathControledByAdmin

Review URL: http://codereview.chromium.org/7718021

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@102274 0039d316-1c4b-4281-b951-d872f2087c98
parent e4d9c1fb
......@@ -377,6 +377,32 @@ BASE_EXPORT bool GetCurrentDirectory(FilePath* path);
// Sets the current working directory for the process.
BASE_EXPORT bool SetCurrentDirectory(const FilePath& path);
#if defined(OS_POSIX)
// Test that |path| can only be changed by a specific user and group.
// Specifically, test that all parts of |path| under (and including) |base|:
// * Exist.
// * Are owned by a specific user and group.
// * Are not writable by all users.
// * Are not symbolic links.
// This is useful for checking that a config file is administrator-controlled.
// |base| must contain |path|.
BASE_EXPORT bool VerifyPathControlledByUser(const FilePath& base,
const FilePath& path,
uid_t owner_uid,
gid_t group_gid);
#endif // defined(OS_POSIX)
#if defined(OS_MACOSX)
// Is |path| writable only by a user with administrator privileges?
// This function uses Mac OS conventions. The super user is assumed to have
// uid 0, and the administrator group is assumed to be named "admin".
// Testing that |path|, and every parent directory including the root of
// the filesystem, are owned by the superuser, controlled by the group
// "admin", are not writable by all users, and contain no symbolic links.
// Will return false if |path| does not exist.
BASE_EXPORT bool VerifyPathControlledByAdmin(const FilePath& path);
#endif // defined(OS_MACOSX)
// A class to handle auto-closing of FILE*'s.
class ScopedFILEClose {
public:
......
......@@ -8,6 +8,7 @@
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <grp.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
......@@ -52,19 +53,6 @@ namespace file_util {
namespace {
// Helper for NormalizeFilePath(), defined below.
bool RealPath(const FilePath& path, FilePath* real_path) {
base::ThreadRestrictions::AssertIOAllowed(); // For realpath().
FilePath::CharType buf[PATH_MAX];
if (!realpath(path.value().c_str(), buf))
return false;
*real_path = FilePath(buf);
return true;
}
} // namespace
#if defined(OS_OPENBSD) || defined(OS_FREEBSD) || \
(defined(OS_MACOSX) && \
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5)
......@@ -73,14 +61,73 @@ static int CallStat(const char *path, stat_wrapper_t *sb) {
base::ThreadRestrictions::AssertIOAllowed();
return stat(path, sb);
}
static int CallLstat(const char *path, stat_wrapper_t *sb) {
base::ThreadRestrictions::AssertIOAllowed();
return lstat(path, sb);
}
#else
typedef struct stat64 stat_wrapper_t;
static int CallStat(const char *path, stat_wrapper_t *sb) {
base::ThreadRestrictions::AssertIOAllowed();
return stat64(path, sb);
}
static int CallLstat(const char *path, stat_wrapper_t *sb) {
base::ThreadRestrictions::AssertIOAllowed();
return lstat64(path, sb);
}
#endif
// Helper for NormalizeFilePath(), defined below.
bool RealPath(const FilePath& path, FilePath* real_path) {
base::ThreadRestrictions::AssertIOAllowed(); // For realpath().
FilePath::CharType buf[PATH_MAX];
if (!realpath(path.value().c_str(), buf))
return false;
*real_path = FilePath(buf);
return true;
}
// Helper for VerifyPathControlledByUser.
bool VerifySpecificPathControlledByUser(const FilePath& path,
uid_t owner_uid,
gid_t group_gid) {
stat_wrapper_t stat_info;
if (CallLstat(path.value().c_str(), &stat_info) != 0) {
PLOG(ERROR) << "Failed to get information on path "
<< path.value();
return false;
}
if (S_ISLNK(stat_info.st_mode)) {
LOG(ERROR) << "Path " << path.value()
<< " is a symbolic link.";
return false;
}
if (stat_info.st_uid != owner_uid) {
LOG(ERROR) << "Path " << path.value()
<< " is owned by the wrong user.";
return false;
}
if (stat_info.st_gid != group_gid) {
LOG(ERROR) << "Path " << path.value()
<< " is owned by the wrong group.";
return false;
}
if (stat_info.st_mode & S_IWOTH) {
LOG(ERROR) << "Path " << path.value()
<< " is writable by any user.";
return false;
}
return true;
}
} // namespace
static std::string TempFileName() {
#if defined(OS_MACOSX)
return StringPrintf(".%s.XXXXXX", base::mac::BaseBundleID());
......@@ -940,4 +987,65 @@ bool CopyFile(const FilePath& from_path, const FilePath& to_path) {
}
#endif // defined(OS_MACOSX)
bool VerifyPathControlledByUser(const FilePath& base,
const FilePath& path,
uid_t owner_uid,
gid_t group_gid) {
if (base != path && !base.IsParent(path)) {
LOG(ERROR) << "|base| must be a subdirectory of |path|. base = \""
<< base.value() << "\", path = \"" << path.value() << "\"";
return false;
}
std::vector<FilePath::StringType> base_components;
std::vector<FilePath::StringType> path_components;
base.GetComponents(&base_components);
path.GetComponents(&path_components);
std::vector<FilePath::StringType>::const_iterator ib, ip;
for (ib = base_components.begin(), ip = path_components.begin();
ib != base_components.end(); ++ib, ++ip) {
// |base| must be a subpath of |path|, so all components should match.
// If these CHECKs fail, look at the test that base is a parent of
// path at the top of this function.
CHECK(ip != path_components.end());
CHECK(*ip == *ib);
}
FilePath current_path = base;
if (!VerifySpecificPathControlledByUser(current_path, owner_uid, group_gid))
return false;
for (; ip != path_components.end(); ++ip) {
current_path = current_path.Append(*ip);
if (!VerifySpecificPathControlledByUser(current_path, owner_uid, group_gid))
return false;
}
return true;
}
#if defined(OS_MACOSX)
bool VerifyPathControlledByAdmin(const FilePath& path) {
const unsigned kRootUid = 0;
const FilePath kFileSystemRoot("/");
// The name of the administrator group on mac os.
const char kAdminGroupName[] = "admin";
// Reading the groups database may touch the file system.
base::ThreadRestrictions::AssertIOAllowed();
struct group *group_record = getgrnam(kAdminGroupName);
if (!group_record) {
PLOG(ERROR) << "Could not get the group ID of group \""
<< kAdminGroupName << "\".";
return false;
}
return VerifyPathControlledByUser(
kFileSystemRoot, path, kRootUid, group_record->gr_gid);
}
#endif // defined(OS_MACOSX)
} // namespace file_util
This diff is collapsed.
......@@ -222,14 +222,38 @@ void ExternalExtensionProviderImpl::CreateExternalProviders(
VisitorInterface* service,
Profile* profile,
ProviderCollection* provider_list) {
// On Mac OS, items in /Library/... should be written by the superuser.
// Check that all components of the path are writable by root only.
ExternalPrefExtensionLoader::Options options;
#if defined(OS_MACOSX)
options = ExternalPrefExtensionLoader::ENSURE_PATH_CONTROLLED_BY_ADMIN;
#else
options = ExternalPrefExtensionLoader::NONE;
#endif
provider_list->push_back(
linked_ptr<ExternalExtensionProviderInterface>(
new ExternalExtensionProviderImpl(
service,
new ExternalPrefExtensionLoader(
chrome::DIR_EXTERNAL_EXTENSIONS, options),
Extension::EXTERNAL_PREF,
Extension::EXTERNAL_PREF_DOWNLOAD)));
#if defined(OS_MACOSX)
// Support old path to external extensions file as we migrate to the
// new one. See crbug/67203.
provider_list->push_back(
linked_ptr<ExternalExtensionProviderInterface>(
new ExternalExtensionProviderImpl(
service,
new ExternalPrefExtensionLoader(
chrome::DIR_EXTERNAL_EXTENSIONS),
chrome::DIR_DEPRICATED_EXTERNAL_EXTENSIONS,
ExternalPrefExtensionLoader::NONE),
Extension::EXTERNAL_PREF,
Extension::EXTERNAL_PREF_DOWNLOAD)));
#endif
#if defined(OS_CHROMEOS)
// Chrome OS specific source for OEM customization.
......
......@@ -7,7 +7,9 @@
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/path_service.h"
#include "chrome/common/chrome_paths.h"
#include "content/browser/browser_thread.h"
#include "content/common/json_value_serializer.h"
......@@ -34,8 +36,10 @@ DictionaryValue* ExtractPrefs(const FilePath& path,
} // namespace
ExternalPrefExtensionLoader::ExternalPrefExtensionLoader(int base_path_key)
: base_path_key_(base_path_key) {
ExternalPrefExtensionLoader::ExternalPrefExtensionLoader(int base_path_key,
Options options)
: base_path_key_(base_path_key),
options_(options){
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
......@@ -55,32 +59,72 @@ void ExternalPrefExtensionLoader::StartLoading() {
&ExternalPrefExtensionLoader::LoadOnFileThread));
}
void ExternalPrefExtensionLoader::LoadOnFileThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DictionaryValue* ExternalPrefExtensionLoader::ReadJsonPrefsFile() {
// TODO(skerner): Some values of base_path_key_ will cause
// PathService::Get() to return false, because the path does
// not exist. Find and fix the build/install scripts so that
// this can become a CHECK(). Known examples include chrome
// OS developer builds and linux install packages.
// Tracked as crbug.com/70402 .
if (!PathService::Get(base_path_key_, &base_path_)) {
return NULL;
}
FilePath json_file = base_path_.Append(
FILE_PATH_LITERAL("external_extensions.json"));
scoped_ptr<DictionaryValue> prefs;
if (PathService::Get(base_path_key_, &base_path_)) {
FilePath json_file;
json_file =
base_path_.Append(FILE_PATH_LITERAL("external_extensions.json"));
if (!file_util::PathExists(json_file)) {
// This is not an error. The file does not exist by default.
return NULL;
}
if (file_util::PathExists(json_file)) {
JSONFileValueSerializer serializer(json_file);
prefs.reset(ExtractPrefs(json_file, &serializer));
if (IsOptionSet(ENSURE_PATH_CONTROLLED_BY_ADMIN)) {
#if defined(OS_MACOSX)
if (!file_util::VerifyPathControlledByAdmin(json_file)) {
LOG(ERROR) << "Can not read external extensions source. The file "
<< json_file.value() << " and every directory in its path, "
<< "must be owned by root, have group \"admin\", and not be "
<< "writable by all users. These restrictions prevent "
<< "unprivleged users from making chrome install extensions "
<< "on other users' accounts.";
return NULL;
}
#else
// The only platform that uses this check is Mac OS. If you add one,
// you need to implement file_util::VerifyPathControlledByAdmin() for
// that platform.
NOTREACHED();
#endif // defined(OS_MACOSX)
}
if (!prefs.get())
prefs.reset(new DictionaryValue());
JSONFileValueSerializer serializer(json_file);
return ExtractPrefs(json_file, &serializer);
}
void ExternalPrefExtensionLoader::LoadOnFileThread() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
prefs_.reset(prefs.release());
prefs_.reset(ReadJsonPrefsFile());
if (!prefs_.get())
prefs_.reset(new DictionaryValue());
// We want to deprecate the external extensions file inside the app
// bundle on mac os. Use a histogram to see how many extensions
// are installed using the deprecated path, and how many are installed
// from the supported path. We can use this data to measure the
// effectiveness of asking developers to use the new path, or any
// automatic migration methods we implement.
#if defined(OS_MACOSX)
// The deprecated path only exists on mac for now.
if (base_path_key_ == chrome::DIR_DEPRICATED_EXTERNAL_EXTENSIONS) {
UMA_HISTOGRAM_COUNTS_100("Extensions.DepricatedExternalJsonCount",
prefs_->size());
}
#endif // defined(OS_MACOSX)
if (base_path_key_ == chrome::DIR_EXTERNAL_EXTENSIONS) {
UMA_HISTOGRAM_COUNTS_100("Extensions.ExternalJsonCount",
prefs_->size());
}
// If we have any records to process, then we must have
// read the .json file. If we read the .json file, then
......
......@@ -20,24 +20,38 @@
// thread and they are expecting public method calls from the UI thread.
class ExternalPrefExtensionLoader : public ExternalExtensionLoader {
public:
enum Options {
NONE = 0,
// Ensure that only root can force an external install by checking
// that all components of the path to external extensions files are
// owned by root and not writable by any non-root user.
ENSURE_PATH_CONTROLLED_BY_ADMIN = 1 << 0
};
// |base_path_key| is the directory containing the external_extensions.json
// file. Relative file paths to extension files are resolved relative
// to this path.
explicit ExternalPrefExtensionLoader(int base_path_key);
explicit ExternalPrefExtensionLoader(int base_path_key, Options options);
virtual const FilePath GetBaseCrxFilePath() OVERRIDE;
protected:
virtual void StartLoading() OVERRIDE;
bool IsOptionSet(Options option) {
return (options_ & option) != 0;
}
private:
friend class base::RefCountedThreadSafe<ExternalExtensionLoader>;
virtual ~ExternalPrefExtensionLoader() {}
DictionaryValue* ReadJsonPrefsFile();
void LoadOnFileThread();
int base_path_key_;
Options options_;
FilePath base_path_;
DISALLOW_COPY_AND_ASSIGN(ExternalPrefExtensionLoader);
......
......@@ -334,16 +334,12 @@ bool PathProvider(int key, FilePath* result) {
#endif
case chrome::DIR_EXTERNAL_EXTENSIONS:
#if defined(OS_MACOSX)
if (!PathService::Get(base::DIR_EXE, &cur))
if (!chrome::GetGlobalApplicationSupportDirectory(&cur))
return false;
// On Mac, built-in extensions are in Contents/Extensions, a sibling of
// the App dir. If there are none, it may not exist.
// TODO(skerner): Reading external extensions from a file inside the
// app budle causes several problems. Change this path to be outside
// the app bundle. crbug/67203
cur = cur.DirName();
cur = cur.Append(FILE_PATH_LITERAL("Extensions"));
cur = cur.Append(FILE_PATH_LITERAL("Google"))
.Append(FILE_PATH_LITERAL("Chrome"))
.Append(FILE_PATH_LITERAL("External Extensions"));
create_dir = false;
#else
if (!PathService::Get(base::DIR_MODULE, &cur))
......@@ -353,6 +349,22 @@ bool PathProvider(int key, FilePath* result) {
create_dir = true;
#endif
break;
#if defined(OS_MACOSX)
case DIR_DEPRICATED_EXTERNAL_EXTENSIONS:
// TODO(skerner): Reading external extensions from a file inside the
// app budle causes several problems. Once users have a chance to
// migrate, remove this path. crbug/67203
if (!PathService::Get(base::DIR_EXE, &cur))
return false;
cur = cur.DirName();
cur = cur.Append(FILE_PATH_LITERAL("Extensions"));
create_dir = false;
break;
#endif
case chrome::DIR_DEFAULT_APPS:
#if defined(OS_MACOSX)
cur = base::mac::MainAppBundlePath();
......@@ -363,6 +375,7 @@ bool PathProvider(int key, FilePath* result) {
cur = cur.Append(FILE_PATH_LITERAL("default_apps"));
#endif
break;
default:
return false;
}
......
......@@ -57,6 +57,13 @@ enum {
#endif
DIR_EXTERNAL_EXTENSIONS, // Directory where installer places .crx files.
#if defined(OS_MACOSX)
DIR_DEPRICATED_EXTERNAL_EXTENSIONS, // Former home of external extensions.
// We read from the old path for now,
// to give users time to migrate.
#endif
DIR_DEFAULT_APPS, // Directory where installer places .crx files
// to be installed when chrome is first run.
FILE_RESOURCE_MODULE, // Full path and filename of the module that
......
......@@ -76,6 +76,9 @@ FilePath GetFrameworkBundlePath();
// Get the local library directory.
bool GetLocalLibraryDirectory(FilePath* result);
// Get the global Application Support directory (under /Library/).
bool GetGlobalApplicationSupportDirectory(FilePath* result);
// Returns the NSBundle for the outer browser application, even when running
// inside the helper. In unbundled applications, such as tests, returns nil.
NSBundle* OuterAppBundle();
......
......@@ -105,6 +105,10 @@ bool GetUserDocumentsDirectory(FilePath* result) {
return base::mac::GetUserDirectory(NSDocumentDirectory, result);
}
bool GetGlobalApplicationSupportDirectory(FilePath* result) {
return base::mac::GetLocalDirectory(NSApplicationSupportDirectory, result);
}
void GetUserCacheDirectory(const FilePath& profile_dir, FilePath* result) {
// If the profile directory is under ~/Library/Application Support,
// use a suitable cache directory under ~/Library/Caches. For
......
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