diff --git a/include/osquery/filesystem.h b/include/osquery/filesystem.h index b5b8f0e5..590ba24c 100644 --- a/include/osquery/filesystem.h +++ b/include/osquery/filesystem.h @@ -191,5 +191,29 @@ Status procDescriptors(const std::string& process, 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 priviledged + * 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 alined, 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 } diff --git a/osquery/filesystem/CMakeLists.txt b/osquery/filesystem/CMakeLists.txt index 0e61ddc8..4855abc5 100644 --- a/osquery/filesystem/CMakeLists.txt +++ b/osquery/filesystem/CMakeLists.txt @@ -6,6 +6,7 @@ if(APPLE) ADD_OSQUERY_CORE_LINK("-framework Foundation") elseif(UBUNTU OR CENTOS) ADD_OSQUERY_CORE_LIBRARY(osquery_filesystem_linux + linux/mem.cpp linux/proc.cpp ) endif() diff --git a/osquery/filesystem/linux/mem.cpp b/osquery/filesystem/linux/mem.cpp new file mode 100644 index 00000000..f97c56cd --- /dev/null +++ b/osquery/filesystem/linux/mem.cpp @@ -0,0 +1,115 @@ +/* + * 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 +#include + +#include +#include +#include + +namespace osquery { + +#define kLinuxMaxMemRead 0x10000 + +const std::string kLinuxMemPath = "/dev/mem"; + +DEFINE_osquery_flag(bool, + disable_memory, + false, + "Disable physical memory reads."); + +int readMem(int fd, size_t base, size_t length, uint8_t* buffer) { + if (lseek(fd, base, SEEK_SET) == -1) { + return -1; + } + + // Read from raw memory until an unrecoverable read error or the requested + // bytes are read. + size_t total_read = 0; + size_t bytes_read = 0; + 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 -1; + } + } else { + total_read += bytes_read; + } + } + + // The read finished without reading the requested number of bytes. + if (total_read != length) { + return -1; + } + + return 0; +} + +Status readRawMem(size_t base, size_t length, void** buffer) { + *buffer = 0; + + if (!FLAGS_disable_memory) { + return Status(1, "Configuration has disabled physical memory reads"); + } + + 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) == -1) { + close(fd); + free(*buffer); + 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(0, "OK"); +} +}