// 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 <stddef.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