summaryrefslogtreecommitdiff
path: root/sandbox/linux/syscall_broker/broker_file_permission.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox/linux/syscall_broker/broker_file_permission.cc')
-rw-r--r--sandbox/linux/syscall_broker/broker_file_permission.cc243
1 files changed, 243 insertions, 0 deletions
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 0000000000..beceda93f5
--- /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 \ No newline at end of file