/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #include #include #include #include #include #include #include #include #include #include #include "osquery/core/process.h" #include "osquery/filesystem/fileops.h" namespace fs = boost::filesystem; namespace errc = boost::system::errc; namespace osquery { PlatformFile::PlatformFile(const std::string& path, int mode, int perms) { int oflag = 0; bool may_create = false; bool check_existence = false; if ((mode & PF_READ) == PF_READ && (mode & PF_WRITE) == PF_WRITE) { oflag = O_RDWR; } else if ((mode & PF_READ) == PF_READ) { oflag = O_RDONLY; } else if ((mode & PF_WRITE) == PF_WRITE) { oflag = O_WRONLY; } switch (PF_GET_OPTIONS(mode)) { case PF_GET_OPTIONS(PF_CREATE_ALWAYS): oflag |= O_CREAT | O_TRUNC; may_create = true; break; case PF_GET_OPTIONS(PF_CREATE_NEW): oflag |= O_CREAT | O_EXCL; may_create = true; break; case PF_GET_OPTIONS(PF_OPEN_EXISTING): check_existence = true; break; case PF_GET_OPTIONS(PF_OPEN_ALWAYS): oflag |= O_CREAT; may_create = true; break; case PF_GET_OPTIONS(PF_TRUNCATE): if (mode & PF_WRITE) { oflag |= O_TRUNC; } break; default: break; } if ((mode & PF_NONBLOCK) == PF_NONBLOCK) { oflag |= O_NONBLOCK; is_nonblock_ = true; } if ((mode & PF_APPEND) == PF_APPEND) { oflag |= O_APPEND; } if (perms == -1 && may_create) { perms = 0666; } boost::system::error_code ec; if (check_existence && (!fs::exists(path.c_str(), ec) || ec.value() != errc::success)) { handle_ = kInvalidHandle; } else { handle_ = ::open(path.c_str(), oflag, perms); } } PlatformFile::~PlatformFile() { if (handle_ != kInvalidHandle) { ::close(handle_); handle_ = kInvalidHandle; } } bool PlatformFile::isSpecialFile() const { return (size() == 0); } static uid_t getFileOwner(PlatformHandle handle) { struct stat file; if (::fstat(handle, &file) < 0) { return -1; } return file.st_uid; } Status PlatformFile::isOwnerRoot() const { if (!isValid()) { return Status(-1, "Invalid handle_"); } uid_t owner_id = getFileOwner(handle_); if (owner_id == (uid_t)-1) { return Status(-1, "fstat error"); } if (owner_id == 0) { return Status(0, "OK"); } return Status(1, "Owner is not root"); } Status PlatformFile::isOwnerCurrentUser() const { if (!isValid()) { return Status(-1, "Invalid handle_"); } uid_t owner_id = getFileOwner(handle_); if (owner_id == (uid_t)-1) { return Status(-1, "fstat error"); } if (owner_id == ::getuid()) { return Status(0, "OK"); } return Status(1, "Owner is not current user"); } Status PlatformFile::isExecutable() const { struct stat file_stat; if (::fstat(handle_, &file_stat) < 0) { return Status(-1, "fstat error"); } if ((file_stat.st_mode & S_IXUSR) == S_IXUSR) { return Status(0, "OK"); } return Status(1, "Not executable"); } Status PlatformFile::hasSafePermissions() const { struct stat file; if (::fstat(handle_, &file) < 0) { return Status(-1, "fstat error"); } // We allow user write for now, since our main threat is external // modification by other users if ((file.st_mode & S_IWOTH) == 0) { return Status(0, "OK"); } return Status(1, "Writable"); } bool PlatformFile::getFileTimes(PlatformTime& times) { if (!isValid()) { return false; } struct stat file; if (::fstat(handle_, &file) < 0) { return false; } #if defined(__linux__) TIMESPEC_TO_TIMEVAL(×.times[0], &file.st_atim); TIMESPEC_TO_TIMEVAL(×.times[1], &file.st_mtim); #else TIMESPEC_TO_TIMEVAL(×.times[0], &file.st_atimespec); TIMESPEC_TO_TIMEVAL(×.times[1], &file.st_mtimespec); #endif return true; } bool PlatformFile::setFileTimes(const PlatformTime& times) { if (!isValid()) { return false; } return (::futimes(handle_, times.times) == 0); } ssize_t PlatformFile::read(void* buf, size_t nbyte) { if (!isValid()) { return -1; } has_pending_io_ = false; auto ret = ::read(handle_, buf, nbyte); if (ret < 0 && errno == EAGAIN) { has_pending_io_ = true; } else if (ret > 0 && static_cast(ret) < nbyte) { // This handles a (bug?) in Linux where special files are labeled as normal // for example: /sys nodes that must be read in pages. has_pending_io_ = true; } return ret; } ssize_t PlatformFile::write(const void* buf, size_t nbyte) { if (!isValid()) { return -1; } has_pending_io_ = false; auto ret = ::write(handle_, buf, nbyte); if (ret < 0 && errno == EAGAIN) { has_pending_io_ = true; } return ret; } off_t PlatformFile::seek(off_t offset, SeekMode mode) { if (!isValid()) { return -1; } int whence = 0; switch (mode) { case PF_SEEK_BEGIN: whence = SEEK_SET; break; case PF_SEEK_CURRENT: whence = SEEK_CUR; break; case PF_SEEK_END: whence = SEEK_END; break; default: break; } return ::lseek(handle_, offset, whence); } size_t PlatformFile::size() const { struct stat file; if (::fstat(handle_, &file) < 0) { // This is an error case, but the size is not signed. return 0; } return file.st_size; } boost::optional getHomeDirectory() { // Try to get the caller's home directory using HOME and getpwuid. auto user = ::getpwuid(getuid()); auto homedir = getEnvVar("HOME"); if (homedir.is_initialized()) { // Fail over to the users home directory if HOME is not writable. if (isWritable(*homedir)) { return homedir; } } if (user != nullptr && user->pw_dir != nullptr) { return std::string(user->pw_dir); } else { return boost::none; } } bool platformChmod(const std::string& path, mode_t perms) { return (::chmod(path.c_str(), perms) == 0); } std::vector platformGlob(const std::string& find_path) { std::vector results; glob_t data; ::glob( find_path.c_str(), GLOB_TILDE | GLOB_MARK | GLOB_BRACE, nullptr, &data); size_t count = data.gl_pathc; for (size_t index = 0; index < count; index++) { results.push_back(data.gl_pathv[index]); } ::globfree(&data); return results; } int platformAccess(const std::string& path, mode_t mode) { return ::access(path.c_str(), mode); } Status platformIsTmpDir(const fs::path& dir) { struct stat dir_stat; if (::stat(dir.c_str(), &dir_stat) < 0) { return Status(-1, ""); } if (dir_stat.st_mode & (1 << 9)) { return Status(0, "OK"); } return Status(1, ""); } // Reduce this to be a lstat check for symlink stuff Status platformIsFileAccessible(const fs::path& path) { struct stat link_stat; if (::lstat(path.c_str(), &link_stat) < 0) { return Status(1, "File is not acccessible"); } return Status(0, "OK"); } bool platformIsatty(FILE* f) { return 0 != isatty(fileno(f)); } boost::optional platformFopen(const std::string& filename, const std::string& mode) { auto fp = ::fopen(filename.c_str(), mode.c_str()); if (fp == nullptr) { return boost::none; } return fp; } Status socketExists(const fs::path& path, bool remove_socket) { // This implies that the socket is writable. if (pathExists(path).ok()) { if (!isWritable(path).ok()) { return Status(1, "Cannot write extension socket: " + path.string()); } else if (remove_socket && !osquery::remove(path).ok()) { return Status(1, "Cannot remove extension socket: " + path.string()); } } else { // The path does not exist. if (!pathExists(path.parent_path()).ok()) { return Status(1, "Extension socket directory missing: " + path.string()); } else if (!isWritable(path.parent_path()).ok()) { return Status(1, "Cannot create extension socket: " + path.string()); } // If we are not requesting to remove the socket then this is a failure. if (!remove_socket) { return Status(1, "Socket does not exist"); } } return Status(0); } fs::path getSystemRoot() { return fs::path("/"); } }