From 6952384c84ed12bdaab802be582b0e4024516b63 Mon Sep 17 00:00:00 2001 From: Sangwan Kwon Date: Fri, 24 Apr 2020 14:04:26 +0900 Subject: [PATCH] Remove filesystem Signed-off-by: Sangwan Kwon --- specs/processes.table | 49 -- specs/utility/file.table | 34 - src/osquery/CMakeLists.txt | 2 - src/osquery/core/init.cpp | 17 - src/osquery/core/system.cpp | 44 +- src/osquery/filesystem/CMakeLists.txt | 22 - src/osquery/filesystem/fileops.h | 427 ---------- src/osquery/filesystem/filesystem.cpp | 571 ------------- src/osquery/filesystem/filesystem.h | 363 --------- src/osquery/filesystem/linux/mem.cpp | 104 --- src/osquery/filesystem/linux/proc.cpp | 342 -------- src/osquery/filesystem/linux/proc.h | 220 ----- .../filesystem/mock_file_structure.cpp | 49 -- src/osquery/filesystem/mock_file_structure.h | 18 - src/osquery/filesystem/posix/fileops.cpp | 379 --------- src/osquery/filesystem/tests/fileops.cpp | 770 ------------------ src/osquery/logger/logger.cpp | 1 - src/osquery/logger/plugins/filesystem.cpp | 110 --- src/osquery/logger/plugins/syslog.cpp | 63 -- src/osquery/main/main.cpp | 4 +- src/osquery/sql/virtual_sqlite_table.cpp | 12 +- src/osquery/tables/system/linux/processes.cpp | 530 ------------ src/osquery/tables/utility/file.cpp | 199 ----- src/osquery/tests/test_util.h | 1 - src/vist/client/schema/processes.hpp | 39 - src/vist/client/tests/virtual-table.cpp | 34 - src/vist/client/virtual-table.cpp | 23 +- 27 files changed, 13 insertions(+), 4414 deletions(-) delete mode 100644 specs/processes.table delete mode 100644 specs/utility/file.table delete mode 100644 src/osquery/filesystem/CMakeLists.txt delete mode 100644 src/osquery/filesystem/fileops.h delete mode 100644 src/osquery/filesystem/filesystem.cpp delete mode 100644 src/osquery/filesystem/filesystem.h delete mode 100644 src/osquery/filesystem/linux/mem.cpp delete mode 100644 src/osquery/filesystem/linux/proc.cpp delete mode 100644 src/osquery/filesystem/linux/proc.h delete mode 100644 src/osquery/filesystem/mock_file_structure.cpp delete mode 100644 src/osquery/filesystem/mock_file_structure.h delete mode 100644 src/osquery/filesystem/posix/fileops.cpp delete mode 100644 src/osquery/filesystem/tests/fileops.cpp delete mode 100644 src/osquery/logger/plugins/filesystem.cpp delete mode 100644 src/osquery/logger/plugins/syslog.cpp delete mode 100644 src/osquery/tables/system/linux/processes.cpp delete mode 100644 src/osquery/tables/utility/file.cpp delete mode 100644 src/vist/client/schema/processes.hpp diff --git a/specs/processes.table b/specs/processes.table deleted file mode 100644 index e07c6ac..0000000 --- a/specs/processes.table +++ /dev/null @@ -1,49 +0,0 @@ -table_name("processes") -description("All running processes on the host system.") -schema([ - Column("pid", BIGINT, "Process (or thread) ID", index=True), - Column("name", TEXT, "The process path or shorthand argv[0]"), - Column("path", TEXT, "Path to executed binary"), - Column("cmdline", TEXT, "Complete argv"), - Column("state", TEXT, "Process state"), - Column("cwd", TEXT, "Process current working directory"), - Column("root", TEXT, "Process virtual root directory"), - Column("uid", BIGINT, "Unsigned user ID"), - Column("gid", BIGINT, "Unsigned group ID"), - Column("euid", BIGINT, "Unsigned effective user ID"), - Column("egid", BIGINT, "Unsigned effective group ID"), - Column("suid", BIGINT, "Unsigned saved user ID"), - Column("sgid", BIGINT, "Unsigned saved group ID"), - Column("on_disk", INTEGER, - "The process path exists yes=1, no=0, unknown=-1"), - Column("wired_size", BIGINT, "Bytes of unpagable memory used by process"), - Column("resident_size", BIGINT, "Bytes of private memory used by process"), - Column("total_size", BIGINT, "Total virtual memory size", - aliases=["phys_footprint"]), - Column("user_time", BIGINT, "CPU time in milliseconds spent in user space"), - Column("system_time", BIGINT, "CPU time in milliseconds spent in kernel space"), - Column("disk_bytes_read", BIGINT, "Bytes read from disk"), - Column("disk_bytes_written", BIGINT, "Bytes written to disk"), - Column("start_time", BIGINT, "Process start time in seconds since Epoch, in case of error -1"), - Column("parent", BIGINT, "Process parent's PID"), - Column("pgroup", BIGINT, "Process group"), - Column("threads", INTEGER, "Number of threads used by process"), - Column("nice", INTEGER, "Process nice level (-20 to 20, default 0)"), -]) -extended_schema(WINDOWS, [ - Column("is_elevated_token", INTEGER, "Process uses elevated token yes=1, no=0"), - Column("elapsed_time", BIGINT, "Elapsed time in seconds this process has been running."), - Column("handle_count", BIGINT, "Total number of handles that the process has open. This number is the sum of the handles currently opened by each thread in the process."), - Column("percent_processor_time", BIGINT, "Returns elapsed time that all of the threads of this process used the processor to execute instructions in 100 nanoseconds ticks."), -]) -extended_schema(DARWIN, [ - Column("upid", BIGINT, "A 64bit pid that is never reused. Returns -1 if we couldn't gather them from the system."), - Column("uppid", BIGINT, "The 64bit parent pid that is never reused. Returns -1 if we couldn't gather them from the system."), - Column("cpu_type", INTEGER, "A 64bit pid that is never reused. Returns -1 if we couldn't gather them from the system."), - Column("cpu_subtype", INTEGER, "The 64bit parent pid that is never reused. Returns -1 if we couldn't gather them from the system."), -]) -attributes(cacheable=True, strongly_typed_rows=True) -implementation("system/processes@genProcesses") -examples([ - "select * from processes where pid = 1", -]) diff --git a/specs/utility/file.table b/specs/utility/file.table deleted file mode 100644 index 43047f3..0000000 --- a/specs/utility/file.table +++ /dev/null @@ -1,34 +0,0 @@ -table_name("file") -description("Interactive filesystem attributes and metadata.") -schema([ - Column("path", TEXT, "Absolute file path", required=True, index=True), - Column("directory", TEXT, "Directory of file(s)", required=True), - Column("filename", TEXT, "Name portion of file path"), - Column("inode", BIGINT, "Filesystem inode number"), - Column("uid", BIGINT, "Owning user ID"), - Column("gid", BIGINT, "Owning group ID"), - Column("mode", TEXT, "Permission bits"), - Column("device", BIGINT, "Device ID (optional)"), - Column("size", BIGINT, "Size of file in bytes"), - Column("block_size", INTEGER, "Block size of filesystem"), - Column("atime", BIGINT, "Last access time"), - Column("mtime", BIGINT, "Last modification time"), - Column("ctime", BIGINT, "Last status change time"), - Column("btime", BIGINT, "(B)irth or (cr)eate time"), - Column("hard_links", INTEGER, "Number of hard links"), - Column("symlink", INTEGER, "1 if the path is a symlink, otherwise 0"), - Column("type", TEXT, "File status"), -]) -extended_schema(WINDOWS, [ - Column("attributes", TEXT, "File attrib string. See: https://ss64.com/nt/attrib.html"), - Column("volume_serial", TEXT, "Volume serial number"), - Column("file_id", TEXT, "file ID"), - Column("product_version", TEXT, "File product version"), -]) -attributes(utility=True) -implementation("utility/file@genFile") -examples([ - "select * from file where path = '/etc/passwd'", - "select * from file where directory = '/etc/'", - "select * from file where path LIKE '/etc/%'", -]) diff --git a/src/osquery/CMakeLists.txt b/src/osquery/CMakeLists.txt index 7fa8209..a7a9008 100644 --- a/src/osquery/CMakeLists.txt +++ b/src/osquery/CMakeLists.txt @@ -40,8 +40,6 @@ ENDIF(DEFINED GBS_BUILD) ## osquery v4.0.0 ADD_SUBDIRECTORY(core) -ADD_SUBDIRECTORY(filesystem) -ADD_SUBDIRECTORY(logger) ADD_SUBDIRECTORY(registry) ADD_SUBDIRECTORY(sql) ADD_SUBDIRECTORY(tables) diff --git a/src/osquery/core/init.cpp b/src/osquery/core/init.cpp index afb9f05..a5319ef 100644 --- a/src/osquery/core/init.cpp +++ b/src/osquery/core/init.cpp @@ -37,7 +37,6 @@ #include "osquery/utils/info/platform_type.h" #include #include -#include #include #include #include @@ -81,10 +80,6 @@ enum { namespace { extern "C" { -static inline bool hasWorkerVariable() { - return ::osquery::getEnvVar("OSQUERY_WORKER").is_initialized(); -} - volatile std::sig_atomic_t kHandledSignal{0}; void signalHandler(int num) { @@ -174,13 +169,6 @@ Initializer::Initializer(int& argc, } void Initializer::initDaemon() const { - if (isWorker() || !isDaemon()) { - // The worker process (child) will not daemonize. - return; - } - - // Print the version to the OS system log. - systemLog(binary_ + " started [version=" + kVersion + "]"); } void Initializer::initShell() const { @@ -196,10 +184,6 @@ void Initializer::initWorker(const std::string& name) const { } } -bool Initializer::isWorker() { - return hasWorkerVariable(); -} - void Initializer::initActivePlugin(const std::string& type, const std::string& name) const { auto rs = RegistryFactory::get().setActive(type, name); @@ -244,7 +228,6 @@ void Initializer::requestShutdown(int retcode) { } void Initializer::requestShutdown(int retcode, const std::string& system_log) { - systemLog(system_log); requestShutdown(retcode); } diff --git a/src/osquery/core/system.cpp b/src/osquery/core/system.cpp index 2720675..a6e973a 100644 --- a/src/osquery/core/system.cpp +++ b/src/osquery/core/system.cpp @@ -46,7 +46,6 @@ #include #include -#include #include #include #include @@ -149,48 +148,7 @@ bool isPlaceholderHardwareUUID(const std::string& uuid) { } std::string generateHostUUID() { - std::string hardware_uuid; -#ifdef __APPLE__ - // Use the hardware UUID available on OSX to identify this machine - uuid_t id; - // wait at most 5 seconds for gethostuuid to return - const timespec wait = {5, 0}; - if (gethostuuid(id, &wait) == 0) { - char out[128] = {0}; - uuid_unparse(id, out); - hardware_uuid = std::string(out); - } -#elif WIN32 - const WmiRequest wmiUUIDReq("Select UUID from Win32_ComputerSystemProduct"); - const std::vector& wmiUUIDResults = wmiUUIDReq.results(); - if (wmiUUIDResults.size() != 0) { - wmiUUIDResults[0].GetString("UUID", hardware_uuid); - } -#else - readFile("/sys/class/dmi/id/product_uuid", hardware_uuid); -#endif - - // We know at least Linux will append a newline. - hardware_uuid.erase( - std::remove(hardware_uuid.begin(), hardware_uuid.end(), '\n'), - hardware_uuid.end()); - boost::algorithm::trim(hardware_uuid); - if (!hardware_uuid.empty()) { - // Construct a new string to remove trailing nulls. - hardware_uuid = std::string(hardware_uuid.c_str()); - } - - // Check whether the UUID is valid. If not generate an ephemeral UUID. - if (hardware_uuid.empty()) { - VLOG(1) << "Failed to read system uuid, returning ephemeral uuid"; - return generateNewUUID(); - } else if (isPlaceholderHardwareUUID(hardware_uuid)) { - VLOG(1) << "Hardware uuid '" << hardware_uuid - << "' is a placeholder, returning ephemeral uuid"; - return generateNewUUID(); - } else { - return hardware_uuid; - } + return "Not supported"; } Status getInstanceUUID(std::string& ident) { diff --git a/src/osquery/filesystem/CMakeLists.txt b/src/osquery/filesystem/CMakeLists.txt deleted file mode 100644 index 5359404..0000000 --- a/src/osquery/filesystem/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License - -ADD_OSQUERY_LIBRARY(osquery_filesystem filesystem.cpp - mock_file_structure.cpp - linux/mem.cpp - linux/proc.cpp - posix/fileops.cpp) - -FILE(GLOB OSQUERY_FILESYSTEM_TESTS "tests/*.cpp") -ADD_OSQUERY_TEST(${OSQUERY_FILESYSTEM_TESTS}) diff --git a/src/osquery/filesystem/fileops.h b/src/osquery/filesystem/fileops.h deleted file mode 100644 index c7d439d..0000000 --- a/src/osquery/filesystem/fileops.h +++ /dev/null @@ -1,427 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#pragma once - -#include -#include -#include - -#ifdef WIN32 -#include -#include -#include -#else -#include -#endif - -#include -#include - -#include -#include -#include - -#include -#include - -namespace osquery { - -#ifdef WIN32 - -using mode_t = int; -using ssize_t = SSIZE_T; -using PlatformHandle = HANDLE; -using PlatformTimeType = FILETIME; - -// Windows do not define these by default -#define R_OK 4 -#define W_OK 2 -#define X_OK 1 - -// Windows does not define these constants, and they are neater -// than using raw octal for platformChmod, etc. -#define S_IRUSR 0400 -#define S_IWUSR 0200 -#define S_IXUSR 0100 -#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) - -#define S_IRGRP (S_IRUSR >> 3) -#define S_IWGRP (S_IWUSR >> 3) -#define S_IXGRP (S_IXUSR >> 3) -#define S_IRWXG (S_IRWXU >> 3) - -#define S_IROTH (S_IRGRP >> 3) -#define S_IWOTH (S_IWGRP >> 3) -#define S_IXOTH (S_IXGRP >> 3) -#define S_IRWXO (S_IRWXG >> 3) - -const std::map kDriveLetters{ - {0, "A:\\"}, {1, "B:\\"}, {2, "C:\\"}, {3, "D:\\"}, {4, "E:\\"}, - {5, "F:\\"}, {6, "G:\\"}, {7, "H:\\"}, {8, "I:\\"}, {9, "J:\\"}, - {10, "K:\\"}, {11, "L:\\"}, {12, "M:\\"}, {13, "N:\\"}, {14, "O:\\"}, - {15, "P:\\"}, {16, "Q:\\"}, {17, "R:\\"}, {18, "S:\\"}, {19, "T:\\"}, - {20, "U:\\"}, {21, "V:\\"}, {22, "W:\\"}, {23, "X:\\"}, {24, "Y:\\"}, - {25, "Z:\\"}, -}; - -typedef struct win_stat { - std::string path; - std::string filename; - int symlink; - std::string file_id; - LONGLONG inode; - unsigned long uid; - unsigned long gid; - std::string mode; - LONGLONG device; - LONGLONG size; - int block_size; - LONGLONG atime; - LONGLONG mtime; - LONGLONG ctime; - LONGLONG btime; - int hard_links; - std::string type; - std::string attributes; - std::string volume_serial; - std::string product_version; - -} WINDOWS_STAT; - -#else - -using PlatformHandle = int; -using PlatformTimeType = struct timeval; -#endif - -typedef struct { PlatformTimeType times[2]; } PlatformTime; - -/// Constant for an invalid handle. -const PlatformHandle kInvalidHandle = (PlatformHandle)-1; - -std::string lastErrorMessage(unsigned long); - -/** - * @brief File access modes for PlatformFile. - * - * A file can be opened for many access modes with a variety of different - * options on Windows and POSIX. To provide multi-platform support, we need to - * provide an abstraction that can cover the supported platforms. - */ - -#define PF_READ 0x0001 -#define PF_WRITE 0x0002 - -#define PF_OPTIONS_MASK 0x001c -#define PF_GET_OPTIONS(x) ((x & PF_OPTIONS_MASK) >> 2) -// Create new file only if it does not exist, or else fail. -#define PF_CREATE_NEW (0 << 2) -// If file exists truncate it, or else create new one. -#define PF_CREATE_ALWAYS (1 << 2) -// If file exists open it, or else fail. -#define PF_OPEN_EXISTING (2 << 2) -// If file exists open it, or else create new one. -#define PF_OPEN_ALWAYS (3 << 2) -#define PF_TRUNCATE (4 << 2) - -#define PF_NONBLOCK 0x0020 -#define PF_APPEND 0x0040 - -/** - * @brief Modes for seeking through a file. - * - * Provides a platform agnostic enumeration for file seek operations. These - * are translated to the appropriate flags for the underlying platform. - */ -enum SeekMode { PF_SEEK_BEGIN = 0, PF_SEEK_CURRENT, PF_SEEK_END }; - -#ifdef WIN32 -/// Takes a Windows FILETIME object and returns seconds since epoch -LONGLONG filetimeToUnixtime(const FILETIME& ft); - -LONGLONG longIntToUnixtime(LARGE_INTEGER& ft); - -std::string getFileAttribStr(unsigned long); - -Status platformStat(const boost::filesystem::path&, WINDOWS_STAT*); - -/** - * @brief Stores information about the last Windows async request - * - * Currently, we have rudimentary support for non-blocking operations on - * Windows. The implementation attempts to emulate POSIX non-blocking IO - * semantics using the Windows asynchronous API. As such, there are currently - * limitations. For example, opening a non-blocking file with read and write - * privileges may produce some problems. If a write operation does not - * immediately succeed, we cancel IO instead of waiting on it. As a result, - * on-going async read operations will get canceled and data might get lost. - * - * Windows-only class that deals with simulating POSIX asynchronous IO semantics - * using Windows API calls - */ -struct AsyncEvent { - AsyncEvent(); - ~AsyncEvent(); - - OVERLAPPED overlapped_{0}; - std::unique_ptr buffer_{nullptr}; - bool is_active_{false}; -}; - -/* - * @brief Converts a Windows short path to a full path - * - * This takes an 8.3 format path (i.e. C:\PROGRA~2\1PASSW~1\x64\AGILE1~1.DLL) - * and converts to a full path - * - * @param shortPath the short path - * @param rLongPath will be populated with the long path - * - * @return Success if successful, otherwise failure - */ -Status windowsShortPathToLongPath(const std::string& shortPath, - std::string& rLongPath); - -/* - * @brief Get the product version associated with a file - * - * @param path: Full path to the file - * @param rVersion: String representing the product version, e.g. "16.0.8201.0" - * - * @return Success if the version could be retrieved, otherwise failure - */ -Status windowsGetFileVersion(const std::string& path, std::string& rVersion); -#endif - -/** - * @brief Platform-agnostic file object. - * - * PlatformFile is a multi-platform class that offers input/output capabilities - * for files. - */ -class PlatformFile : private boost::noncopyable { - public: - explicit PlatformFile(const boost::filesystem::path& path, - int mode, - int perms = -1); - explicit PlatformFile(PlatformHandle handle) : handle_(handle) {} - - ~PlatformFile(); - - /// Checks to see if the file object is "special file". - bool isSpecialFile() const; - - /** - * @brief Checks to see if there are any pending IO operations. - * - * This is mostly used after a read()/write() error in non-blocking mode to - * determine the intention of the error. If read()/write() returns an error - * and hasPendingIo() is true, this indicates that the read()/write() - * operation didn't complete on time. - */ - bool hasPendingIo() const { - return has_pending_io_; - } - - /// Checks to see if the handle backing the PlatformFile object is valid. - bool isValid() const { - return (handle_ != kInvalidHandle); - } - - /// Returns the platform specific handle. - PlatformHandle nativeHandle() const { - return handle_; - } - - /** - * @brief Returns success if owner of the file is root. - * - * At the moment, we only determine that the owner of the current file is a - * member of the Administrators group. We do not count files owned by - * TrustedInstaller as owned by root. - */ - Status isOwnerRoot() const; - - /// Returns success if the owner of the file is the current user. - Status isOwnerCurrentUser() const; - - /// Determines whether the file has the executable bit set. - Status isExecutable() const; - - /** - * @brief Determines how immutable the file is to external modifications. - * - * Currently, this is only implemented on Windows. The Windows version of this - * function ensures that writes are explicitly denied for the file AND the - * file's parent directory. - */ - Status hasSafePermissions() const; - - /// Return the modified, created, birth, updated, etc times. - bool getFileTimes(PlatformTime& times); - - /// Change the file times. - bool setFileTimes(const PlatformTime& times); - - /// Read a number of bytes into a buffer. - ssize_t read(void* buf, size_t nbyte); - - /// Write a number of bytes from a buffer. - ssize_t write(const void* buf, size_t nbyte); - - /// Use the platform-specific seek. - off_t seek(off_t offset, SeekMode mode); - - /// Inspect the file size. - size_t size() const; - - private: - boost::filesystem::path fname_; - - /// The internal platform-specific open file handle. - PlatformHandle handle_{kInvalidHandle}; - - /// Is the file opened in a non-blocking read mode. - bool is_nonblock_{false}; - - /// Does the file have pending operations. - bool has_pending_io_{false}; - -#ifdef WIN32 - int cursor_{0}; - - AsyncEvent last_read_; - - ssize_t getOverlappedResultForRead(void* buf, size_t requested_size); -#endif -}; - -/** - * @brief Returns the current user's home directory. - * - * This uses multiple methods to find the current user's home directory. It - * attempts to use environment variables first and on failure, tries to obtain - * the path using platform specific functions. Returns a boost::none on the - * failure of both methods. - */ -boost::optional getHomeDirectory(); - -/** - * @brief Multi-platform implementation of chmod. - * @note There are issues with the ACL being ordered "incorrectly". This - * incorrect ordering does help with implementing the proper - * behaviors - * - * This function approximates the functionality of the POSIX chmod function on - * Windows. While there is the _chmod function on Windows, it does not support - * the user, group, world permissions model. The Windows version of this - * function will approximate it by using GetNamedSecurityInfoA to obtain the - * file's owner and group. World is represented by the Everyone group on - * Windows. Allowed permissions are represented by an access allowed access - * control entry and unset permissions are represented by an explicit access - * denied access control entry. However, the Windows preference for ACL ordering - * creates some problems. For instance, if a user wishes to protect a file by - * denying world access to a file, the normal standard for ACL ordering will end - * up denying everyone, including the user, to the file (because of the deny - * Everyone access control entry that is first in the ACL). To counter this, we - * have to be more creative with the ACL order which presents some problems for - * when attempting to modify permissions via File Explorer (complains of a - * mis-ordered ACL and offers to rectify the problem). - */ -bool platformChmod(const std::string& path, mode_t perms); - -/** - * @brief Sets 'safe' permissions for the database backing osquery - * - * @note Safe DB perms are equivalent to a chmod 0700 for root on posix - * so we emulate this by granting Full perms to SYSTEM and Administrators - * only. - */ -bool platformSetSafeDbPerms(const std::string& path); - -/** - * @brief Multi-platform implementation of glob. - * @note glob support is not 100% congruent with Linux glob. There are slight - * differences in how GLOB_TILDE and GLOB_BRACE are implemented. - * - * This function approximates the functionality of the POSIX glob function on - * Windows. It has naive support of GLOB_TILDE (doesn't support ~user syntax), - * GLOB_MARK, and GLOB_BRACE (custom translation of glob expressions to regex). - */ -std::vector platformGlob(const std::string& find_path); - -/** - * @brief Checks to see if the current user has the permissions to perform a - * specified operation on a file. - * - * This abstracts the POSIX access function across Windows and POSIX. On - * Windows, this calls the equivalent _access function. - */ -int platformAccess(const std::string& path, mode_t mode); - -/** - * @brief Checks to see if the provided directory is a temporary folder. - * @note This just compares the temporary directory path against the given path - * on Windows. - */ -Status platformIsTmpDir(const boost::filesystem::path& dir); - -/// Determines the accessibility and existence of the file path. -Status platformIsFileAccessible(const boost::filesystem::path& path); - -/// Determine if the FILE object points to a tty (console, serial port, etc). -bool platformIsatty(FILE* f); - -/// Opens a file and returns boost::none on error -boost::optional platformFopen(const std::string& filename, - const std::string& mode); - -/** - * @brief Checks for the existence of a named pipe or UNIX socket. - * - * This method is overloaded to perform two actions. If removal is requested - * the success is determined based on the non-existence or successful removal - * of the socket path. Otherwise the result is straightforward. - * - * The removal action is only used when extensions or the extension manager - * is first starting. - * - * @param path The filesystem path to a UNIX socket or Windows named pipe. - * @param remove_socket Attempt to remove the socket if it exists. - * - * @return Success if the socket exists and removal was not requested. False - * if the socket exists and removal was requested (and the attempt to remove - * had failed). - */ -Status socketExists(const boost::filesystem::path& path, - bool remove_socket = false); - -/** - * @brief Returns the OS root system directory. - * - * Some applications store configuration and application data inside of the - * Windows directory. This function retrieves the path to the current - * configurations Windows location. - * - * On POSIX systems this returns "/". - * - * @return boost::filesystem::path containing the OS root location. - */ -boost::filesystem::path getSystemRoot(); - -/** - * @brief Returns the successfully and fills d_stat if lstat was successful. - * - * - * On Windows systems this does not touch the structure. - * - * @return osquery::Status - */ -Status platformLstat(const std::string& path, struct stat& d_stat); -} diff --git a/src/osquery/filesystem/filesystem.cpp b/src/osquery/filesystem/filesystem.cpp deleted file mode 100644 index 3922c94..0000000 --- a/src/osquery/filesystem/filesystem.cpp +++ /dev/null @@ -1,571 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#include - -#include -#include - -#ifndef WIN32 -#include -#include -#include -#endif - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -namespace pt = boost::property_tree; -namespace fs = boost::filesystem; -namespace errc = boost::system::errc; - -namespace osquery { -static const size_t kMaxRecursiveGlobs = 64; - -Status writeTextFile(const fs::path& path, - const std::string& content, - int permissions, - int mode) { - // Open the file with the request permissions. - PlatformFile output_fd(path, mode, permissions); - if (!output_fd.isValid()) { - return Status(1, "Could not create file: " + path.string()); - } - - // If the file existed with different permissions before our open - // they must be restricted. - if (!platformChmod(path.string(), permissions)) { - // Could not change the file to the requested permissions. - return Status(1, "Failed to change permissions for file: " + path.string()); - } - - ssize_t bytes = output_fd.write(content.c_str(), content.size()); - if (static_cast(bytes) != content.size()) { - return Status(1, "Failed to write contents to file: " + path.string()); - } - - return Status::success(); -} - -struct OpenReadableFile : private boost::noncopyable { - public: - explicit OpenReadableFile(const fs::path& path, bool blocking = false) { - int mode = PF_OPEN_EXISTING | PF_READ; - if (!blocking) { - mode |= PF_NONBLOCK; - } - - // Open the file descriptor and allow caller to perform error checking. - fd.reset(new PlatformFile(path, mode)); - } - - public: - std::unique_ptr fd{nullptr}; -}; - -Status readFile(const fs::path& path, - size_t size, - size_t block_size, - bool dry_run, - bool preserve_time, - std::function predicate, - bool blocking) { - OpenReadableFile handle(path, blocking); - if (handle.fd == nullptr || !handle.fd->isValid()) { - return Status(1, "Cannot open file for reading: " + path.string()); - } - - off_t file_size = static_cast(handle.fd->size()); - if (handle.fd->isSpecialFile() && size > 0) { - file_size = static_cast(size); - } - - // Apply the max byte-read based on file/link target ownership. - auto read_max = static_cast(2048); - if (file_size > read_max) { - if (!dry_run) { - LOG(WARNING) << "Cannot read file that exceeds size limit: " - << path.string(); - VLOG(1) << "Cannot read " << path.string() - << " size exceeds limit: " << file_size << " > " << read_max; - } - return Status(1, "File exceeds read limits"); - } - - if (dry_run) { - // The caller is only interested in performing file read checks. - boost::system::error_code ec; - try { - return Status(0, fs::canonical(path, ec).string()); - } catch (const boost::filesystem::filesystem_error& err) { - return Status(1, err.what()); - } - } - - PlatformTime times; - handle.fd->getFileTimes(times); - - off_t total_bytes = 0; - if (file_size == 0 || block_size > 0) { - // Reset block size to a sane minimum. - block_size = (block_size < 4096) ? 4096 : block_size; - ssize_t part_bytes = 0; - bool overflow = false; - do { - std::string part(block_size, '\0'); - part_bytes = handle.fd->read(&part[0], block_size); - if (part_bytes > 0) { - total_bytes += static_cast(part_bytes); - if (total_bytes >= read_max) { - return Status(1, "File exceeds read limits"); - } - if (file_size > 0 && total_bytes > file_size) { - overflow = true; - part_bytes -= (total_bytes - file_size); - } - predicate(part, part_bytes); - } - } while (part_bytes > 0 && !overflow); - } else { - std::string content(file_size, '\0'); - do { - auto part_bytes = - handle.fd->read(&content[total_bytes], file_size - total_bytes); - if (part_bytes > 0) { - total_bytes += static_cast(part_bytes); - } - } while (handle.fd->hasPendingIo()); - predicate(content, file_size); - } - - return Status::success(); -} - -Status readFile(const fs::path& path, - std::string& content, - size_t size, - bool dry_run, - bool preserve_time, - bool blocking) { - return readFile(path, - size, - 4096, - dry_run, - preserve_time, - ([&content](std::string& buffer, size_t _size) { - if (buffer.size() == _size) { - content += std::move(buffer); - } else { - content += buffer.substr(0, _size); - } - }), - blocking); -} - -Status readFile(const fs::path& path, bool blocking) { - std::string blank; - return readFile(path, blank, 0, true, false, blocking); -} - -Status forensicReadFile(const fs::path& path, - std::string& content, - bool blocking) { - return readFile(path, content, 0, false, true, blocking); -} - -Status isWritable(const fs::path& path, bool effective) { - auto path_exists = pathExists(path); - if (!path_exists.ok()) { - return path_exists; - } - - if (effective) { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_WRITE); - return Status(fd.isValid() ? 0 : 1); - } else if (platformAccess(path.string(), W_OK) == 0) { - return Status::success(); - } - - return Status(1, "Path is not writable: " + path.string()); -} - -Status isReadable(const fs::path& path, bool effective) { - auto path_exists = pathExists(path); - if (!path_exists.ok()) { - return path_exists; - } - - if (effective) { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - return Status(fd.isValid() ? 0 : 1); - } else if (platformAccess(path.string(), R_OK) == 0) { - return Status::success(); - } - - return Status(1, "Path is not readable: " + path.string()); -} - -Status pathExists(const fs::path& path) { - boost::system::error_code ec; - if (path.empty()) { - return Status(1, "-1"); - } - - // A tri-state determination of presence - if (!fs::exists(path, ec) || ec.value() != errc::success) { - return Status(1, ec.message()); - } - return Status::success(); -} - -Status movePath(const fs::path& from, const fs::path& to) { - boost::system::error_code ec; - if (from.empty() || to.empty()) { - return Status(1, "Cannot copy empty paths"); - } - - fs::rename(from, to, ec); - if (ec.value() != errc::success) { - return Status(1, ec.message()); - } - return Status(0); -} - -Status removePath(const fs::path& path) { - boost::system::error_code ec; - auto removed_files = fs::remove_all(path, ec); - if (ec.value() != errc::success) { - return Status(1, ec.message()); - } - return Status(0, std::to_string(removed_files)); -} - -static bool checkForLoops(std::set& dsym_inos, std::string path) { - if (path.empty() || path.back() != '/') { - return false; - } - - path.pop_back(); - struct stat d_stat; - // On Windows systems (lstat not implemented) this immiedately returns - if (!platformLstat(path, d_stat).ok()) { - return false; - } - - if ((d_stat.st_mode & 0170000) == 0) { - return false; - } - - if (dsym_inos.find(d_stat.st_ino) == dsym_inos.end()) { - dsym_inos.insert(d_stat.st_ino); - } else { - LOG(WARNING) << "Symlink loop detected possibly involving: " << path; - return true; - } - return false; -} - -static void genGlobs(std::string path, - std::vector& results, - GlobLimits limits) { - // Use our helped escape/replace for wildcards. - replaceGlobWildcards(path, limits); - // inodes of directory symlinks for loop detection - std::set dsym_inos; - - // Generate a glob set and recurse for double star. - for (size_t glob_index = 0; ++glob_index < kMaxRecursiveGlobs;) { - auto glob_results = platformGlob(path); - - for (auto& result_path : glob_results) { - results.push_back(result_path); - - if (checkForLoops(dsym_inos, result_path)) { - glob_index = kMaxRecursiveGlobs; - } - } - - // The end state is a non-recursive ending or empty set of matches. - size_t wild = path.rfind("**"); - // Allow a trailing slash after the double wild indicator. - if (glob_results.size() == 0 || wild > path.size() || - wild + 3 < path.size()) { - break; - } - - path += "/**"; - } - - // Prune results based on settings/requested glob limitations. - auto end = std::remove_if( - results.begin(), results.end(), [limits](const std::string& found) { - return !(((found[found.length() - 1] == '/' || - found[found.length() - 1] == '\\') && - limits & GLOB_FOLDERS) || - ((found[found.length() - 1] != '/' && - found[found.length() - 1] != '\\') && - limits & GLOB_FILES)); - }); - results.erase(end, results.end()); -} - -Status resolveFilePattern(const fs::path& fs_path, - std::vector& results) { - return resolveFilePattern(fs_path, results, GLOB_ALL); -} - -Status resolveFilePattern(const fs::path& fs_path, - std::vector& results, - GlobLimits setting) { - genGlobs(fs_path.string(), results, setting); - return Status::success(); -} - -inline void replaceGlobWildcards(std::string& pattern, GlobLimits limits) { - // Replace SQL-wildcard '%' with globbing wildcard '*'. - if (pattern.find('%') != std::string::npos) { - boost::replace_all(pattern, "%", "*"); - } - - // Relative paths are a bad idea, but we try to accommodate. - if ((pattern.size() == 0 || ((pattern[0] != '/' && pattern[0] != '\\') && - (pattern.size() > 3 && pattern[1] != ':' && - pattern[2] != '\\' && pattern[2] != '/'))) && - pattern[0] != '~') { - try { - boost::system::error_code ec; - pattern = (fs::current_path(ec) / pattern).make_preferred().string(); - } catch (const fs::filesystem_error& /* e */) { - // There is a bug in versions of current_path that still throw. - } - } - - auto base = - fs::path(pattern.substr(0, pattern.find('*'))).make_preferred().string(); - - if (base.size() > 0) { - boost::system::error_code ec; - auto canonicalized = ((limits & GLOB_NO_CANON) == 0) - ? fs::canonical(base, ec).make_preferred().string() - : base; - - if (canonicalized.size() > 0 && canonicalized != base) { - if (isDirectory(canonicalized)) { - // Canonicalized directory paths will not include a trailing '/'. - // However, if the wildcards are applied to files within a directory - // then the missing '/' changes the wildcard meaning. - canonicalized += '/'; - } - // We are unable to canonicalize the meaning of post-wildcard limiters. - pattern = fs::path(canonicalized + pattern.substr(base.size())) - .make_preferred() - .string(); - } - } -} - -inline Status listInAbsoluteDirectory(const fs::path& path, - std::vector& results, - GlobLimits limits) { - if (path.filename() == "*" && !pathExists(path.parent_path())) { - return Status(1, "Directory not found: " + path.parent_path().string()); - } - - if (path.filename() == "*" && !isDirectory(path.parent_path())) { - return Status(1, "Path not a directory: " + path.parent_path().string()); - } - - genGlobs(path.string(), results, limits); - return Status::success(); -} - -Status listFilesInDirectory(const fs::path& path, - std::vector& results, - bool recursive) { - return listInAbsoluteDirectory( - (path / ((recursive) ? "**" : "*")), results, GLOB_FILES); -} - -Status listDirectoriesInDirectory(const fs::path& path, - std::vector& results, - bool recursive) { - return listInAbsoluteDirectory( - (path / ((recursive) ? "**" : "*")), results, GLOB_FOLDERS); -} - -Status isDirectory(const fs::path& path) { - boost::system::error_code ec; - if (fs::is_directory(path, ec)) { - return Status::success(); - } - - // The success error code is returned for as a failure (undefined error) - // We need to flip that into an error, a success would have falling through - // in the above conditional. - if (ec.value() == errc::success) { - return Status(1, "Path is not a directory: " + path.string()); - } - return Status(ec.value(), ec.message()); -} - -Status createDirectory(const boost::filesystem::path& dir_path, - bool const recursive, - bool const ignore_existence) { - auto err = boost::system::error_code{}; - bool is_created = false; - if (recursive) { - is_created = boost::filesystem::create_directories(dir_path, err); - } else { - is_created = boost::filesystem::create_directory(dir_path, err); - } - if (is_created) { - return Status::success(); - } - if (ignore_existence && isDirectory(dir_path).ok()) { - return Status::success(); - } - auto msg = std::string{"Could not create directory \""}; - msg += dir_path.string(); - msg += '"'; - if (err) { - msg += ": "; - msg += err.message(); - } - return Status::failure(msg); -} - -std::set getHomeDirectories() { - std::set results; - - auto users = SQL::selectAllFrom("users"); - for (const auto& user : users) { - if (user.at("directory").size() > 0) { - results.insert(user.at("directory")); - } - } - - return results; -} - -bool safePermissions(const fs::path& dir, - const fs::path& path, - bool executable) { - if (!platformIsFileAccessible(path).ok()) { - // Path was not real, had too may links, or could not be accessed. - return false; - } - - Status result = platformIsTmpDir(dir); - if (!result.ok() && result.getCode() < 0) { - // An error has occurred in stat() on dir, most likely because the file path - // does not exist - return false; - } else if (result.ok()) { - // Do not load modules from /tmp-like directories. - return false; - } - - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - if (!fd.isValid()) { - return false; - } - - result = isDirectory(path); - if (!result.ok() && result.getCode() < 0) { - // Something went wrong when determining the file's directoriness - return false; - } else if (result.ok()) { - // Only load file-like nodes (not directories). - return false; - } - - if (fd.isOwnerRoot().ok() || fd.isOwnerCurrentUser().ok()) { - result = fd.isExecutable(); - - // Otherwise, require matching or root file ownership. - if (executable && (result.getCode() > 0 || !fd.hasSafePermissions().ok())) { - // Require executable, implies by the owner. - return false; - } - - return true; - } - - // Do not load modules not owned by the user. - return false; -} - -const std::string& osqueryHomeDirectory() { - static std::string homedir; - - if (homedir.size() == 0) { - // Try to get the caller's home directory - boost::system::error_code ec; - auto userdir = getHomeDirectory(); - if (userdir.is_initialized() && isWritable(*userdir).ok()) { - auto osquery_dir = (fs::path(*userdir) / ".osquery"); - if (isWritable(osquery_dir) || - boost::filesystem::create_directories(osquery_dir, ec)) { - homedir = osquery_dir.make_preferred().string(); - return homedir; - } - } - - // Fail over to a temporary directory (used for the shell). - auto temp = - fs::temp_directory_path(ec) / - (std::string("osquery-") + std::to_string((rand() % 10000) + 20000)); - boost::filesystem::create_directories(temp, ec); - homedir = temp.make_preferred().string(); - } - - return homedir; -} - -std::string lsperms(int mode) { - static const char rwx[] = {'0', '1', '2', '3', '4', '5', '6', '7'}; - std::string bits; - - bits += rwx[(mode >> 9) & 7]; - bits += rwx[(mode >> 6) & 7]; - bits += rwx[(mode >> 3) & 7]; - bits += rwx[(mode >> 0) & 7]; - return bits; -} - -Status parseJSON(const fs::path& path, pt::ptree& tree) { - std::string json_data; - if (!readFile(path, json_data).ok()) { - return Status(1, "Could not read JSON from file"); - } - - return parseJSONContent(json_data, tree); -} - -Status parseJSONContent(const std::string& content, pt::ptree& tree) { - // Read the extensions data into a JSON blob, then property tree. - try { - std::stringstream json_stream; - json_stream << content; - pt::read_json(json_stream, tree); - } catch (const pt::json_parser::json_parser_error& /* e */) { - return Status(1, "Could not parse JSON from file"); - } - return Status::success(); -} -} // namespace osquery diff --git a/src/osquery/filesystem/filesystem.h b/src/osquery/filesystem/filesystem.h deleted file mode 100644 index 6e68080..0000000 --- a/src/osquery/filesystem/filesystem.h +++ /dev/null @@ -1,363 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#pragma once - -#include - -#include -#include -#include -#include - -#include -#include - -namespace osquery { - -class Status; - -/// Globbing directory traversal function recursive limit. -enum GlobLimits : size_t { - GLOB_FILES = 0x1, - GLOB_FOLDERS = 0x2, - GLOB_ALL = GLOB_FILES | GLOB_FOLDERS, - GLOB_NO_CANON = 0x4, -}; - -inline GlobLimits operator|(GlobLimits a, GlobLimits b) { - return static_cast(static_cast(a) | - static_cast(b)); -} - -/// Globbing wildcard character. -const std::string kSQLGlobWildcard{"%"}; - -/// Globbing wildcard recursive character (double wildcard). -const std::string kSQLGlobRecursive{kSQLGlobWildcard + kSQLGlobWildcard}; - -/** - * @brief Read a file from disk. - * - * @param path the path of the file that you would like to read. - * @param size Number of bytes to read from file. - * @param content a reference to a string which will be populated with the - * contents of the path indicated by the path parameter. - * @param dry_run do not actually read the file content. - * @param preserve_time Attempt to preserve file mtime and atime. - * @param blocking Request a blocking read. - * - * @return an instance of Status, indicating success or failure. - */ -Status readFile(const boost::filesystem::path& path, - std::string& content, - size_t size = 0, - bool dry_run = false, - bool preserve_time = false, - bool blocking = false); - -/// Read a file and preserve the atime and mtime. -Status forensicReadFile(const boost::filesystem::path& path, - std::string& content, - bool blocking = false); - -/** - * @brief Return the status of an attempted file read. - * - * @param path the path of the file that you would like to read. - * @param blocking Request a blocking read. - * - * @return success iff the file would have been read. On success the status - * message is the complete/absolute path. - */ -Status readFile(const boost::filesystem::path& path, bool blocking = false); - -/// Internal representation for predicate-based chunk reading. -Status readFile(const boost::filesystem::path& path, - size_t size, - size_t block_size, - bool dry_run, - bool preserve_time, - std::function predicate, - bool blocking = false); - -/** - * @brief Write text to disk. - * - * @param path the path of the file that you would like to write. - * @param content the text that should be written exactly to disk. - * @param permissions the filesystem permissions to request when opening. - * @param mode to open file with - * - * @return an instance of Status, indicating success or failure. - */ -Status writeTextFile(const boost::filesystem::path& path, - const std::string& content, - int permissions = 0660, - int mode = PF_OPEN_ALWAYS | PF_WRITE | PF_APPEND); - -/** - * @brief Check if a path is writable. - * - * @param path The path of the file that you would like to write. - * @param effective If you would like to check using effective UID - * - * @return A status returning if it's writable - */ -Status isWritable(const boost::filesystem::path& path, bool effective = false); - -/** - * @brief Check if a path is readable. - * - * @param path The path of the file that you would like to read. - * @param effective If you would like to check using effective UID - * - * @return A status returning if it's readable - */ -Status isReadable(const boost::filesystem::path& path, bool effective = false); - -/** - * @brief A helper to check if a path exists on disk or not. - * - * @param path Target path. - * - * @return The code of the Status instance will be -1 if no input was supplied, - * assuming the caller is not aware of how to check path-getter results. - * The code will be 0 if the path does not exist on disk and 1 if the path - * does exist on disk. - */ -Status pathExists(const boost::filesystem::path& path); - -/** - * @brief List all of the files in a specific directory. - * - * @param path the path which you would like to list. - * @param results a non-const reference to a vector which will be populated - * with the directory listing of the path param, assuming that all operations - * completed successfully. - * @param recursive should the listing descend recursively into the directory. - * - * @return an instance of Status, indicating success or failure. - */ -Status listFilesInDirectory(const boost::filesystem::path& path, - std::vector& results, - bool recursive = false); - -/** - * @brief List all of the directories in a specific directory, non-recursively. - * - * @param path the path which you would like to list - * @param results a non-const reference to a vector which will be populated - * with the directory listing of the path param, assuming that all operations - * completed successfully. - * @param recursive should the listing descend recursively into the directory. - * - * @return an instance of Status, indicating success or failure. - */ -Status listDirectoriesInDirectory(const boost::filesystem::path& path, - std::vector& results, - bool recursive = false); - -/** - * @brief Given a filesystem globbing patten, resolve all matching paths. - * - * @code{.cpp} - * std::vector results; - * auto s = resolveFilePattern("/Users/marpaia/Downloads/%", results); - * if (s.ok()) { - * for (const auto& result : results) { - * LOG(INFO) << result; - * } - * } - * @endcode - * - * @param pattern filesystem globbing pattern. - * @param results output vector of matching paths. - * - * @return an instance of Status, indicating success or failure. - */ -Status resolveFilePattern(const boost::filesystem::path& pattern, - std::vector& results); - -/** - * @brief Given a filesystem globbing patten, resolve all matching paths. - * - * See resolveFilePattern, but supply a limitation to request only directories - * or files that match the path. - * - * @param pattern filesystem globbing pattern. - * @param results output vector of matching paths. - * @param setting a bit list of match types, e.g., files, folders. - * - * @return an instance of Status, indicating success or failure. - */ -Status resolveFilePattern(const boost::filesystem::path& pattern, - std::vector& results, - GlobLimits setting); - -/** - * @brief Transform a path with SQL wildcards to globbing wildcard. - * - * SQL uses '%' as a wildcard matching token, and filesystem globbing uses '*'. - * In osquery-internal methods the filesystem character is used. This helper - * method will perform the correct preg/escape and replace. - * - * This has a side effect of canonicalizing paths up to the first wildcard. - * For example: /tmp/% becomes /private/tmp/% on OS X systems. And /tmp/%. - * - * @param pattern the input and output filesystem glob pattern. - * @param limits osquery::GlobLimits to apply (currently only recognizes - * osquery::GLOB_NO_CANON) - */ -void replaceGlobWildcards(std::string& pattern, GlobLimits limits = GLOB_ALL); - -/// Attempt to remove a directory path. -Status removePath(const boost::filesystem::path& path); - -/// Move a file or directory to another path. -Status movePath(const boost::filesystem::path& from, - const boost::filesystem::path& to); - -/** - * @brief Check if an input path is a directory. - * - * @param path input path, either a filename or directory. - * - * @return If the input path was a directory. - */ -Status isDirectory(const boost::filesystem::path& path); - -/** - * @brief Create the directory - * - * @param path to the intended directory - * @param recursive - make parent directories as needed - * @param ignore_existence - no error if directory already exists - * - * @return Status of operation - */ -Status createDirectory(const boost::filesystem::path& path, - bool recursive = false, - bool ignore_existence = false); - -/** - * @brief Return a vector of all home directories on the system. - * - * @return a vector of string paths containing all home directories. - */ -std::set getHomeDirectories(); - -/** - * @brief Check the permissions of a file and its directory. - * - * 'Safe' implies the directory is not a /tmp-like directory in that users - * cannot control super-user-owner files. The file should be owned by the - * process's UID or the file should be owned by root. - * - * @param dir the directory to check `/tmp` mode. - * @param path a path to a file to check. - * @param executable true if the file must also be executable. - * - * @return true if the file is 'safe' else false. - */ -bool safePermissions(const boost::filesystem::path& dir, - const boost::filesystem::path& path, - bool executable = false); - -/** - * @brief osquery may use local storage in a user-protected "home". - * - * Return a standard path to an "osquery" home directory. This path may store - * a protected extensions socket, backing storage database, and debug logs. - */ -const std::string& osqueryHomeDirectory(); - -/// Return bit-mask-style permissions. -std::string lsperms(int mode); - -/** - * @brief Parse a JSON file on disk into a property tree. - * - * @param path the path of the JSON file. - * @param tree output property tree. - * - * @return an instance of Status, indicating success or failure if malformed. - */ -Status parseJSON(const boost::filesystem::path& path, - boost::property_tree::ptree& tree); - -/** - * @brief Parse JSON content into a property tree. - * - * @param content JSON string data. - * @param tree output property tree. - * - * @return an instance of Status, indicating success or failure if malformed. - */ -Status parseJSONContent(const std::string& content, - boost::property_tree::ptree& tree); - -#ifdef __linux__ -/** - * @brief Iterate over `/proc` process, returns a list of pids. - * - * @param processes output list of process pids as strings (int paths in proc). - * - * @return an instance of Status, indicating success or failure. - */ -Status procProcesses(std::set& processes); - -/** - * @brief Iterate over a `/proc` process's descriptors, return a list of fds. - * - * @param process a string pid from proc. - * @param descriptors output list of descriptor numbers as strings. - * - * @return status of iteration, failure if the process path did not exist. - */ -Status procDescriptors(const std::string& process, - std::map& descriptors); - -/** - * @brief Read a descriptor's virtual path. - * - * @param process a string pid from proc. - * @param descriptor a string descriptor number for a proc. - * @param result output variable with value of link. - * - * @return status of read, failure on permission error or filesystem error. - */ -Status procReadDescriptor(const std::string& process, - const std::string& descriptor, - std::string& result); - -/** - * @brief Read bytes from Linux's raw memory. - * - * Most Linux kernels include a device node /dev/mem that allows privileged - * users to map or seek/read pages of physical memory. - * osquery discourages the use of physical memory reads for security and - * performance reasons and must first try safer methods for data parsing - * such as /sys and /proc. - * - * A platform user may disable physical memory reads: - * --disable_memory=true - * This flag/option will cause readRawMemory to forcefully fail. - * - * @param base The absolute memory address to read from. This does not need - * to be page aligned, readRawMem will take care of alignment and only - * return the requested start address and size. - * @param length The length of the buffer with a max of 0x10000. - * @param buffer The output buffer, caller is responsible for resources if - * readRawMem returns success. - * @return status The status of the read. - */ -Status readRawMem(size_t base, size_t length, void** buffer); -#endif - -} // namespace osquery diff --git a/src/osquery/filesystem/linux/mem.cpp b/src/osquery/filesystem/linux/mem.cpp deleted file mode 100644 index 645b1f2..0000000 --- a/src/osquery/filesystem/linux/mem.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#include -#include - -#include -#include -#include - -#include -#include - -namespace osquery { - -#define kLinuxMaxMemRead 0x10000 - -const std::string kLinuxMemPath = "/dev/mem"; - -Status readMem(int fd, size_t base, size_t length, uint8_t* buffer) { - if (lseek(fd, base, SEEK_SET) == -1) { - return Status(1, "Cannot seek to physical base"); - } - - // Read from raw memory until an unrecoverable read error or the all of the - // requested bytes are read. - size_t total_read = 0; - ssize_t bytes_read = -1; - while (total_read != length && bytes_read != 0) { - bytes_read = read(fd, buffer + total_read, length - total_read); - if (bytes_read == -1) { - if (errno != EINTR) { - return Status(1, "Cannot read requested length"); - } - } else { - total_read += bytes_read; - } - } - - // The read call finished without reading the requested number of bytes. - if (total_read != length) { - return Status(1, "Read incorrect number of bytes"); - } - - return Status::success(); -} - -Status readRawMem(size_t base, size_t length, void** buffer) { - *buffer = 0; - - if (length > kLinuxMaxMemRead) { - return Status(1, "Cowardly refusing to read a large number of bytes"); - } - - auto status = isReadable(kLinuxMemPath); - if (!status.ok()) { - // For non-su users *hopefully* raw memory is not readable. - return status; - } - - int fd = open(kLinuxMemPath.c_str(), O_RDONLY); - if (fd < 0) { - return Status(1, std::string("Cannot open ") + kLinuxMemPath); - } - - if ((*buffer = malloc(length)) == nullptr) { - close(fd); - return Status(1, "Cannot allocate memory for read"); - } - -#ifdef _SC_PAGESIZE - size_t offset = base % sysconf(_SC_PAGESIZE); -#else - // getpagesize() is more or less deprecated. - size_t offset = base % getpagesize(); -#endif - - // Use memmap for maximum portability over read(). - auto map = mmap(0, offset + length, PROT_READ, MAP_SHARED, fd, base - offset); - if (map == MAP_FAILED) { - // Could fallback to a lseek/read. - if (!readMem(fd, base, length, (uint8_t*)*buffer).ok()) { - close(fd); - free(*buffer); - *buffer = nullptr; - return Status(1, "Cannot memory map or seek/read memory"); - } - } else { - // Memory map succeeded, copy and unmap. - memcpy(*buffer, (uint8_t*)map + offset, length); - if (munmap(map, offset + length) == -1) { - LOG(WARNING) << "Unable to unmap raw memory"; - } - } - - close(fd); - return Status::success(); -} -} diff --git a/src/osquery/filesystem/linux/proc.cpp b/src/osquery/filesystem/linux/proc.cpp deleted file mode 100644 index 5a10b76..0000000 --- a/src/osquery/filesystem/linux/proc.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#include -#include - -#include - -#include -#include -#include -#include - - -namespace osquery { -const std::vector kUserNamespaceList = { - "cgroup", "ipc", "mnt", "net", "pid", "user", "uts"}; - -Status procGetNamespaceInode(ino_t& inode, - const std::string& namespace_name, - const std::string& process_namespace_root) { - inode = 0; - - auto path = process_namespace_root + "/" + namespace_name; - - char link_destination[PATH_MAX] = {}; - auto link_dest_length = readlink(path.data(), link_destination, PATH_MAX - 1); - if (link_dest_length < 0) { - return Status(1, "Failed to retrieve the inode for namespace " + path); - } - - // The link destination must be in the following form: namespace:[inode] - if (std::strncmp(link_destination, - namespace_name.data(), - namespace_name.size()) != 0 || - std::strncmp(link_destination + namespace_name.size(), ":[", 2) != 0) { - return Status(1, "Invalid descriptor for namespace " + path); - } - - // Parse the inode part of the string; strtoull should return us a pointer - // to the closing square bracket - const char* inode_string_ptr = link_destination + namespace_name.size() + 2; - char* square_bracket_ptr = nullptr; - - inode = static_cast( - std::strtoull(inode_string_ptr, &square_bracket_ptr, 10)); - if (inode == 0 || square_bracket_ptr == nullptr || - *square_bracket_ptr != ']') { - return Status(1, "Invalid inode value in descriptor for namespace " + path); - } - - return Status::success(); -} - -Status procGetProcessNamespaces(const std::string& process_id, - ProcessNamespaceList& namespace_list, - std::vector namespaces) { - namespace_list.clear(); - - if (namespaces.empty()) { - namespaces = kUserNamespaceList; - } - - auto process_namespace_root = kLinuxProcPath + "/" + process_id + "/ns"; - - for (const auto& namespace_name : namespaces) { - ino_t namespace_inode; - auto status = procGetNamespaceInode( - namespace_inode, namespace_name, process_namespace_root); - if (!status.ok()) { - continue; - } - - namespace_list[namespace_name] = namespace_inode; - } - - return Status::success(); -} - -std::string procDecodeAddressFromHex(const std::string& encoded_address, - int family) { - char addr_buffer[INET6_ADDRSTRLEN] = {0}; - if (family == AF_INET) { - struct in_addr decoded; - if (encoded_address.length() == 8) { - sscanf(encoded_address.c_str(), "%X", &(decoded.s_addr)); - inet_ntop(AF_INET, &decoded, addr_buffer, INET_ADDRSTRLEN); - } - - } else if (family == AF_INET6) { - struct in6_addr decoded; - if (encoded_address.length() == 32) { - sscanf(encoded_address.c_str(), - "%8x%8x%8x%8x", - (unsigned int*)&(decoded.s6_addr[0]), - (unsigned int*)&(decoded.s6_addr[4]), - (unsigned int*)&(decoded.s6_addr[8]), - (unsigned int*)&(decoded.s6_addr[12])); - inet_ntop(AF_INET6, &decoded, addr_buffer, INET6_ADDRSTRLEN); - } - } - - return std::string(addr_buffer); -} - -unsigned short procDecodePortFromHex(const std::string& encoded_port) { - unsigned short decoded = 0; - if (encoded_port.length() == 4) { - sscanf(encoded_port.c_str(), "%hX", &decoded); - } - return decoded; -} - -static Status procGetSocketListInet(int family, - int protocol, - ino_t net_ns, - const std::string& path, - const std::string& content, - SocketInfoList& result) { - // The system's socket information is tokenized by line. - bool header = true; - for (const auto& line : osquery::split(content, "\n")) { - if (header) { - if (line.find("sl") != 0 && line.find("sk") != 0) { - return Status(1, std::string("Invalid file header for ") + path); - } - header = false; - continue; - } - - // The socket information is tokenized by spaces, each a field. - auto fields = osquery::split(line, " "); - if (fields.size() < 10) { - VLOG(1) << "Invalid socket descriptor found: '" << line - << "'. Skipping this entry"; - continue; - } - - // Two of the fields are the local/remote address/port pairs. - auto locals = osquery::split(fields[1], ":"); - auto remotes = osquery::split(fields[2], ":"); - - if (locals.size() != 2 || remotes.size() != 2) { - VLOG(1) << "Invalid socket descriptor found: '" << line - << "'. Skipping this entry"; - continue; - } - - SocketInfo socket_info = {}; - socket_info.socket = fields[9]; - socket_info.net_ns = net_ns; - socket_info.family = family; - socket_info.protocol = protocol; - socket_info.local_address = procDecodeAddressFromHex(locals[0], family); - socket_info.local_port = procDecodePortFromHex(locals[1]); - socket_info.remote_address = procDecodeAddressFromHex(remotes[0], family); - socket_info.remote_port = procDecodePortFromHex(remotes[1]); - - if (protocol == IPPROTO_TCP) { - char* null_terminator_ptr = nullptr; - auto integer_socket_state = - std::strtoull(fields[3].data(), &null_terminator_ptr, 16); - if (integer_socket_state == 0 || - integer_socket_state >= tcp_states.size() || - null_terminator_ptr == nullptr || *null_terminator_ptr != 0) { - socket_info.state = "UNKNOWN"; - } else { - socket_info.state = tcp_states[integer_socket_state]; - } - } - - result.push_back(std::move(socket_info)); - } - - return Status(0); -} - -static Status procGetSocketListUnix(ino_t net_ns, - const std::string& path, - const std::string& content, - SocketInfoList& result) { - // The system's socket information is tokenized by line. - bool header = true; - for (const auto& line : osquery::split(content, "\n")) { - if (header) { - if (line.find("Num") != 0) { - return Status(1, std::string("Invalid file header for ") + path); - } - header = false; - continue; - } - - // The socket information is tokenized by spaces, each a field. - auto fields = osquery::split(line, " "); - if (fields.size() < 7) { - VLOG(1) << "Invalid UNIX socket descriptor found: '" << line - << "'. Skipping this entry"; - continue; - } - - SocketInfo socket_info = {}; - socket_info.socket = fields[6]; - socket_info.net_ns = net_ns; - socket_info.family = AF_UNIX; - socket_info.protocol = std::atoll(fields[2].data()); - socket_info.unix_socket_path = (fields.size() >= 8) ? fields[7] : ""; - - result.push_back(std::move(socket_info)); - } - - return Status(0); -} - -Status procGetSocketList(int family, - int protocol, - ino_t net_ns, - const std::string& pid, - SocketInfoList& result) { - std::string path = kLinuxProcPath + "/" + pid + "/net/"; - - switch (family) { - case AF_INET: - if (kLinuxProtocolNames.count(protocol) == 0) { - return Status(1, - "Invalid family " + std::to_string(protocol) + - " for AF_INET familiy"); - } else { - path += kLinuxProtocolNames.at(protocol); - } - break; - - case AF_INET6: - if (kLinuxProtocolNames.count(protocol) == 0) { - return Status(1, - "Invalid protocol " + std::to_string(protocol) + - " for AF_INET6 familiy"); - } else { - path += kLinuxProtocolNames.at(protocol) + "6"; - } - break; - - case AF_UNIX: - if (protocol != IPPROTO_IP) { - return Status(1, - "Invalid protocol " + std::to_string(protocol) + - " for AF_UNIX familiy"); - } else { - path += "unix"; - } - - break; - - default: - return Status(1, "Invalid family " + std::to_string(family)); - } - - std::string content; - if (!osquery::readFile(path, content).ok()) { - return Status(1, "Could not open socket information from " + path); - } - - Status status(0); - switch (family) { - case AF_INET: - case AF_INET6: - status = - procGetSocketListInet(family, protocol, net_ns, path, content, result); - break; - - case AF_UNIX: - status = procGetSocketListUnix(net_ns, path, content, result); - break; - } - - return status; -} - -Status procGetSocketInodeToProcessInfoMap(const std::string& pid, - SocketInodeToProcessInfoMap& result) { - auto callback = [](const std::string& _pid, - const std::string& fd, - const std::string& link, - SocketInodeToProcessInfoMap& _result) -> bool { - /* We only care about sockets. But there will be other descriptors. */ - if (link.find("socket:[") != 0) { - return true; - } - - std::string inode = link.substr(8, link.size() - 9); - _result[inode] = {_pid, fd}; - return true; - }; - - return procEnumerateProcessDescriptors( - pid, result, callback); -} - -Status procProcesses(std::set& processes) { - auto callback = [](const std::string& pid, - std::set& _processes) -> bool { - _processes.insert(pid); - return true; - }; - - return procEnumerateProcesses(processes, callback); -} - -Status procDescriptors(const std::string& process, - std::map& descriptors) { - auto callback = [](const std::string& pid, - const std::string& fd, - const std::string& link_name, - std::map& _descriptors) -> bool { - _descriptors[fd] = link_name; - return true; - }; - - return procEnumerateProcessDescriptors( - process, descriptors, callback); -} - -Status procReadDescriptor(const std::string& process, - const std::string& descriptor, - std::string& result) { - auto link = kLinuxProcPath + "/" + process + "/fd/" + descriptor; - - char result_path[PATH_MAX] = {0}; - auto size = readlink(link.c_str(), result_path, sizeof(result_path) - 1); - if (size >= 0) { - result = std::string(result_path); - return Status(0); - } else { - return Status(1, "Could not read path"); - } -} - -} // namespace osquery diff --git a/src/osquery/filesystem/linux/proc.h b/src/osquery/filesystem/linux/proc.h deleted file mode 100644 index 7d97750..0000000 --- a/src/osquery/filesystem/linux/proc.h +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#pragma once - -#include - -#include -#include -#include - -#include - -#include -#include -#include - -namespace osquery { -const std::string kLinuxProcPath = "/proc"; - -struct SocketInfo final { - std::string socket; - ino_t net_ns; - - int family{0}; - int protocol{0}; - - std::string local_address; - std::uint16_t local_port{0U}; - - std::string remote_address; - std::uint16_t remote_port{0U}; - - std::string unix_socket_path; - - std::string state; -}; -typedef std::vector SocketInfoList; - -struct SocketProcessInfo final { - std::string pid; - std::string fd; -}; -typedef std::map SocketInodeToProcessInfoMap; - -// Linux proc protocol define to net stats file name. -const std::map kLinuxProtocolNames = { - {IPPROTO_ICMP, "icmp"}, - {IPPROTO_TCP, "tcp"}, - {IPPROTO_UDP, "udp"}, - {IPPROTO_UDPLITE, "udplite"}, - {IPPROTO_RAW, "raw"}, -}; - -const std::vector tcp_states = {"UNKNOWN", - "ESTABLISHED", - "SYN_SENT", - "SYN_RECV", - "FIN_WAIT1", - "FIN_WAIT2", - "TIME_WAIT", - "CLOSED", - "CLOSE_WAIT", - "LAST_ACK", - "LISTEN", - "CLOSING"}; - -using ProcessNamespaceList = std::map; - -Status procGetProcessNamespaces( - const std::string& process_id, - ProcessNamespaceList& namespace_list, - std::vector namespaces = std::vector()); - -Status procReadDescriptor(const std::string& process, - const std::string& descriptor, - std::string& result); - -/// This function parses the inode value in the destination of a user namespace -/// symlink; fail if the namespace name is now what we expect -Status procGetNamespaceInode(ino_t& inode, - const std::string& namespace_name, - const std::string& process_namespace_root); - -std::string procDecodeAddressFromHex(const std::string& encoded_address, - int family); - -unsigned short procDecodePortFromHex(const std::string& encoded_port); - -/** - * @brief Construct a map of socket inode number to socket information collected - * from /proc//net for a certain family and protocol under a certain pid. - * - * The output parameter result is used as-is, i.e. it IS NOT cleared beforehand, - * so values will either be added or replace existing ones without check. - * - * @param family The socket family. One of AF_INET, AF_INET6 or AF_UNIX. - * @param protocol The socket protocol. For AF_INET and AF_INET6 one of the keys - * @param pid Query data for this pid. - * of kLinuxProtocolNames. For AF_UNIX only IPPROTO_IP is valid. - * @param result The output parameter. - */ -Status procGetSocketList(int family, - int protocol, - ino_t net_ns, - const std::string& pid, - SocketInfoList& result); - -/** - * @brief Construct a map of socket inode number to process information for the - * process that owns the socket by reading entries under /proc//fd. - * - * The output parameter result is used as-is, i.e. it IS NOT cleared beforehand, - * so values will either be added or replace existing ones without check. - * - * @param pid The process of interests - * @param result The output parameter. - */ -Status procGetSocketInodeToProcessInfoMap(const std::string& pid, - SocketInodeToProcessInfoMap& result); - -/** - * @brief Enumerate all pids in the system by listing pid numbers under /proc - * and execute a callback for each one of them. The callback will receive the - * pid and the user_data provided as argument. - * - * Notice there isn't any type of locking here so race conditions might occur, - * e.g. a process is destroyed right before the callback being called. - * - * The loop will stop after the first callback failed, i.e. returned false. - * - * @param user_data User provided data to be passed to the callback - * @param callback A pointer to the callback function - */ -template -Status procEnumerateProcesses(UserData& user_data, - bool (*callback)(const std::string&, UserData&)) { - boost::filesystem::directory_iterator it(kLinuxProcPath), end; - - try { - for (; it != end; ++it) { - if (!boost::filesystem::is_directory(it->status())) { - continue; - } - - // See #792: std::regex is incomplete until GCC 4.9 - const auto& pid = it->path().leaf().string(); - if (std::atoll(pid.data()) <= 0) { - continue; - } - - bool ret = callback(pid, user_data); - if (ret == false) { - break; - } - } - } catch (const boost::filesystem::filesystem_error& e) { - VLOG(1) << "Exception iterating Linux processes: " << e.what(); - return Status(1, e.what()); - } - - return Status(0); -} - -/** - * @brief Enumerate all file descriptors of a certain process identified by its - * pid by listing files under /proc//fd and execute a callback for each one - * of them. The callback will receive the pid the file descriptor and the real - * path the file descriptor links to, and the user_data provided as argument. - * - * Notice there isn't any type of locking here so race conditions might occur, - * e.g. a socket is closed right before the callback being called. - * - * The loop will stop after the first callback failed, i.e. returned false. - * - * @param pid The process id of interest - * @param user_data User provided data to be passed to the callback - * @param callback A pointer to the callback function - */ -template -Status procEnumerateProcessDescriptors(const std::string& pid, - UserData& user_data, - bool (*callback)(const std::string& pid, - const std::string& fd, - const std::string& link, - UserData& user_data)) { - std::string descriptors_path = kLinuxProcPath + "/" + pid + "/fd"; - - try { - boost::filesystem::directory_iterator it(descriptors_path), end; - - for (; it != end; ++it) { - auto fd = it->path().leaf().string(); - - std::string link; - Status status = procReadDescriptor(pid, fd, link); - if (!status.ok()) { - VLOG(1) << "Failed to read the link for file descriptor " << fd - << " of pid " << pid << ". Data might be incomplete."; - } - - bool ret = callback(pid, fd, link, user_data); - if (ret == false) { - break; - } - } - } catch (boost::filesystem::filesystem_error& e) { - VLOG(1) << "Exception iterating process file descriptors: " << e.what(); - return Status(1, e.what()); - } - - return Status(0); -} - -} // namespace osquery diff --git a/src/osquery/filesystem/mock_file_structure.cpp b/src/osquery/filesystem/mock_file_structure.cpp deleted file mode 100644 index a4d9ee3..0000000 --- a/src/osquery/filesystem/mock_file_structure.cpp +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#include - -#include - -namespace osquery { - -namespace fs = boost::filesystem; - -fs::path createMockFileStructure() { - const auto root_dir = - fs::temp_directory_path() / - fs::unique_path("osquery.tests.%%%%.%%%%"); - fs::create_directories(root_dir / "toplevel/"); - fs::create_directories(root_dir / "toplevel/secondlevel1"); - fs::create_directories(root_dir / "toplevel/secondlevel2"); - fs::create_directories(root_dir / "toplevel/secondlevel3"); - fs::create_directories(root_dir / "toplevel/secondlevel3/thirdlevel1"); - fs::create_directories(root_dir / "deep11/deep2/deep3/"); - fs::create_directories(root_dir / "deep1/deep2/"); - writeTextFile(root_dir / "root.txt", "root"); - writeTextFile(root_dir / "door.txt", "toor", 0550); - writeTextFile(root_dir / "roto.txt", "roto"); - writeTextFile(root_dir / "deep1/level1.txt", "l1"); - writeTextFile(root_dir / "deep11/not_bash", "l1"); - writeTextFile(root_dir / "deep1/deep2/level2.txt", "l2"); - - writeTextFile(root_dir / "deep11/level1.txt", "l1"); - writeTextFile(root_dir / "deep11/deep2/level2.txt", "l2"); - writeTextFile(root_dir / "deep11/deep2/deep3/level3.txt", "l3"); - -#ifdef WIN32 - writeTextFile(root_dir / "root2.txt", "l1"); -#else - boost::system::error_code ec; - fs::create_symlink( - root_dir / "root.txt", root_dir / "root2.txt", ec); -#endif - return root_dir; -} - -} // namespace osquery diff --git a/src/osquery/filesystem/mock_file_structure.h b/src/osquery/filesystem/mock_file_structure.h deleted file mode 100644 index 82375d4..0000000 --- a/src/osquery/filesystem/mock_file_structure.h +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#pragma once - -#include - -namespace osquery { - -// generate a small directory structure for testing -boost::filesystem::path createMockFileStructure(); - -} // namespace diff --git a/src/osquery/filesystem/posix/fileops.cpp b/src/osquery/filesystem/posix/fileops.cpp deleted file mode 100644 index 0892aa1..0000000 --- a/src/osquery/filesystem/posix/fileops.cpp +++ /dev/null @@ -1,379 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -namespace fs = boost::filesystem; -namespace errc = boost::system::errc; - -namespace osquery { - -PlatformFile::PlatformFile(const fs::path& path, int mode, int perms) - : fname_(path) { - 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(fname_, ec) || ec.value() != errc::success)) { - handle_ = kInvalidHandle; - } else { - handle_ = ::open(fname_.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::success(); - } - 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::success(); - } - - 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::success(); - } - - 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::success(); - } - - 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 platformSetSafeDbPerms(const std::string& path) { - return platformChmod(path, S_IRWXU); -} - -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::success(); - } - - 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::success(); -} - -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 && !removePath(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("/"); -} - -Status platformLstat(const std::string& path, struct stat& d_stat) { - if (::lstat(path.c_str(), &d_stat) < 0) { - return Status(1); - } - return Status(0); -} -} diff --git a/src/osquery/filesystem/tests/fileops.cpp b/src/osquery/filesystem/tests/fileops.cpp deleted file mode 100644 index d0fdce0..0000000 --- a/src/osquery/filesystem/tests/fileops.cpp +++ /dev/null @@ -1,770 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#include - -#include - -#include -#include - -#include - -#include - - -namespace fs = boost::filesystem; - -namespace osquery { - -class FileOpsTests : public testing::Test { - protected: - fs::path fake_directory_; - - void SetUp() override { - fake_directory_ = createMockFileStructure(); - } - - void TearDown() override { - fs::remove_all(fake_directory_); - } - - bool globResultsMatch(const std::vector& results, - const std::vector& expected) { - // Sets cannot be the same if they are different sizes - if (results.size() != expected.size()) { - return false; - } - // Convert the data structure to a set for better searching - std::set results_set; - for (const auto& res : results) { - results_set.insert(res); - } - - for (auto res : expected) { - const auto loc = results_set.find(res.make_preferred().string()); - // Unable to find element (something is in expected but not results) - if (loc == results_set.end()) { - return false; - } - // Pair found so remove from results - results_set.erase(loc); - } - - // There are unremoved values so expected is a proper subset of results - if (!results_set.empty()) { - return false; - } - return true; - } -}; - -class TempFile { - public: - TempFile() { - do { - path_ = generateTempPath(); - } while (fs::exists(path_)); - } - - ~TempFile() { - if (fs::exists(path_)) { - fs::remove(path_); - } - } - - const std::string& path() const { - return path_; - } - - private: - static std::string generateTempPath() { - return (fs::temp_directory_path() / fs::unique_path("osquery-%%%%-%%%%")) - .make_preferred() - .string(); - } - - private: - std::string path_; -}; - -TEST_F(FileOpsTests, test_openFile) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - EXPECT_FALSE(fd.isValid()); - } - - { - PlatformFile fd(path, PF_CREATE_NEW | PF_WRITE); - EXPECT_TRUE(fd.isValid()); - } - - { - PlatformFile fd(path, PF_CREATE_NEW | PF_READ); - EXPECT_FALSE(fd.isValid()); - } - - fs::remove(path); - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_READ); - EXPECT_TRUE(fd.isValid()); - } - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_READ); - EXPECT_TRUE(fd.isValid()); - } - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - EXPECT_TRUE(fd.isValid()); - } -} - -/* - * This is a special function for testing file share operations on Windows. Our - * PlatformFile as of now will only set FILE_SHARE_READ to play nicely with log - * reading tools. However, we need to create one with FILE_SHARE_READ and - * FILE_SHARE_WRITE for testing. - */ -std::unique_ptr openRWSharedFile(const std::string& path, - int mode) { -#ifdef WIN32 - DWORD access_mask = -1; - DWORD creation_disposition = -1; - - if (mode == (PF_OPEN_EXISTING | PF_READ)) { - access_mask = PF_READ; - creation_disposition = OPEN_EXISTING; - } else if (mode == (PF_OPEN_ALWAYS | PF_WRITE)) { - access_mask = PF_WRITE; - creation_disposition = OPEN_ALWAYS; - } - - HANDLE handle = ::CreateFileA(path.c_str(), - access_mask, - FILE_SHARE_READ | FILE_SHARE_WRITE, - nullptr, - creation_disposition, - 0, - nullptr); - return std::unique_ptr(new PlatformFile(handle)); -#else - return std::unique_ptr(new PlatformFile(path, mode)); -#endif -} - -TEST_F(FileOpsTests, test_shareRead) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - const char* test1_data = "AAAABBBB"; - const ssize_t test1_size = ::strlen(test1_data); - - { - PlatformFile fd(path, PF_CREATE_NEW | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(test1_size, fd.write(test1_data, test1_size)); - } - - { - auto reader_fd = openRWSharedFile(path, PF_OPEN_EXISTING | PF_READ); - ASSERT_TRUE(reader_fd->isValid()); - - std::vector buf; - buf.assign(test1_size, '\0'); - - EXPECT_EQ(test1_size, reader_fd->read(buf.data(), test1_size)); - EXPECT_EQ(static_cast(test1_size), buf.size()); - - for (ssize_t i = 0; i < test1_size; i++) { - EXPECT_EQ(test1_data[i], buf[i]); - } - - PlatformFile fd(path, PF_OPEN_ALWAYS | PF_WRITE | PF_APPEND); - EXPECT_TRUE(fd.isValid()); - } -} - -TEST_F(FileOpsTests, test_fileIo) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - const char* expected_read = "AAAABBBBCCCCDDDD"; - const ssize_t expected_read_len = ::strlen(expected_read); - const ssize_t expected_write_len = ::strlen(expected_read); - const size_t expected_buf_size = ::strlen(expected_read); - - { - PlatformFile fd(path, PF_CREATE_NEW | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(expected_write_len, fd.write(expected_read, expected_read_len)); - } - - { - std::vector buf(expected_read_len); - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - ASSERT_TRUE(fd.isValid()); - ASSERT_FALSE(fd.isSpecialFile()); - EXPECT_EQ(expected_read_len, fd.read(buf.data(), expected_read_len)); - EXPECT_EQ(expected_buf_size, buf.size()); - for (ssize_t i = 0; i < expected_read_len; i++) { - EXPECT_EQ(expected_read[i], buf[i]); - } - } -} - -TEST_F(FileOpsTests, test_append) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - const char* test_data = "AAAABBBBCCCCDDDDD"; - const ssize_t test_size = ::strlen(test_data); - const ssize_t test1_size = 7; - const ssize_t test2_size = test_size - test1_size; - - { - PlatformFile fd(path, PF_OPEN_ALWAYS | PF_WRITE | PF_APPEND); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(test1_size, fd.write(test_data, test1_size)); - } - - { - PlatformFile fd(path, PF_OPEN_ALWAYS | PF_WRITE | PF_APPEND); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(test2_size, fd.write(&test_data[7], test2_size)); - } - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - ASSERT_TRUE(fd.isValid()); - - std::vector buf; - buf.assign(test_size, '\0'); - EXPECT_EQ(test_size, fd.read(buf.data(), test_size)); - EXPECT_EQ(static_cast(test_size), buf.size()); - - for (ssize_t i = 0; i < test_size; i++) { - EXPECT_EQ(test_data[i], buf[i]); - } - } -} - -TEST_F(FileOpsTests, test_asyncIo) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - const char* expected = "AAAABBBBCCCCDDDDEEEEFFFFGGGG"; - const ssize_t expected_len = ::strlen(expected); - - { - PlatformFile fd(path, PF_CREATE_NEW | PF_WRITE | PF_NONBLOCK); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(expected_len, fd.write(expected, expected_len)); - } - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ | PF_NONBLOCK); - ASSERT_TRUE(fd.isValid()); - ASSERT_FALSE(fd.isSpecialFile()); - - std::vector buf(expected_len); - EXPECT_EQ(expected_len, fd.read(buf.data(), expected_len)); - EXPECT_EQ(0, ::memcmp(expected, buf.data(), expected_len)); - } - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ | PF_NONBLOCK); - ASSERT_TRUE(fd.isValid()); - ASSERT_FALSE(fd.isSpecialFile()); - - std::vector buf(expected_len); - char* ptr = buf.data(); - ssize_t part_bytes = 0; - int iterations = 0; - do { - part_bytes = fd.read(ptr, 4); - if (part_bytes > 0) { - ptr += part_bytes; - iterations++; - } - } while (part_bytes > 0); - - EXPECT_EQ(7, iterations); - EXPECT_EQ(0, ::memcmp(expected, buf.data(), expected_len)); - } -} - -TEST_F(FileOpsTests, test_seekFile) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - const char* expected = "AABBBBAACCCAAAAADDDDAAAAAAAA"; - const ssize_t expected_len = ::strlen(expected); - ssize_t expected_offs; - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(expected_len, - fd.write("AAAAAAAAAAAAAAAAAAAAAAAAAAAA", expected_len)); - } - - // Cast to the proper type, off_t - expected_offs = expected_len - 12; - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - - EXPECT_EQ(expected_offs, fd.seek(-12, PF_SEEK_END)); - EXPECT_EQ(4, fd.write("DDDD", 4)); - - EXPECT_EQ(2, fd.seek(2, PF_SEEK_BEGIN)); - EXPECT_EQ(4, fd.write("BBBB", 4)); - - EXPECT_EQ(8, fd.seek(2, PF_SEEK_CURRENT)); - EXPECT_EQ(3, fd.write("CCC", 3)); - } - - { - std::vector buffer(expected_len); - - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - ASSERT_TRUE(fd.isValid()); - - EXPECT_EQ(expected_len, fd.read(buffer.data(), expected_len)); - EXPECT_EQ(0, ::memcmp(buffer.data(), expected, expected_len)); - } -} - -TEST_F(FileOpsTests, test_large_read_write) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - const std::string expected(20000000, 'A'); - const ssize_t expected_len = expected.size(); - ASSERT_EQ(strnlen(expected.data(), 20000001), 20000000U); - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - auto write_len = fd.write(expected.c_str(), expected_len); - EXPECT_EQ(expected_len, write_len); - } - - { - std::vector buffer(expected_len); - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - ASSERT_TRUE(fd.isValid()); - auto read_len = fd.read(buffer.data(), expected_len); - EXPECT_EQ(expected_len, read_len); - EXPECT_EQ(expected, std::string(buffer.data(), buffer.size())); - } -} - -TEST_F(FileOpsTests, test_chmod_no_exec) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(4, fd.write("TEST", 4)); - } - - EXPECT_TRUE(platformChmod(path, S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH)); - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - ASSERT_TRUE(fd.isValid()); - - auto status = fd.isExecutable(); - EXPECT_TRUE(!status.ok()); - EXPECT_EQ(1, status.getCode()); - } - - EXPECT_TRUE(platformChmod( - path, S_IRUSR | S_IWUSR | S_IXUSR | S_IROTH | S_IWOTH | S_IXOTH)); - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - ASSERT_TRUE(fd.isValid()); - - EXPECT_TRUE(fd.isExecutable().ok()); - } -} - -TEST_F(FileOpsTests, test_chmod_no_read) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(4, fd.write("TEST", 4)); - } - - EXPECT_TRUE(platformChmod(path, S_IWUSR | S_IWOTH)); - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - EXPECT_FALSE(fd.isValid()); - } - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_WRITE); - EXPECT_TRUE(fd.isValid()); - } -} - -TEST_F(FileOpsTests, test_chmod_no_write) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(4, fd.write("TEST", 4)); - } - - EXPECT_TRUE(platformChmod(path, S_IRUSR | S_IROTH)); - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_READ); - EXPECT_TRUE(fd.isValid()); - } - - { - PlatformFile fd(path, PF_OPEN_EXISTING | PF_WRITE); - EXPECT_FALSE(fd.isValid()); - } -} - -TEST_F(FileOpsTests, test_access) { - const int all_access = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | - S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH; - - TempFile tmp_file; - std::string path = tmp_file.path(); - - { - PlatformFile fd(path, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(4, fd.write("TEST", 4)); - } - - EXPECT_TRUE(platformChmod(path, S_IRUSR | S_IWUSR | S_IXUSR)); - - EXPECT_EQ(0, platformAccess(path, R_OK | W_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK | W_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, W_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK)); - EXPECT_EQ(0, platformAccess(path, W_OK)); - EXPECT_EQ(0, platformAccess(path, X_OK)); - - EXPECT_TRUE(platformChmod(path, S_IRUSR | S_IWUSR)); - - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK | W_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK)); - EXPECT_EQ(0, platformAccess(path, W_OK)); - EXPECT_EQ(-1, platformAccess(path, X_OK)); - - EXPECT_TRUE(platformChmod(path, S_IRUSR | S_IXUSR)); - - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK)); - EXPECT_EQ(0, platformAccess(path, X_OK)); - - EXPECT_TRUE(platformChmod(path, S_IWUSR | S_IXUSR)); - - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK)); - EXPECT_EQ(0, platformAccess(path, W_OK)); - EXPECT_EQ(0, platformAccess(path, X_OK)); - - EXPECT_TRUE(platformChmod(path, S_IRUSR)); - - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK | X_OK)); - EXPECT_EQ(0, platformAccess(path, R_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK)); - EXPECT_EQ(-1, platformAccess(path, X_OK)); - - EXPECT_TRUE(platformChmod(path, S_IWUSR)); - - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK)); - EXPECT_EQ(0, platformAccess(path, W_OK)); - EXPECT_EQ(-1, platformAccess(path, X_OK)); - - EXPECT_TRUE(platformChmod(path, S_IXUSR)); - - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | W_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK | X_OK)); - EXPECT_EQ(-1, platformAccess(path, R_OK)); - EXPECT_EQ(-1, platformAccess(path, W_OK)); - EXPECT_EQ(0, platformAccess(path, X_OK)); - - // Reset permissions - EXPECT_TRUE(platformChmod(path, all_access)); -} - -TEST_F(FileOpsTests, test_safe_permissions) { - const auto root_path = fs::temp_directory_path() / - fs::unique_path("osquery.safe-perms-test.%%%%.%%%%"); - auto const root_path_manager = - scope_guard::create([&root_path]() { fs::remove_all(root_path); }); - - const auto temp_file = (root_path / "test").string(); - const auto root_dir = root_path.string(); - - const int all_access = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | - S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH; - - fs::create_directories(root_dir); - - { - PlatformFile fd(temp_file, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - - EXPECT_TRUE( - platformChmod(temp_file, S_IRUSR | S_IWGRP | S_IROTH | S_IWOTH)); - EXPECT_TRUE(platformChmod(root_dir, S_IRUSR | S_IRGRP | S_IROTH)); - - auto status = fd.hasSafePermissions(); - EXPECT_FALSE(status.ok()); - EXPECT_EQ(1, status.getCode()); - - if (isPlatform(PlatformType::TYPE_POSIX)) { - // On POSIX, chmod on a file requires +x on the parent directory - EXPECT_TRUE(platformChmod(root_dir, all_access)); - } - - EXPECT_TRUE(platformChmod(temp_file, S_IRUSR | S_IRGRP | S_IROTH)); - EXPECT_TRUE(platformChmod(root_dir, - S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); - - status = fd.hasSafePermissions(); - - if (isPlatform(PlatformType::TYPE_WINDOWS)) { - EXPECT_FALSE(status.ok()); - EXPECT_EQ(1, status.getCode()); - } else { - // On POSIX, we only check to see if temp_file has S_IWOTH - EXPECT_TRUE(status.ok()); - } - - if (isPlatform(PlatformType::TYPE_POSIX)) { - // On POSIX, chmod on a file requires +x on the parent directory - EXPECT_TRUE(platformChmod(root_dir, all_access)); - } - - EXPECT_TRUE(platformChmod(temp_file, S_IRUSR | S_IRGRP | S_IROTH)); - EXPECT_TRUE(platformChmod(root_dir, S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH)); - - status = fd.hasSafePermissions(); - - if (isPlatform(PlatformType::TYPE_WINDOWS)) { - EXPECT_FALSE(status.ok()); - EXPECT_EQ(1, status.getCode()); - } else { - // On POSIX, we only check to see if temp_file has S_IWOTH - EXPECT_TRUE(status.ok()); - } - - if (isPlatform(PlatformType::TYPE_POSIX)) { - // On POSIX, chmod on a file requires +x on the parent directory - EXPECT_TRUE(platformChmod(root_dir, all_access)); - } - - EXPECT_TRUE(platformChmod(temp_file, 0)); - EXPECT_TRUE(platformChmod(root_dir, 0)); - EXPECT_TRUE(fd.hasSafePermissions().ok()); - - if (isPlatform(PlatformType::TYPE_POSIX)) { - // On POSIX, chmod on a file requires +x on the parent directory - EXPECT_TRUE(platformChmod(root_dir, all_access)); - } - - EXPECT_TRUE(platformChmod(temp_file, S_IRUSR | S_IRGRP | S_IROTH)); - EXPECT_TRUE(platformChmod(root_dir, S_IRUSR | S_IRGRP | S_IROTH)); - EXPECT_TRUE(fd.hasSafePermissions().ok()); - } - - EXPECT_TRUE(platformChmod(root_dir, all_access)); - EXPECT_TRUE(platformChmod(temp_file, all_access)); -} - -TEST_F(FileOpsTests, test_safe_db_permissions) { - const auto db_path = - fs::temp_directory_path() / - fs::unique_path("osquery.safe-db-perms-test.%%%%.%%%%.db"); - auto const db_path_manager = - scope_guard::create([&db_path]() { fs::remove_all(db_path); }); - - const auto sst_file = (db_path / "1234.sst").string(); - const auto db = db_path.string(); - - fs::create_directories(db); - - // Ensure that 'safe' permissions get applied correctly - { - EXPECT_TRUE(platformSetSafeDbPerms(db)); - - PlatformFile fd(sst_file, PF_CREATE_ALWAYS | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - - // The 'hasSafePermissions' function ensures no low priv writes can occur - auto status = fd.hasSafePermissions(); - - EXPECT_TRUE(fd.hasSafePermissions().ok()); - EXPECT_EQ(0, status.getCode()); - } - - // Ensure that we still have read and write access to the db - { - EXPECT_EQ(0, platformAccess(db, R_OK | W_OK)); - EXPECT_EQ(0, platformAccess(sst_file, R_OK | W_OK)); - } - - // Tear down our mock DB files - const int all_access = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | - S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH; - EXPECT_TRUE(platformChmod(db, all_access)); - EXPECT_TRUE(platformChmod(sst_file, all_access)); -} - -TEST_F(FileOpsTests, test_glob) { - { - std::vector expected{fake_directory_ / "door.txt", - fake_directory_ / "root.txt", - fake_directory_ / "root2.txt", - fake_directory_ / "roto.txt"}; - auto result = platformGlob((fake_directory_ / "*.txt").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } - - { - std::vector expected{fake_directory_ / "deep1/", - fake_directory_ / "deep11/", - fake_directory_ / "door.txt", - fake_directory_ / "root.txt", - fake_directory_ / "root2.txt", - fake_directory_ / "roto.txt", - fake_directory_ / "toplevel/"}; - auto result = platformGlob((fake_directory_ / "*").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } - - { - std::vector expected{fake_directory_ / "deep1/deep2/", - fake_directory_ / "deep1/level1.txt", - fake_directory_ / "deep11/deep2/", - fake_directory_ / "deep11/level1.txt", - fake_directory_ / "deep11/not_bash", - fake_directory_ / "toplevel/secondlevel1/", - fake_directory_ / "toplevel/secondlevel2/", - fake_directory_ / "toplevel/secondlevel3/"}; - auto result = platformGlob((fake_directory_ / "*" / "*").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } - - { - std::vector expected{ - fake_directory_ / "deep1/deep2/level2.txt", - fake_directory_ / "deep11/deep2/deep3/", - fake_directory_ / "deep11/deep2/level2.txt", - fake_directory_ / "toplevel/secondlevel3/thirdlevel1/", - }; - auto result = platformGlob((fake_directory_ / "*/*/*").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } - - { - std::vector expected{fake_directory_ / "deep11/deep2/deep3/", - fake_directory_ / "deep11/deep2/level2.txt"}; - auto result = platformGlob((fake_directory_ / "*11/*/*").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } - - { - std::vector expected{fake_directory_ / "deep1/", - fake_directory_ / "root.txt"}; - auto result = platformGlob((fake_directory_ / "{deep,root}{1,.txt}").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } - - { - std::vector expected{fake_directory_ / "deep1/deep2/level2.txt", - fake_directory_ / "deep11/deep2/deep3/", - fake_directory_ / "deep11/deep2/level2.txt"}; - auto result = platformGlob((fake_directory_ / "*/deep2/*").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } - - { - std::vector expected{fake_directory_ / "deep1/deep2/", - fake_directory_ / "deep1/level1.txt", - fake_directory_ / "deep11/deep2/", - fake_directory_ / "deep11/level1.txt", - fake_directory_ / "deep11/not_bash"}; - auto result = - platformGlob((fake_directory_ / "*/{deep2,level1,not_bash}{,.txt}").string()); - EXPECT_TRUE(globResultsMatch(result, expected)); - } -} - -TEST_F(FileOpsTests, test_zero_permissions_file) { - TempFile tmp_file; - std::string path = tmp_file.path(); - - const std::string expected_str = "0_permissions"; - const ssize_t expected_len = expected_str.size(); - - // Setup file for testing - PlatformFile fd(path, PF_CREATE_NEW | PF_READ | PF_WRITE); - ASSERT_TRUE(fd.isValid()); - EXPECT_EQ(expected_len, fd.write(expected_str.c_str(), expected_len)); - EXPECT_TRUE(platformChmod(path, 0)); - - // Test file - EXPECT_TRUE(!fd.isExecutable().ok()); - - std::vector buf(expected_len); - EXPECT_EQ(0, fd.read(buf.data(), expected_len)); - - auto modes = {R_OK, W_OK, X_OK}; - for (auto& mode : modes) { - EXPECT_EQ(-1, platformAccess(path, mode)); - } - EXPECT_EQ(boost::none, platformFopen(path, "r")); -} -} // namespace osquery diff --git a/src/osquery/logger/logger.cpp b/src/osquery/logger/logger.cpp index eb4748e..1ab11c6 100644 --- a/src/osquery/logger/logger.cpp +++ b/src/osquery/logger/logger.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include #include diff --git a/src/osquery/logger/plugins/filesystem.cpp b/src/osquery/logger/plugins/filesystem.cpp deleted file mode 100644 index b340575..0000000 --- a/src/osquery/logger/plugins/filesystem.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2014, 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 - -namespace pt = boost::property_tree; -namespace fs = boost::filesystem; - -namespace osquery { - -const std::string kFilesystemLoggerFilename = "osqueryd.results.log"; -const std::string kFilesystemLoggerSnapshots = "osqueryd.snapshots.log"; -const std::string kFilesystemLoggerHealth = "osqueryd.health.log"; - -std::mutex filesystemLoggerPluginMutex; - -class FilesystemLoggerPlugin : public LoggerPlugin { - public: - Status setUp(); - Status logString(const std::string& s); - Status logStringToFile(const std::string& s, const std::string& filename); - Status logSnapshot(const std::string& s); - Status logHealth(const std::string& s); - Status init(const std::string& name, const std::vector& log); - Status logStatus(const std::vector& log); - - private: - fs::path log_path_; -}; - -REGISTER(FilesystemLoggerPlugin, "logger", "filesystem"); - -Status FilesystemLoggerPlugin::setUp() { - return Status(0, "OK"); -} - -Status FilesystemLoggerPlugin::logString(const std::string& s) { - return logStringToFile(s, kFilesystemLoggerFilename); -} - -Status FilesystemLoggerPlugin::logStringToFile(const std::string& s, - const std::string& filename) { - std::lock_guard lock(filesystemLoggerPluginMutex); - try { - // The results log may contain sensitive information if run as root. - auto status = writeTextFile((log_path_ / filename).string(), s, 0640, true); - if (!status.ok()) { - return status; - } - } catch (const std::exception& e) { - return Status(1, e.what()); - } - return Status(0, "OK"); -} - -Status FilesystemLoggerPlugin::logStatus( - const std::vector& log) { - for (const auto& item : log) { - // Emit this intermediate log to the Glog filesystem logger. - google::LogMessage(item.filename.c_str(), - item.line, - (google::LogSeverity)item.severity).stream() - << item.message; - } - - return Status(0, "OK"); -} - -Status FilesystemLoggerPlugin::logSnapshot(const std::string& s) { - // Send the snapshot data to a separate filename. - return logStringToFile(s, kFilesystemLoggerSnapshots); -} - -Status FilesystemLoggerPlugin::logHealth(const std::string& s) { - return logStringToFile(s, kFilesystemLoggerHealth); -} - -Status FilesystemLoggerPlugin::init(const std::string& name, - const std::vector& log) { - // Stop the internal Glog facilities. - google::ShutdownGoogleLogging(); - - // Restart the Glog facilities using the name `init` was provided. - google::InitGoogleLogging(name.c_str()); - - // We may violate Glog global object assumptions. So set names manually. - auto basename = (log_path_ / name).string(); - google::SetLogDestination(google::INFO, (basename + ".INFO.").c_str()); - google::SetLogDestination(google::WARNING, (basename + ".WARNING.").c_str()); - google::SetLogDestination(google::ERROR, (basename + ".ERROR.").c_str()); - - // Now funnel the intermediate status logs provided to `init`. - logStatus(log); - - // The filesystem logger cheats and uses Glog to log to the filesystem so - // we can return failure here and stop the custom log sink. - return Status(1, "No status logger used for filesystem"); -} -} diff --git a/src/osquery/logger/plugins/syslog.cpp b/src/osquery/logger/plugins/syslog.cpp deleted file mode 100644 index 04f70d1..0000000 --- a/src/osquery/logger/plugins/syslog.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2014, 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 - -namespace osquery { - -class SyslogLoggerPlugin : public LoggerPlugin { - public: - Status logString(const std::string& s); - Status init(const std::string& name, const std::vector& log); - Status logStatus(const std::vector& log); -}; - -REGISTER(SyslogLoggerPlugin, "logger", "syslog"); - -Status SyslogLoggerPlugin::logString(const std::string& s) { - for (const auto& line : osquery::split(s, "\n")) { - syslog(LOG_INFO, "result=%s", line.c_str()); - } - return Status(0, "OK"); -} - -Status SyslogLoggerPlugin::logStatus(const std::vector& log) { - for (const auto& item : log) { - int severity = LOG_NOTICE; - if (item.severity == O_INFO) { - severity = LOG_NOTICE; - } else if (item.severity == O_WARNING) { - severity = LOG_WARNING; - } else if (item.severity == O_ERROR) { - severity = LOG_ERR; - } else if (item.severity == O_FATAL) { - severity = LOG_CRIT; - } - - std::string line = "severity=" + std::to_string(item.severity) - + " location=" + item.filename + ":" + std::to_string(item.line) + - " message=" + item.message; - - syslog(severity, "%s", line.c_str()); - } - return Status(0, "OK"); -} - -Status SyslogLoggerPlugin::init(const std::string& name, - const std::vector& log) { - closelog(); - - // Now funnel the intermediate status logs provided to `init`. - return logStatus(log); -} -} diff --git a/src/osquery/main/main.cpp b/src/osquery/main/main.cpp index 0a49f65..253b961 100644 --- a/src/osquery/main/main.cpp +++ b/src/osquery/main/main.cpp @@ -13,10 +13,10 @@ #include #include +#include #include #include -#include #include #include #include @@ -32,8 +32,6 @@ namespace osquery { int startDaemon(Initializer& runner) { runner.start(); -// osquery::events::init_syscall_tracing(); - // Finally wait for a signal / interrupt to shutdown. runner.waitForShutdown(); return 0; diff --git a/src/osquery/sql/virtual_sqlite_table.cpp b/src/osquery/sql/virtual_sqlite_table.cpp index c1979ec..40ba0b8 100644 --- a/src/osquery/sql/virtual_sqlite_table.cpp +++ b/src/osquery/sql/virtual_sqlite_table.cpp @@ -7,7 +7,6 @@ */ #include -#include #include #include @@ -16,7 +15,10 @@ #include "osquery/sql/dynamic_table_row.h" #include "osquery/sql/sqlite_util.h" +#include + namespace fs = boost::filesystem; +namespace errc = boost::system::errc; namespace osquery { @@ -73,10 +75,16 @@ Status genTableRowsForSqliteTable(const fs::path& sqlite_db, TableRows& results, bool respect_locking) { sqlite3* db = nullptr; - if (!pathExists(sqlite_db).ok()) { + boost::system::error_code ec; + if (sqlite_db.empty()) { return Status(1, "Database path does not exist"); } + // A tri-state determination of presence + if (!fs::exists(sqlite_db, ec) || ec.value() != errc::success) { + return Status(1, ec.message()); + } + auto rc = sqlite3_open_v2( sqlite_db.string().c_str(), &db, diff --git a/src/osquery/tables/system/linux/processes.cpp b/src/osquery/tables/system/linux/processes.cpp deleted file mode 100644 index 6ab8334..0000000 --- a/src/osquery/tables/system/linux/processes.cpp +++ /dev/null @@ -1,530 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -namespace osquery { -namespace tables { - -const int kMSIn1CLKTCK = (1000 / sysconf(_SC_CLK_TCK)); - -inline std::string getProcAttr(const std::string& attr, - const std::string& pid) { - return "/proc/" + pid + "/" + attr; -} - -inline std::string readProcCMDLine(const std::string& pid) { - auto attr = getProcAttr("cmdline", pid); - - std::string content; - readFile(attr, content); - // Remove \0 delimiters. - std::replace_if(content.begin(), - content.end(), - [](const char& c) { return c == 0; }, - ' '); - // Remove trailing delimiter. - boost::algorithm::trim(content); - return content; -} - -inline std::string readProcLink(const std::string& attr, - const std::string& pid) { - // The exe is a symlink to the binary on-disk. - auto attr_path = getProcAttr(attr, pid); - - std::string result = ""; - struct stat sb; - if (lstat(attr_path.c_str(), &sb) != -1) { - // Some symlinks may report 'st_size' as zero - // Use PATH_MAX as best guess - // For cases when 'st_size' is not zero but smaller than - // PATH_MAX we will still use PATH_MAX to minimize chance - // of output trucation during race condition - ssize_t buf_size = sb.st_size < PATH_MAX ? PATH_MAX : sb.st_size; - // +1 for \0, since readlink does not append a null - char* linkname = static_cast(malloc(buf_size + 1)); - ssize_t r = readlink(attr_path.c_str(), linkname, buf_size); - - if (r > 0) { // Success check - // r may not be equal to buf_size - // if r == buf_size there was race condition - // and link is longer than buf_size and because of this - // truncated - linkname[r] = '\0'; - result = std::string(linkname); - } - free(linkname); - } - - return result; -} - -// In the case where the linked binary path ends in " (deleted)", and a file -// actually exists at that path, check whether the inode of that file matches -// the inode of the mapped file in /proc/%pid/maps -Status deletedMatchesInode(const std::string& path, const std::string& pid) { - const std::string maps_path = getProcAttr("maps", pid); - std::string maps_contents; - auto s = osquery::readFile(maps_path, maps_contents); - if (!s.ok()) { - return Status(-1, "Cannot read maps file: " + maps_path); - } - - // Extract the expected inode of the binary file from /proc/%pid/maps - boost::smatch what; - boost::regex expression("([0-9]+)\\h+\\Q" + path + "\\E"); - if (!boost::regex_search(maps_contents, what, expression)) { - return Status(-1, "Could not find binary inode in maps file: " + maps_path); - } - std::string inode = what[1]; - - // stat the file at the expected binary path - struct stat st; - if (stat(path.c_str(), &st) != 0) { - return Status(-1, "Error in stat of binary: " + path); - } - - // If the inodes match, the binary name actually ends with " (deleted)" - if (std::to_string(st.st_ino) == inode) { - return Status::success(); - } else { - return Status(1, "Inodes do not match"); - } -} - -std::set getProcList(const QueryContext& context) { - std::set pidlist; - if (context.constraints.count("pid") > 0 && - context.constraints.at("pid").exists(EQUALS)) { - for (const auto& pid : context.constraints.at("pid").getAll(EQUALS)) { - if (isDirectory("/proc/" + pid)) { - pidlist.insert(pid); - } - } - } else { - osquery::procProcesses(pidlist); - } - - return pidlist; -} - -void genProcessEnvironment(const std::string& pid, QueryData& results) { - auto attr = getProcAttr("environ", pid); - - std::string content; - readFile(attr, content); - const char* variable = content.c_str(); - - // Stop at the end of nul-delimited string content. - while (*variable > 0) { - auto buf = std::string(variable); - size_t idx = buf.find_first_of("="); - - Row r; - r["pid"] = pid; - r["key"] = buf.substr(0, idx); - r["value"] = buf.substr(idx + 1); - results.push_back(r); - variable += buf.size() + 1; - } -} - -void genProcessMap(const std::string& pid, QueryData& results) { - auto map = getProcAttr("maps", pid); - - std::string content; - readFile(map, content); - for (auto& line : osquery::split(content, "\n")) { - auto fields = osquery::split(line, " "); - // If can't read address, not sure. - if (fields.size() < 5) { - continue; - } - - Row r; - r["pid"] = pid; - if (!fields[0].empty()) { - auto addresses = osquery::split(fields[0], "-"); - if (addresses.size() >= 2) { - r["start"] = "0x" + addresses[0]; - r["end"] = "0x" + addresses[1]; - } else { - // Problem with the address format. - continue; - } - } - - r["permissions"] = fields[1]; - auto offset = tryTo(fields[2], 16); - r["offset"] = BIGINT((offset) ? offset.take() : -1); - r["device"] = fields[3]; - r["inode"] = fields[4]; - - // Path name must be trimmed. - if (fields.size() > 5) { - boost::trim(fields[5]); - r["path"] = fields[5]; - } - - // BSS with name in pathname. - r["pseudo"] = (fields[4] == "0" && !r["path"].empty()) ? "1" : "0"; - results.push_back(std::move(r)); - } -} - -/** - * Output from string parsing /proc//status. - */ -struct SimpleProcStat : private boost::noncopyable { - public: - std::string name; - std::string real_uid; - std::string real_gid; - std::string effective_uid; - std::string effective_gid; - std::string saved_uid; - std::string saved_gid; - std::string resident_size; - std::string total_size; - std::string state; - std::string parent; - std::string group; - std::string nice; - std::string threads; - std::string user_time; - std::string system_time; - std::string start_time; - - /// For errors processing proc data. - Status status; - - explicit SimpleProcStat(const std::string& pid); -}; - -SimpleProcStat::SimpleProcStat(const std::string& pid) { - std::string content; - if (readFile(getProcAttr("stat", pid), content).ok()) { - auto start = content.find_last_of(")"); - // Start parsing stats from ") ..." - if (start == std::string::npos || content.size() <= start + 2) { - status = Status(1, "Invalid /proc/stat header"); - return; - } - - auto details = osquery::split(content.substr(start + 2), " "); - if (details.size() <= 19) { - status = Status(1, "Invalid /proc/stat content"); - return; - } - - this->state = details.at(0); - this->parent = details.at(1); - this->group = details.at(2); - this->user_time = details.at(11); - this->system_time = details.at(12); - this->nice = details.at(16); - this->threads = details.at(17); - this->start_time = details.at(19); - } - - // /proc/N/status may be not available, or readable by this user. - if (!readFile(getProcAttr("status", pid), content).ok()) { - status = Status(1, "Cannot read /proc/status"); - return; - } - - for (const auto& line : osquery::split(content, "\n")) { - // Status lines are formatted: Key: Value....\n. - auto detail = osquery::split(line, ':', 1); - if (detail.size() != 2) { - continue; - } - - // There are specific fields from each detail. - if (detail.at(0) == "Name") { - this->name = detail.at(1); - } else if (detail.at(0) == "VmRSS") { - detail[1].erase(detail.at(1).end() - 3, detail.at(1).end()); - // Memory is reported in kB. - this->resident_size = detail.at(1) + "000"; - } else if (detail.at(0) == "VmSize") { - detail[1].erase(detail.at(1).end() - 3, detail.at(1).end()); - // Memory is reported in kB. - this->total_size = detail.at(1) + "000"; - } else if (detail.at(0) == "Gid") { - // Format is: R E - - - auto gid_detail = osquery::split(detail.at(1), "\t"); - if (gid_detail.size() == 4) { - this->real_gid = gid_detail.at(0); - this->effective_gid = gid_detail.at(1); - this->saved_gid = gid_detail.at(2); - } - } else if (detail.at(0) == "Uid") { - auto uid_detail = osquery::split(detail.at(1), "\t"); - if (uid_detail.size() == 4) { - this->real_uid = uid_detail.at(0); - this->effective_uid = uid_detail.at(1); - this->saved_uid = uid_detail.at(2); - } - } - } -} - -/** - * Output from string parsing /proc//io. - */ -struct SimpleProcIo : private boost::noncopyable { - public: - std::string read_bytes; - std::string write_bytes; - std::string cancelled_write_bytes; - - /// For errors processing proc data. - Status status; - - explicit SimpleProcIo(const std::string& pid); -}; - -SimpleProcIo::SimpleProcIo(const std::string& pid) { - std::string content; - if (!readFile(getProcAttr("io", pid), content).ok()) { - status = Status( - 1, "Cannot read /proc/" + pid + "/io (is osquery running as root?)"); - return; - } - - for (const auto& line : osquery::split(content, "\n")) { - // IO lines are formatted: Key: Value....\n. - auto detail = osquery::split(line, ':', 1); - if (detail.size() != 2) { - continue; - } - - // There are specific fields from each detail - if (detail.at(0) == "read_bytes") { - this->read_bytes = detail.at(1); - } else if (detail.at(0) == "write_bytes") { - this->write_bytes = detail.at(1); - } else if (detail.at(0) == "cancelled_write_bytes") { - this->cancelled_write_bytes = detail.at(1); - } - } -} - -/** - * @brief Determine if the process path (binary) exists on the filesystem. - * - * If the path of the executable that started the process is available and - * the path exists on disk, set on_disk to 1. If the path is not - * available, set on_disk to -1. If, and only if, the path of the - * executable is available and the file does NOT exist on disk, set on_disk - * to 0. - * - * @param pid The string (because we're referencing file path) pid. - * @param path A mutable string found from /proc/N/exe. If this is found - * to contain the (deleted) suffix, it will be removed. - * @return A tristate -1 error, 1 yes, 0 nope. - */ -int getOnDisk(const std::string& pid, std::string& path) { - if (path.empty()) { - return -1; - } - - // The string appended to the exe path when the binary is deleted - const std::string kDeletedString = " (deleted)"; - if (!boost::algorithm::ends_with(path, kDeletedString)) { - return (osquery::pathExists(path)) ? 1 : 0; - } - - if (!osquery::pathExists(path)) { - // No file exists with the path including " (deleted)", so we can strip - // this from the path and set on_disk = 0 - path.erase(path.size() - kDeletedString.size()); - return 0; - } - - // Special case in which we have to check the inode to see whether the - // process is actually running from a binary file ending with - // " (deleted)". See #1607 - std::string maps_contents; - Status deleted = deletedMatchesInode(path, pid); - if (deleted.getCode() == -1) { - LOG(ERROR) << deleted.getMessage(); - return -1; - } else if (deleted.getCode() == 0) { - // The process is actually running from a binary ending with - // " (deleted)" - return 1; - } else { - // There is a collision with a file name ending in " (deleted)", but - // that file is not the binary for this process - path.erase(path.size() - kDeletedString.size()); - return 0; - } -} - -void genProcess(const std::string& pid, - long system_boot_time, - TableRows& results) { - // Parse the process stat and status. - SimpleProcStat proc_stat(pid); - // Parse the process io - SimpleProcIo proc_io(pid); - - if (!proc_stat.status.ok()) { - VLOG(1) << proc_stat.status.getMessage() << " for pid " << pid; - return; - } - - auto r = make_table_row(); - r["pid"] = pid; - r["parent"] = proc_stat.parent; - r["path"] = readProcLink("exe", pid); - r["name"] = proc_stat.name; - r["pgroup"] = proc_stat.group; - r["state"] = proc_stat.state; - r["nice"] = proc_stat.nice; - r["threads"] = proc_stat.threads; - // Read/parse cmdline arguments. - r["cmdline"] = readProcCMDLine(pid); - r["cwd"] = readProcLink("cwd", pid); - r["root"] = readProcLink("root", pid); - r["uid"] = proc_stat.real_uid; - r["euid"] = proc_stat.effective_uid; - r["suid"] = proc_stat.saved_uid; - r["gid"] = proc_stat.real_gid; - r["egid"] = proc_stat.effective_gid; - r["sgid"] = proc_stat.saved_gid; - - r["on_disk"] = INTEGER(getOnDisk(pid, r["path"])); - - // size/memory information - r["wired_size"] = "0"; // No support for unpagable counters in linux. - r["resident_size"] = proc_stat.resident_size; - r["total_size"] = proc_stat.total_size; - - // time information - auto usr_time = std::strtoull(proc_stat.user_time.data(), nullptr, 10); - r["user_time"] = std::to_string(usr_time * kMSIn1CLKTCK); - auto sys_time = std::strtoull(proc_stat.system_time.data(), nullptr, 10); - r["system_time"] = std::to_string(sys_time * kMSIn1CLKTCK); - - auto proc_start_time_exp = tryTo(proc_stat.start_time); - if (proc_start_time_exp.isValue() && system_boot_time > 0) { - r["start_time"] = INTEGER(system_boot_time + proc_start_time_exp.take() / - sysconf(_SC_CLK_TCK)); - } else { - r["start_time"] = "-1"; - } - - if (!proc_io.status.ok()) { - // /proc//io can require root to access, so don't fail if we can't - VLOG(1) << proc_io.status.getMessage(); - } else { - r["disk_bytes_read"] = proc_io.read_bytes; - long long write_bytes = tryTo(proc_io.write_bytes).takeOr(0ll); - long long cancelled_write_bytes = - tryTo(proc_io.cancelled_write_bytes).takeOr(0ll); - - r["disk_bytes_written"] = - std::to_string(write_bytes - cancelled_write_bytes); - } - - results.push_back(r); -} - -void genNamespaces(const std::string& pid, QueryData& results) { - Row r; - - ProcessNamespaceList proc_ns; - Status status = procGetProcessNamespaces(pid, proc_ns); - if (!status.ok()) { - VLOG(1) << "Namespaces for pid " << pid - << " are incomplete: " << status.what(); - } - - r["pid"] = pid; - for (const auto& pair : proc_ns) { - r[pair.first + "_namespace"] = std::to_string(pair.second); - } - - results.push_back(r); -} - -TableRows genProcesses(QueryContext& context) { - TableRows results; - auto system_boot_time = getUptime(); - if (system_boot_time > 0) { - system_boot_time = std::time(nullptr) - system_boot_time; - } - - auto pidlist = getProcList(context); - for (const auto& pid : pidlist) { - genProcess(pid, system_boot_time, results); - } - - return results; -} - -QueryData genProcessEnvs(QueryContext& context) { - QueryData results; - - auto pidlist = getProcList(context); - for (const auto& pid : pidlist) { - genProcessEnvironment(pid, results); - } - - return results; -} - -QueryData genProcessMemoryMap(QueryContext& context) { - QueryData results; - - auto pidlist = getProcList(context); - for (const auto& pid : pidlist) { - genProcessMap(pid, results); - } - - return results; -} - -QueryData genProcessNamespaces(QueryContext& context) { - QueryData results; - - const auto pidlist = getProcList(context); - for (const auto& pid : pidlist) { - genNamespaces(pid, results); - } - - return results; -} -} -} diff --git a/src/osquery/tables/utility/file.cpp b/src/osquery/tables/utility/file.cpp deleted file mode 100644 index c72ab73..0000000 --- a/src/osquery/tables/utility/file.cpp +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright (c) 2014-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed in accordance with the terms specified in - * the LICENSE file found in the root directory of this source tree. - */ - -#if !defined(WIN32) -#include -#endif - -#include -#include -#include -#include - -namespace fs = boost::filesystem; - -namespace osquery { - -namespace tables { - -#if !defined(WIN32) - -const std::map kTypeNames{ - {fs::regular_file, "regular"}, - {fs::directory_file, "directory"}, - {fs::symlink_file, "symlink"}, - {fs::block_file, "block"}, - {fs::character_file, "character"}, - {fs::fifo_file, "fifo"}, - {fs::socket_file, "socket"}, - {fs::type_unknown, "unknown"}, - {fs::status_error, "error"}, -}; - -#endif - -void genFileInfo(const fs::path& path, - const fs::path& parent, - const std::string& pattern, - QueryData& results) { - // Must provide the path, filename, directory separate from boost path->string - // helpers to match any explicit (query-parsed) predicate constraints. - - Row r; - r["path"] = path.string(); - r["filename"] = path.filename().string(); - r["directory"] = parent.string(); - r["symlink"] = "0"; - -#if !defined(WIN32) - - struct stat file_stat; - - // On POSIX systems, first check the link state. - struct stat link_stat; - if (lstat(path.string().c_str(), &link_stat) < 0) { - // Path was not real, had too may links, or could not be accessed. - return; - } - if (S_ISLNK(link_stat.st_mode)) { - r["symlink"] = "1"; - } - - if (stat(path.string().c_str(), &file_stat)) { - file_stat = link_stat; - } - - r["inode"] = BIGINT(file_stat.st_ino); - r["uid"] = BIGINT(file_stat.st_uid); - r["gid"] = BIGINT(file_stat.st_gid); - r["mode"] = lsperms(file_stat.st_mode); - r["device"] = BIGINT(file_stat.st_rdev); - r["size"] = BIGINT(file_stat.st_size); - r["block_size"] = INTEGER(file_stat.st_blksize); - r["hard_links"] = INTEGER(file_stat.st_nlink); - - r["atime"] = BIGINT(file_stat.st_atime); - r["mtime"] = BIGINT(file_stat.st_mtime); - r["ctime"] = BIGINT(file_stat.st_ctime); - -#if defined(__linux__) - // No 'birth' or create time in Linux or Windows. - r["btime"] = "0"; -#else - r["btime"] = BIGINT(file_stat.st_birthtimespec.tv_sec); -#endif - - // Type booleans - boost::system::error_code ec; - auto status = fs::status(path, ec); - if (kTypeNames.count(status.type())) { - r["type"] = kTypeNames.at(status.type()); - } else { - r["type"] = "unknown"; - } - -#else - - WINDOWS_STAT file_stat; - - auto rtn = platformStat(path, &file_stat); - if (!rtn.ok()) { - VLOG(1) << "PlatformStat failed with " << rtn.getMessage(); - return; - } - - r["symlink"] = INTEGER(file_stat.symlink); - r["inode"] = BIGINT(file_stat.inode); - r["uid"] = BIGINT(file_stat.uid); - r["gid"] = BIGINT(file_stat.gid); - r["mode"] = TEXT(file_stat.mode); - r["device"] = BIGINT(file_stat.device); - r["size"] = BIGINT(file_stat.size); - r["block_size"] = INTEGER(file_stat.block_size); - r["hard_links"] = INTEGER(file_stat.hard_links); - r["atime"] = BIGINT(file_stat.atime); - r["mtime"] = BIGINT(file_stat.mtime); - r["ctime"] = BIGINT(file_stat.ctime); - r["btime"] = BIGINT(file_stat.btime); - r["type"] = TEXT(file_stat.type); - r["attributes"] = TEXT(file_stat.attributes); - r["file_id"] = TEXT(file_stat.file_id); - r["volume_serial"] = TEXT(file_stat.volume_serial); - r["product_version"] = TEXT(file_stat.product_version); - -#endif - - results.push_back(r); -} - -QueryData genFile(QueryContext& context) { - QueryData results; - - // Resolve file paths for EQUALS and LIKE operations. - auto paths = context.constraints["path"].getAll(EQUALS); - context.expandConstraints( - "path", - LIKE, - paths, - ([&](const std::string& pattern, std::set& out) { - std::vector patterns; - auto status = - resolveFilePattern(pattern, patterns, GLOB_ALL | GLOB_NO_CANON); - if (status.ok()) { - for (const auto& resolved : patterns) { - out.insert(resolved); - } - } - return status; - })); - - // Iterate through each of the resolved/supplied paths. - for (const auto& path_string : paths) { - fs::path path = path_string; - genFileInfo(path, path.parent_path(), "", results); - } - - // Resolve directories for EQUALS and LIKE operations. - auto directories = context.constraints["directory"].getAll(EQUALS); - context.expandConstraints( - "directory", - LIKE, - directories, - ([&](const std::string& pattern, std::set& out) { - std::vector patterns; - auto status = - resolveFilePattern(pattern, patterns, GLOB_FOLDERS | GLOB_NO_CANON); - if (status.ok()) { - for (const auto& resolved : patterns) { - out.insert(resolved); - } - } - return status; - })); - - // Now loop through constraints using the directory column constraint. - for (const auto& directory_string : directories) { - if (!isReadable(directory_string) || !isDirectory(directory_string)) { - continue; - } - - try { - // Iterate over the directory and generate info for each regular file. - fs::directory_iterator begin(directory_string), end; - for (; begin != end; ++begin) { - genFileInfo(begin->path(), directory_string, "", results); - } - } catch (const fs::filesystem_error& /* e */) { - continue; - } - } - - return results; -} -} -} // namespace osquery diff --git a/src/osquery/tests/test_util.h b/src/osquery/tests/test_util.h index f02fc03..78e4a4d 100644 --- a/src/osquery/tests/test_util.h +++ b/src/osquery/tests/test_util.h @@ -13,7 +13,6 @@ #include #include -#include namespace osquery { diff --git a/src/vist/client/schema/processes.hpp b/src/vist/client/schema/processes.hpp deleted file mode 100644 index 764cd2d..0000000 --- a/src/vist/client/schema/processes.hpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2019 Samsung Electronics Co., Ltd All Rights Reserved - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -#pragma once - -#include - -namespace vist { -namespace schema { - -struct Processes { - long long int pid; - std::string name; - std::string path; - std::string cmdline; - long long int uid; - long long int gid; - long long int euid; - long long int egid; - int on_disk; - long long int resident_size; - long long int parent; -}; - -} // namesapce schema -} // namesapce vist diff --git a/src/vist/client/tests/virtual-table.cpp b/src/vist/client/tests/virtual-table.cpp index 61dbdcf..27814ff 100644 --- a/src/vist/client/tests/virtual-table.cpp +++ b/src/vist/client/tests/virtual-table.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -75,39 +74,6 @@ TEST(VirtualTableTests, time_row_arry_op) EXPECT_NE(result.seconds, -1); } -TEST(VirtualTableTests, processes_table) -{ - Processes result; - VirtualTable processes; - EXPECT_TRUE(processes.size() > 0); - - for(auto& p : processes) { - EXPECT_TRUE(p.size() > 0); - result.pid = p.at(&Processes::pid); - result.name = p.at(&Processes::name); - result.path = p.at(&Processes::path); - result.cmdline = p.at(&Processes::cmdline); - result.uid = p.at(&Processes::uid); - result.gid = p.at(&Processes::gid); - result.euid = p.at(&Processes::euid); - result.egid = p.at(&Processes::egid); - result.on_disk = p.at(&Processes::on_disk); - result.parent = p.at(&Processes::parent); - - INFO(VIST_CLIENT) << "[Test] Processes table:"; - INFO(VIST_CLIENT) << "\t pid: " << result.pid; - INFO(VIST_CLIENT) << "\t name: " << result.name; - INFO(VIST_CLIENT) << "\t path: " << result.path; - INFO(VIST_CLIENT) << "\t cmdline: " << result.cmdline; - INFO(VIST_CLIENT) << "\t uid: " << result.uid; - INFO(VIST_CLIENT) << "\t gid: " << result.gid; - INFO(VIST_CLIENT) << "\t euid: " << result.euid; - INFO(VIST_CLIENT) << "\t egid: " << result.egid; - INFO(VIST_CLIENT) << "\t on_disk: " << result.on_disk; - INFO(VIST_CLIENT) << "\t parent: " << result.parent; - } -} - TEST(VirtualTableTests, policy_int_table) { VirtualTable> table; diff --git a/src/vist/client/virtual-table.cpp b/src/vist/client/virtual-table.cpp index 0423516..2da7c73 100644 --- a/src/vist/client/virtual-table.cpp +++ b/src/vist/client/virtual-table.cpp @@ -18,7 +18,6 @@ #include #include -#include #include #include @@ -37,24 +36,13 @@ Table time { "time", Column("hour", &Time::hour), Column("minutes", &Time::minutes), Column("seconds", &Time::seconds) }; -Table processes { "processes", Column("pid", &Processes::pid), - Column("name", &Processes::name), - Column("path", &Processes::path), - Column("cmdline", &Processes::cmdline), - Column("uid", &Processes::uid), - Column("gid", &Processes::gid), - Column("euid", &Processes::euid), - Column("egid", &Processes::egid), - Column("on_disk", &Processes::on_disk), - Column("parent", &Processes::parent) }; - Table policyInt { "policy", Column("name", &Policy::name), Column("value", &Policy::value) }; Table policyStr { "policy", Column("name", &Policy::name), Column("value", &Policy::value) }; -Database metaDB { "db", time, processes, policyInt, policyStr }; +Database metaDB { "db", time, policyInt, policyStr }; } // anonymous namespace @@ -137,15 +125,6 @@ template class VirtualRow