| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This provides an API for performing typical filesystem related tasks while |
| // guaranteeing certain security properties are maintained. Specifically, checks |
| // are performed to disallow symbolic links, and exotic file objects. The goal |
| // behind these checks is to thwart attacks that rely on confusing system |
| // services to perform unintended file operations like ownership changes or |
| // copy-as-root attack primitives. To accomplish this these operations are |
| // written to avoid susceptibility to TOCTOU (time-of-check-time-of-use) |
| // attacks. |
| |
| // To use this API start with the root path and work from there. For example: |
| // SafeFD fd(SafeDirFD::Root().MakeFile(PATH).first); |
| // if (!fd.is_valid()) { |
| // LOG(ERROR) << "Failed to open " << PATH; |
| // return false; |
| // } |
| // if (fd.WriteString(CONTENTS) != SafeFD::kNoError) { |
| // LOG(ERROR) << "Failed to write to " << PATH; |
| // return false; |
| // } |
| // auto read_result = fd.ReadString(); |
| // if (!read_result.second != SafeFD::kNoError) { |
| // LOG(ERROR) << "Failed to read from " << PATH; |
| // return false; |
| // } |
| |
| #ifndef LIBBRILLO_BRILLO_FILES_SAFE_FD_H_ |
| #define LIBBRILLO_BRILLO_FILES_SAFE_FD_H_ |
| |
| #include <fcntl.h> |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/scoped_file.h> |
| #include <base/optional.h> |
| #include <base/synchronization/lock.h> |
| #include <brillo/brillo_export.h> |
| |
| namespace brillo { |
| |
| class SafeFDTest; |
| |
| class SafeFD { |
| public: |
| enum class Error { |
| kNoError = 0, |
| kBadArgument, |
| kNotInitialized, // Invalid operation on a SafeFD that was not initialized. |
| kIOError, // Check errno for specific cause. |
| kDoesNotExist, // The specified path does not exist. |
| kSymlinkDetected, |
| kBoundaryDetected, // Detected a file system boundary during recursion. |
| kWrongType, // (e.g. got a directory and expected a file) |
| kWrongUID, |
| kWrongGID, |
| kWrongPermissions, |
| kExceededMaximum, // The maximum allowed read size was reached. |
| }; |
| |
| // Returns true if |err| denotes a failed operation. |
| BRILLO_EXPORT static bool IsError(SafeFD::Error err); |
| |
| typedef std::pair<SafeFD, Error> SafeFDResult; |
| |
| // 100 MiB |
| BRILLO_EXPORT static constexpr size_t kDefaultMaxRead = 100 << 20; |
| BRILLO_EXPORT static constexpr size_t kDefaultMaxPathDepth = 256; |
| // User read and write only. |
| BRILLO_EXPORT static constexpr size_t kDefaultFilePermissions = 0640; |
| // User read, write, and execute. Group read and execute. |
| BRILLO_EXPORT static constexpr size_t kDefaultDirPermissions = 0750; |
| |
| // Get a SafeFD to the root path. |
| BRILLO_EXPORT static SafeFDResult Root() WARN_UNUSED_RESULT; |
| BRILLO_EXPORT static void SetRootPathForTesting(const char* new_root_path); |
| |
| // Constructs an invalid fd; |
| BRILLO_EXPORT SafeFD() = default; |
| |
| // Move-based constructor and assignment. |
| BRILLO_EXPORT SafeFD(SafeFD&&) = default; |
| BRILLO_EXPORT SafeFD& operator=(SafeFD&&) = default; |
| |
| // Return the fd number. |
| BRILLO_EXPORT int get() const WARN_UNUSED_RESULT; |
| |
| // Check the validity of the file descriptor. |
| BRILLO_EXPORT bool is_valid() const WARN_UNUSED_RESULT; |
| |
| // Close the scoped file if one was open. |
| BRILLO_EXPORT void reset(); |
| |
| // Wrap |fd| with a SafeFD which will close the fd when this goes out of |
| // scope. This closes the original fd if one was open. |
| // This is named "Unsafe" because the recommended way to get a SafeFD |
| // instance is opening one from SafeFD::Root(). |
| BRILLO_EXPORT void UnsafeReset(int fd); |
| |
| // Writes |size| bytes from |data| into a file and returns kNoError on |
| // success. Note the file will be truncated to the size of the content. |
| // |
| // Parameters |
| // data - The buffer to write to the file. |
| // size - The number of bytes to write. |
| BRILLO_EXPORT Error Write(const char* data, size_t size) WARN_UNUSED_RESULT; |
| |
| // Read the contents of the file and return it as a string. |
| // |
| // Parameters |
| // size - The max number of bytes to read. |
| BRILLO_EXPORT std::pair<std::vector<char>, Error> ReadContents( |
| size_t max_size = kDefaultMaxRead) WARN_UNUSED_RESULT; |
| |
| // Reads exactly |size| bytes into |data|. |
| // |
| // Parameters |
| // data - The buffer to read the file into. |
| // size - The number of bytes to read. |
| BRILLO_EXPORT Error Read(char* data, size_t size) WARN_UNUSED_RESULT; |
| |
| // Open an existing file relative to this directory. |
| // |
| // Parameters |
| // path - The path to open relative to the current directory. |
| BRILLO_EXPORT SafeFDResult OpenExistingFile(const base::FilePath& path, |
| int flags = O_RDWR | O_CLOEXEC) |
| WARN_UNUSED_RESULT; |
| |
| // Open an existing directory relative to this directory. |
| // |
| // Parameters |
| // path - The path to open relative to the current directory. |
| BRILLO_EXPORT SafeFDResult OpenExistingDir(const base::FilePath& path, |
| int flags = O_RDONLY | O_CLOEXEC) |
| WARN_UNUSED_RESULT; |
| |
| // Open a file relative to this directory creating the parent directories and |
| // file if they don't already exist. |
| BRILLO_EXPORT SafeFDResult |
| MakeFile(const base::FilePath& path, |
| mode_t permissions = kDefaultFilePermissions, |
| uid_t uid = getuid(), |
| gid_t gid = getgid(), |
| int flags = O_RDWR | O_CLOEXEC) WARN_UNUSED_RESULT; |
| |
| // Create the directories in the relative path with the given ownership and |
| // permissions and return a file descriptor to the result. |
| BRILLO_EXPORT SafeFDResult |
| MakeDir(const base::FilePath& path, |
| mode_t permissions = kDefaultDirPermissions, |
| uid_t uid = getuid(), |
| gid_t gid = getgid(), |
| int flags = O_RDONLY | O_CLOEXEC) WARN_UNUSED_RESULT; |
| |
| // Hard link |fd| in the directory represented by |this| with the specified |
| // name |filename|. This requires CAP_DAC_READ_SEARCH. |
| // |
| // Parameters |
| // data - The buffer to write to the file. |
| // size - The number of bytes to write. |
| BRILLO_EXPORT Error Link(const SafeFD& source_dir, |
| const std::string& source_name, |
| const std::string& destination_name) |
| WARN_UNUSED_RESULT; |
| |
| // Deletes the child path named |name|. |
| // |
| // Parameters |
| // name - the name of the filesystem object to delete. |
| BRILLO_EXPORT Error Unlink(const std::string& name) WARN_UNUSED_RESULT; |
| |
| // Deletes a child directory. It will return kBoundaryDetected if a file |
| // system boundary is reached during recursion. |
| // |
| // Parameters |
| // name - the name of the directory to delete. |
| // recursive - if true also unlink child paths. |
| // max_depth - limit on recursion depth to prevent fd exhaustion and stack |
| // overflows. |
| // keep_going - in recursive case continue deleting even in the face of |
| // errors. If all entries cannot be deleted, the last error encountered |
| // during recursion is returned. |
| BRILLO_EXPORT Error Rmdir(const std::string& name, |
| bool recursive = false, |
| size_t max_depth = kDefaultMaxPathDepth, |
| bool keep_going = true) WARN_UNUSED_RESULT; |
| |
| private: |
| BRILLO_EXPORT static const char* RootPath; |
| |
| base::ScopedFD fd_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SafeFD); |
| }; |
| |
| } // namespace brillo |
| |
| #endif // LIBBRILLO_BRILLO_FILES_SAFE_FD_H_ |