// 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 #include #include #include #include #include #include #include #include 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 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, 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_