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 |