| Index: sandbox/linux/syscall_broker/broker_file_permission.cc
|
| diff --git a/sandbox/linux/syscall_broker/broker_file_permission.cc b/sandbox/linux/syscall_broker/broker_file_permission.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..beceda93f50c8871b6d3c37b0ad8c66a6d97d1bc
|
| --- /dev/null
|
| +++ b/sandbox/linux/syscall_broker/broker_file_permission.cc
|
| @@ -0,0 +1,243 @@
|
| +// Copyright 2014 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "sandbox/linux/syscall_broker/broker_file_permission.h"
|
| +
|
| +#include <fcntl.h>
|
| +#include <string.h>
|
| +
|
| +#include <string>
|
| +
|
| +#include "base/logging.h"
|
| +#include "sandbox/linux/syscall_broker/broker_common.h"
|
| +
|
| +namespace sandbox {
|
| +
|
| +namespace syscall_broker {
|
| +
|
| +// Async signal safe
|
| +bool BrokerFilePermission::ValidatePath(const char* path) {
|
| + if (!path)
|
| + return false;
|
| +
|
| + const size_t len = strlen(path);
|
| + // No empty paths
|
| + if (len == 0)
|
| + return false;
|
| + // Paths must be absolute and not relative
|
| + if (path[0] != '/')
|
| + return false;
|
| + // No trailing / (but "/" is valid)
|
| + if (len > 1 && path[len - 1] == '/')
|
| + return false;
|
| + // No trailing /..
|
| + if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' &&
|
| + path[len - 1] == '.')
|
| + return false;
|
| + // No /../ anywhere
|
| + for (size_t i = 0; i < len; i++) {
|
| + if (path[i] == '/' && (len - i) > 3) {
|
| + if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') {
|
| + return false;
|
| + }
|
| + }
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +// Async signal safe
|
| +// Calls std::string::c_str(), strncmp and strlen. All these
|
| +// methods are async signal safe in common standard libs.
|
| +// TODO(leecam): remove dependency on std::string
|
| +bool BrokerFilePermission::MatchPath(const char* requested_filename) const {
|
| + const char* path = path_.c_str();
|
| + if ((recursive_ && strncmp(requested_filename, path, strlen(path)) == 0)) {
|
| + // Note: This prefix match will allow any path under the whitelisted
|
| + // path, for any number of directory levels. E.g. if the whitelisted
|
| + // path is /good/ then the following will be permitted by the policy.
|
| + // /good/file1
|
| + // /good/folder/file2
|
| + // /good/folder/folder2/file3
|
| + // If an attacker could make 'folder' a symlink to ../../ they would have
|
| + // access to the entire filesystem.
|
| + // Whitelisting with multiple depths is useful, e.g /proc/ but
|
| + // the system needs to ensure symlinks can not be created!
|
| + // That said if an attacker can convert any of the absolute paths
|
| + // to a symlink they can control any file on the system also.
|
| + return true;
|
| + } else if (strcmp(requested_filename, path) == 0) {
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +// Async signal safe.
|
| +// External call to std::string::c_str() is
|
| +// called in MatchPath.
|
| +// TODO(leecam): remove dependency on std::string
|
| +bool BrokerFilePermission::CheckAccess(const char* requested_filename,
|
| + int mode,
|
| + const char** file_to_access) const {
|
| + // First, check if |mode| is existence, ability to read or ability
|
| + // to write. We do not support X_OK.
|
| + if (mode != F_OK && mode & ~(R_OK | W_OK)) {
|
| + return false;
|
| + }
|
| +
|
| + if (!ValidatePath(requested_filename))
|
| + return false;
|
| +
|
| + if (!MatchPath(requested_filename)) {
|
| + return false;
|
| + }
|
| + bool allowed = false;
|
| + switch (mode) {
|
| + case F_OK:
|
| + if (allow_read_ || allow_write_)
|
| + allowed = true;
|
| + break;
|
| + case R_OK:
|
| + if (allow_read_)
|
| + allowed = true;
|
| + break;
|
| + case W_OK:
|
| + if (allow_write_)
|
| + allowed = true;
|
| + break;
|
| + case R_OK | W_OK:
|
| + if (allow_read_ && allow_write_)
|
| + allowed = true;
|
| + break;
|
| + default:
|
| + return false;
|
| + }
|
| +
|
| + if (allowed && file_to_access) {
|
| + if (!recursive_)
|
| + *file_to_access = path_.c_str();
|
| + else
|
| + *file_to_access = requested_filename;
|
| + }
|
| + return allowed;
|
| +}
|
| +
|
| +// Async signal safe.
|
| +// External call to std::string::c_str() is
|
| +// called in MatchPath.
|
| +// TODO(leecam): remove dependency on std::string
|
| +bool BrokerFilePermission::CheckOpen(const char* requested_filename,
|
| + int flags,
|
| + const char** file_to_open,
|
| + bool* unlink_after_open) const {
|
| + if (!ValidatePath(requested_filename))
|
| + return false;
|
| +
|
| + if (!MatchPath(requested_filename)) {
|
| + return false;
|
| + }
|
| +
|
| + // First, check the access mode is valid.
|
| + const int access_mode = flags & O_ACCMODE;
|
| + if (access_mode != O_RDONLY && access_mode != O_WRONLY &&
|
| + access_mode != O_RDWR) {
|
| + return false;
|
| + }
|
| +
|
| + // Check if read is allowed
|
| + if (!allow_read_ && (access_mode == O_RDONLY || access_mode == O_RDWR)) {
|
| + return false;
|
| + }
|
| +
|
| + // Check if write is allowed
|
| + if (!allow_write_ && (access_mode == O_WRONLY || access_mode == O_RDWR)) {
|
| + return false;
|
| + }
|
| +
|
| + // Check if file creation is allowed.
|
| + if (!allow_create_ && (flags & O_CREAT)) {
|
| + return false;
|
| + }
|
| +
|
| + // If O_CREAT is present, ensure O_EXCL
|
| + if ((flags & O_CREAT) && !(flags & O_EXCL)) {
|
| + return false;
|
| + }
|
| +
|
| + // If this file is to be unlinked, ensure it's created.
|
| + if (unlink_ && !(flags & O_CREAT)) {
|
| + return false;
|
| + }
|
| +
|
| + // Some flags affect the behavior of the current process. We don't support
|
| + // them and don't allow them for now.
|
| + if (flags & kCurrentProcessOpenFlagsMask) {
|
| + return false;
|
| + }
|
| +
|
| + // Now check that all the flags are known to us.
|
| + const int creation_and_status_flags = flags & ~O_ACCMODE;
|
| +
|
| + const int known_flags = O_APPEND | O_ASYNC | O_CLOEXEC | O_CREAT | O_DIRECT |
|
| + O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME |
|
| + O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY |
|
| + O_SYNC | O_TRUNC;
|
| +
|
| + const int unknown_flags = ~known_flags;
|
| + const bool has_unknown_flags = creation_and_status_flags & unknown_flags;
|
| +
|
| + if (has_unknown_flags)
|
| + return false;
|
| +
|
| + if (file_to_open) {
|
| + if (!recursive_)
|
| + *file_to_open = path_.c_str();
|
| + else
|
| + *file_to_open = requested_filename;
|
| + }
|
| + if (unlink_after_open)
|
| + *unlink_after_open = unlink_;
|
| +
|
| + return true;
|
| +}
|
| +
|
| +const char* BrokerFilePermission::GetErrorMessageForTests() {
|
| + static char kInvalidBrokerFileString[] = "Invalid BrokerFilePermission";
|
| + return kInvalidBrokerFileString;
|
| +}
|
| +
|
| +BrokerFilePermission::BrokerFilePermission(const std::string& path,
|
| + bool recursive,
|
| + bool unlink,
|
| + bool allow_read,
|
| + bool allow_write,
|
| + bool allow_create)
|
| + : path_(path),
|
| + recursive_(recursive),
|
| + unlink_(unlink),
|
| + allow_read_(allow_read),
|
| + allow_write_(allow_write),
|
| + allow_create_(allow_create) {
|
| + // Validate this permission and die if invalid!
|
| +
|
| + // Must have enough length for a '/'
|
| + CHECK(path_.length() > 0) << GetErrorMessageForTests();
|
| + // Whitelisted paths must be absolute.
|
| + CHECK(path_[0] == '/') << GetErrorMessageForTests();
|
| +
|
| + // Don't allow unlinking on creation without create permission
|
| + if (unlink_) {
|
| + CHECK(allow_create) << GetErrorMessageForTests();
|
| + }
|
| + const char last_char = *(path_.rbegin());
|
| + // Recursive paths must have a trailing slash
|
| + if (recursive_) {
|
| + CHECK(last_char == '/') << GetErrorMessageForTests();
|
| + } else {
|
| + CHECK(last_char != '/') << GetErrorMessageForTests();
|
| + }
|
| +}
|
| +
|
| +} // namespace syscall_broker
|
| +
|
| +} // namespace sandbox
|
|
|