| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ |
| #define SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ |
| |
| #include <bitset> |
| #include <cstdint> |
| #include <string> |
| |
| #include "sandbox/sandbox_export.h" |
| |
| namespace sandbox { |
| namespace syscall_broker { |
| |
| // Recursive: allow everything under |path| (must be a dir). |
| enum class RecursionOption { kNonRecursive = 0, kRecursive }; |
| // Temporary: file will be unlink'd after opening. |
| enum class PersistenceOption { kPermanent = 0, kTemporaryOnly }; |
| enum class ReadPermission { kBlockRead = 0, kAllowRead }; |
| enum class WritePermission { kBlockWrite = 0, kAllowWrite }; |
| enum class CreatePermission { kBlockCreate = 0, kAllowCreate }; |
| // Allow stat() for the path and all intermediate dirs. |
| enum class StatWithIntermediatesPermission { |
| kBlockStatWithIntermediates = 0, |
| kAllowStatWithIntermediates |
| }; |
| enum class InotifyAddWatchWithIntermediatesPermission { |
| kBlockInotifyAddWatchWithIntermediates = 0, |
| kAllowInotifyAddWatchWithIntermediates |
| }; |
| |
| // BrokerFilePermission defines a path for allowlisting. |
| // Pick the correct static factory method to create a permission. |
| // CheckOpen and CheckAccess are async signal safe. |
| // Construction and Destruction are not async signal safe. |
| // |path| is the path to be allowlisted. |
| class SANDBOX_EXPORT BrokerFilePermission { |
| public: |
| // Movable and copyable. |
| BrokerFilePermission(BrokerFilePermission&&); |
| BrokerFilePermission& operator=(BrokerFilePermission&&); |
| BrokerFilePermission(const BrokerFilePermission&); |
| BrokerFilePermission& operator=(const BrokerFilePermission&); |
| |
| ~BrokerFilePermission(); |
| |
| static BrokerFilePermission ReadOnly(const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kAllowRead, WritePermission::kBlockWrite, |
| CreatePermission::kBlockCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission ReadOnlyRecursive(const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kAllowRead, WritePermission::kBlockWrite, |
| CreatePermission::kBlockCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission WriteOnly(const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kBlockRead, WritePermission::kAllowWrite, |
| CreatePermission::kBlockCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission ReadWrite(const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kAllowRead, WritePermission::kAllowWrite, |
| CreatePermission::kBlockCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission ReadWriteCreate(const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kAllowRead, WritePermission::kAllowWrite, |
| CreatePermission::kAllowCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission ReadWriteCreateRecursive( |
| const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kAllowRead, WritePermission::kAllowWrite, |
| CreatePermission::kAllowCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission AllPermissions(const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kAllowRead, WritePermission::kAllowWrite, |
| CreatePermission::kAllowCreate, |
| StatWithIntermediatesPermission::kAllowStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kAllowInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission AllPermissionsRecursive(const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kAllowRead, WritePermission::kAllowWrite, |
| CreatePermission::kAllowCreate, |
| StatWithIntermediatesPermission::kAllowStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kAllowInotifyAddWatchWithIntermediates); |
| } |
| |
| // Temporary files must always be newly created and do not confer rights to |
| // use pre-existing files of the same name. |
| static BrokerFilePermission ReadWriteCreateTemporary( |
| const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kTemporaryOnly, |
| ReadPermission::kAllowRead, WritePermission::kAllowWrite, |
| CreatePermission::kAllowCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission ReadWriteCreateTemporaryRecursive( |
| const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kRecursive, PersistenceOption::kTemporaryOnly, |
| ReadPermission::kAllowRead, WritePermission::kAllowWrite, |
| CreatePermission::kAllowCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission StatOnlyWithIntermediateDirs( |
| const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kBlockRead, WritePermission::kBlockWrite, |
| CreatePermission::kBlockCreate, |
| StatWithIntermediatesPermission::kAllowStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kBlockInotifyAddWatchWithIntermediates); |
| } |
| |
| static BrokerFilePermission InotifyAddWatchWithIntermediateDirs( |
| const std::string& path) { |
| return BrokerFilePermission( |
| path, RecursionOption::kNonRecursive, PersistenceOption::kPermanent, |
| ReadPermission::kBlockRead, WritePermission::kBlockWrite, |
| CreatePermission::kBlockCreate, |
| StatWithIntermediatesPermission::kBlockStatWithIntermediates, |
| InotifyAddWatchWithIntermediatesPermission:: |
| kAllowInotifyAddWatchWithIntermediates); |
| } |
| |
| // Returns nullptr if |requested_filename| is NOT allowed to be accessed |
| // by this permission with the given |mode| as per access(2). |
| // |
| // Otherwise, the returned C-string is either |requested_filename| in the |
| // case of a recursive match, or a pointer to the matched path in the |
| // allowlist if an absolute match. |
| // |
| // Async signal safe. |
| [[nodiscard]] const char* CheckAccess(const char* requested_filename, |
| int mode) const; |
| |
| // Returns {nullptr, _} if |requested_filename| is NOT allowed to be opened |
| // by this permission. |
| // |
| // Otherwise, the returned C-string is either |requested_filename| in the |
| // case of a recursive match, or a pointer to the matched path in the |
| // allowlist if an absolute match. And, the returned boolean indicates whether |
| // or not the caller is required to unlink the path after opening. |
| // |
| // Async signal safe. |
| [[nodiscard]] std::pair<const char*, bool> CheckOpen( |
| const char* requested_filename, |
| int flags) const; |
| |
| // Returns nullptr if |requested_filename| is NOT allowed to be stat'd |
| // by this permission as per stat(2). Differs from CheckAccess() |
| // in that if create permission is granted to a file, we permit |
| // stat() on all of its leading components. |
| // |
| // Otherwise, the returned C-string is either |requested_filename| in the |
| // case of a recursive match, or a pointer to the matched path in the |
| // allowlist if an absolute match. |
| // |
| // Async signal safe. |
| [[nodiscard]] const char* CheckStatWithIntermediates( |
| const char* requested_filename) const; |
| |
| // Returns nullptr if |requested_filename| is NOT allowed by this |
| // permission to be added to an inotify instance's watch list by |
| // inotify_add_watch(2), with the specific |mask|. Differs from CheckAccess() |
| // in that if inotify_add_watch permission is granted to a file, we permit |
| // inotify_add_watch() on all of its leading components. |
| // |
| // Otherwise, the returned C-string is either |requested_filename| in the |
| // case of a recursive match, or a pointer to the matched path in the |
| // allowlist if an absolute match. |
| // |
| // Async signal safe. |
| [[nodiscard]] const char* CheckInotifyAddWatchWithIntermediates( |
| const char* requested_filename, |
| uint32_t mask) const; |
| |
| private: |
| friend class BrokerFilePermissionTester; |
| |
| enum BitPositions { |
| kRecursiveBitPos = 0, |
| kTemporaryOnlyBitPos, |
| kAllowReadBitPos, |
| kAllowWriteBitPos, |
| kAllowCreateBitPos, |
| kAllowStatWithIntermediatesBitPos, |
| kAllowInotifyAddWatchWithIntermediates, |
| |
| kMaxValueBitPos = kAllowInotifyAddWatchWithIntermediates |
| }; |
| |
| // NOTE: Validates the permission and dies if invalid! |
| BrokerFilePermission(std::string path, |
| RecursionOption recurse_opt, |
| PersistenceOption persist_opt, |
| ReadPermission read_perm, |
| WritePermission write_perm, |
| CreatePermission create_perm, |
| StatWithIntermediatesPermission stat_perm, |
| InotifyAddWatchWithIntermediatesPermission inotify_perm); |
| |
| // Allows construction from the raw bitset. |
| BrokerFilePermission(std::string path, uint64_t flags); |
| |
| const std::string& path() const { return path_; } |
| |
| // Returns a serialiazable version of |flags_|. |
| uint64_t flags() const { return flags_.to_ullong(); } |
| |
| bool recursive() const { return flags_.test(kRecursiveBitPos); } |
| |
| bool temporary_only() const { return flags_.test(kTemporaryOnlyBitPos); } |
| |
| bool allow_read() const { return flags_.test(kAllowReadBitPos); } |
| |
| bool allow_write() const { return flags_.test(kAllowWriteBitPos); } |
| |
| bool allow_create() const { return flags_.test(kAllowCreateBitPos); } |
| |
| bool allow_stat_with_intermediates() const { |
| return flags_.test(kAllowStatWithIntermediatesBitPos); |
| } |
| |
| bool allow_inotify_add_watch_with_intermediates() const { |
| return flags_.test(kAllowInotifyAddWatchWithIntermediates); |
| } |
| |
| // ValidatePath checks |path| and returns true if these conditions are met |
| // * Greater than 0 length |
| // * Is an absolute path |
| // * No trailing slash |
| // * No /../ path traversal |
| [[nodiscard]] static bool ValidatePath(const char* path); |
| |
| // MatchPath returns true if |requested_filename| is covered by this instance |
| [[nodiscard]] bool MatchPath(const char* requested_filename) const; |
| |
| // Helper routine for CheckAccess() and CheckStat(). Must be safe to call |
| // from an async signal context. |
| [[nodiscard]] const char* CheckAccessInternal(const char* requested_filename, |
| int mode) const; |
| |
| // Helper routine for CheckStatWithIntermediates() and |
| // CheckInotifyAddWatchWithIntermediates() to return true if one of the |
| // following is true: |
| // 1. |requested_filename| matches a leading directory of |path_|. |
| // 2. |can_match_full_path| is true and |path_| == |requested_filename|. |
| [[nodiscard]] bool CheckIntermediates(const char* requested_filename, |
| bool can_match_full_path) const; |
| |
| // Used in by BrokerFilePermissionTester for tests. |
| static const char* GetErrorMessageForTests(); |
| |
| void DieOnInvalidPermission(); |
| |
| // These are not const to support the move constructor. All methods are marked |
| // const so the compiler will still enforce no changes outside of the |
| // constructor. |
| std::string path_; |
| std::bitset<kMaxValueBitPos + 1> flags_; |
| }; |
| |
| } // namespace syscall_broker |
| } // namespace sandbox |
| |
| #endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ |