From 6558f605ffb76b625c2c29c81631007d3ca2b090 Mon Sep 17 00:00:00 2001 From: Wesley Shields Date: Fri, 29 May 2015 04:27:10 +0000 Subject: [PATCH] Implement process related tables on FreeBSD. This implements the following tables on FreeBSD: process_envs process_memory_map process_open_files process_open_sockets processes All the heavy lifting is done with libprocstat(3). All the tables follow the same general principle. Use the common function, getProcesses() in procstat.cpp, to get the processes and then generate the rows for each process returned. There is also a procstatCleanup() function commonly used across all the tables. The one thing I am not able to test is the process_open_sockets table on an IPv6 machine. --- osquery/main/tests.cpp | 2 +- osquery/tables/CMakeLists.txt | 5 + osquery/tables/events/yara_events.cpp | 2 +- .../freebsd/process_open_sockets.cpp | 120 ++++++++++ osquery/tables/specs/blacklist | 5 - .../system/freebsd/process_open_files.cpp | 76 +++++++ osquery/tables/system/freebsd/processes.cpp | 210 +++++++++++++++++- osquery/tables/system/freebsd/procstat.cpp | 92 ++++++++ osquery/tables/system/freebsd/procstat.h | 29 +++ osquery/tables/utils/tests/yara_tests.cpp | 2 +- osquery/tables/utils/yara.cpp | 2 +- osquery/tables/utils/yara_utils.cpp | 2 +- 12 files changed, 530 insertions(+), 17 deletions(-) create mode 100644 osquery/tables/networking/freebsd/process_open_sockets.cpp create mode 100644 osquery/tables/system/freebsd/process_open_files.cpp create mode 100644 osquery/tables/system/freebsd/procstat.cpp create mode 100644 osquery/tables/system/freebsd/procstat.h diff --git a/osquery/main/tests.cpp b/osquery/main/tests.cpp index 63141e53..6e2bd522 100644 --- a/osquery/main/tests.cpp +++ b/osquery/main/tests.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Wesley Shields + * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the diff --git a/osquery/tables/CMakeLists.txt b/osquery/tables/CMakeLists.txt index 88beb142..744a4a2a 100644 --- a/osquery/tables/CMakeLists.txt +++ b/osquery/tables/CMakeLists.txt @@ -24,6 +24,11 @@ elseif(FREEBSD) file(GLOB OSQUERY_FREEBSD_TABLES_TESTS "*/freebsd/tests/*.cpp") ADD_OSQUERY_TABLE_TEST(${OSQUERY_FREEBSD_TABLES_TESTS}) + + ADD_OSQUERY_LINK_ADDITIONAL("procstat") + ADD_OSQUERY_LINK_ADDITIONAL("util") + ADD_OSQUERY_LINK_ADDITIONAL("kvm") + ADD_OSQUERY_LINK_ADDITIONAL("elf") else() file(GLOB OSQUERY_LINUX_TABLES "*/linux/*.cpp") ADD_OSQUERY_LIBRARY_ADDITIONAL(osquery_tables_linux diff --git a/osquery/tables/events/yara_events.cpp b/osquery/tables/events/yara_events.cpp index aa10baea..9c944ba4 100644 --- a/osquery/tables/events/yara_events.cpp +++ b/osquery/tables/events/yara_events.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Wesley Shields + * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the diff --git a/osquery/tables/networking/freebsd/process_open_sockets.cpp b/osquery/tables/networking/freebsd/process_open_sockets.cpp new file mode 100644 index 00000000..21b28879 --- /dev/null +++ b/osquery/tables/networking/freebsd/process_open_sockets.cpp @@ -0,0 +1,120 @@ +/* + * 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 +#include +#include + +#include "osquery/tables/system/freebsd/procstat.h" + +namespace osquery { +namespace tables { + +// Heavily inspired by procstat(1) on FreeBSD. +std::pair& sockaddr_to_pair(struct sockaddr_storage* sstor) { + char buffer[INET6_ADDRSTRLEN] = {0}; + struct sockaddr_in6* sin6 = nullptr; + struct sockaddr_in* sin = nullptr; + struct sockaddr_un* sun = nullptr; + + static std::pair addr; + switch (sstor->ss_family) { + case AF_LOCAL: + sun = (struct sockaddr_un*) sstor; + addr = std::make_pair(std::string(sun->sun_path), 0); + break; + case AF_INET: + sin = (struct sockaddr_in*) sstor; + addr = std::make_pair(std::string(inet_ntoa(sin->sin_addr)), + ntohs(sin->sin_port)); + break; + case AF_INET6: + sin6 = (struct sockaddr_in6*) sstor; + inet_ntop(AF_INET6, &sin6->sin6_addr, buffer, sizeof(buffer)); + addr = std::make_pair(std::string(buffer), ntohs(sin6->sin6_port)); + break; + default: + addr = std::make_pair(std::string(""), 0); + break; + } + + return addr; +} + +void genSockets(struct procstat* pstat, + struct kinfo_proc* proc, + QueryData &results) { + Row r; + struct filestat_list* files = nullptr; + struct filestat* file = nullptr; + struct sockstat sock; + int error; + std::pair addr; + + files = procstat_getfiles(pstat, proc, 0); + if (files == nullptr) { + return; + } + + STAILQ_FOREACH(file, files, next) { + // Skip files that aren't sockets. + if (file->fs_type != PS_FST_TYPE_SOCKET) { + continue; + } + + error = procstat_get_socket_info(pstat, file, &sock, nullptr); + if (error != 0) { + continue; + } + + r["pid"] = INTEGER(proc->ki_pid); + r["socket"] = INTEGER(file->fs_fd); + r["family"] = INTEGER(sock.dom_family); + r["protocol"] = INTEGER(sock.proto); + + addr = sockaddr_to_pair(&(sock.sa_local)); + r["local_address"] = TEXT(addr.first); + r["local_port"] = INTEGER(addr.second); + + addr = sockaddr_to_pair(&(sock.sa_peer)); + r["remote_address"] = TEXT(addr.first); + r["remote_port"] = INTEGER(addr.second); + + results.push_back(r); + } + + procstat_freefiles(pstat, files); +} + +QueryData genOpenSockets(QueryContext &context) { + QueryData results; + struct kinfo_proc* procs = nullptr; + struct procstat* pstat = nullptr; + + auto cnt = getProcesses(context, &pstat, &procs); + + for (size_t i = 0; i < cnt; i++) { + genSockets(pstat, &procs[i], results); + } + + procstatCleanup(pstat, procs); + + return results; +} +} +} diff --git a/osquery/tables/specs/blacklist b/osquery/tables/specs/blacklist index a3d0f268..c7681425 100644 --- a/osquery/tables/specs/blacklist +++ b/osquery/tables/specs/blacklist @@ -16,11 +16,6 @@ freebsd:opera_extensions freebsd:os_version freebsd:passwd_changes freebsd:pci_devices -freebsd:process_envs -freebsd:process_memory_map -freebsd:process_open_files -freebsd:process_open_sockets -freebsd:processes freebsd:routes freebsd:system_controls freebsd:usb_devices diff --git a/osquery/tables/system/freebsd/process_open_files.cpp b/osquery/tables/system/freebsd/process_open_files.cpp new file mode 100644 index 00000000..6fac3a8a --- /dev/null +++ b/osquery/tables/system/freebsd/process_open_files.cpp @@ -0,0 +1,76 @@ +/* + * 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 +#include + +#include "osquery/tables/system/freebsd/procstat.h" + +namespace osquery { +namespace tables { + +void genDescriptors(struct procstat* pstat, + struct kinfo_proc* proc, + QueryData& results) { + + Row r; + struct filestat_list* files = nullptr; + struct filestat* file = nullptr; + + files = procstat_getfiles(pstat, proc, 0); + if (files == nullptr) { + return; + } + + STAILQ_FOREACH(file, files, next) { + // Skip files that aren't "open" (no fd). + if (file->fs_fd == -1) { + continue; + } + + r["pid"] = INTEGER(proc->ki_pid); + if (file->fs_path == nullptr) { + r["path"] = TEXT(""); + } else { + r["path"] = TEXT(file->fs_path); + } + r["fd"] = BIGINT(file->fs_fd); + + results.push_back(r); + } + + procstat_freefiles(pstat, files); +} + +QueryData genOpenFiles(QueryContext& context) { + QueryData results; + struct kinfo_proc* procs = nullptr; + struct procstat* pstat = nullptr; + + auto cnt = getProcesses(context, &pstat, &procs); + + for (size_t i = 0; i < cnt; i++) { + genDescriptors(pstat, &procs[i], results); + } + + procstatCleanup(pstat, procs); + return results; +} +} +} diff --git a/osquery/tables/system/freebsd/processes.cpp b/osquery/tables/system/freebsd/processes.cpp index fcb068f5..e27aef88 100644 --- a/osquery/tables/system/freebsd/processes.cpp +++ b/osquery/tables/system/freebsd/processes.cpp @@ -8,33 +8,229 @@ * */ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + #include #include +#include +#include + +#include "osquery/tables/system/freebsd/procstat.h" namespace osquery { namespace tables { -QueryData genProcessEnvs(QueryContext& context) { - QueryData results; +void genProcessEnvironment(struct procstat* pstat, + struct kinfo_proc* proc, + QueryData& results) { + char** envs; + unsigned int i; - throw std::domain_error("Table not implemented for FreeBSD"); + envs = procstat_getenvv(pstat, proc, 0); + if (envs != nullptr) { + for (i = 0; envs[i] != NULL; i++) { + Row r; + size_t idx; + std::string buf = std::string(envs[i]); - return results; + r["pid"] = INTEGER(proc->ki_pid); + + idx = buf.find_first_of("="); + r["key"] = buf.substr(0, idx); + r["value"] = buf.substr(idx + 1); + results.push_back(r); + } + + procstat_freeenvv(pstat); + } +} + +void genProcessMap(struct procstat* pstat, + struct kinfo_proc* proc, + QueryData& results) { + struct kinfo_vmentry* vmentry; + unsigned int i; + unsigned int cnt = 0; + + vmentry = procstat_getvmmap(pstat, proc, &cnt); + if (vmentry != nullptr) { + for (i = 0; i < cnt; i++) { + Row r; + + r["pid"] = INTEGER(proc->ki_pid); + r["path"] = TEXT(vmentry[i].kve_path); + r["device"] = TEXT(vmentry[i].kve_vn_rdev); + r["inode"] = INTEGER(vmentry[i].kve_vn_fileid); + r["offset"] = INTEGER(vmentry[i].kve_offset); + + // To match the linux implementation, convert to hex. + char addr_str[17] = {0}; + sprintf(addr_str, "%016lx", vmentry[i].kve_start); + r["start"] = "0x" + TEXT(addr_str); + sprintf(addr_str, "%016lx", vmentry[i].kve_end); + r["end"] = "0x" + TEXT(addr_str); + + std::string permissions; + permissions += (vmentry[i].kve_protection & KVME_PROT_READ) ? "r" : "-"; + permissions += (vmentry[i].kve_protection & KVME_PROT_WRITE) ? "w" : "-"; + permissions += (vmentry[i].kve_protection & KVME_PROT_EXEC) ? "x" : "-"; + // COW is stored as a flag on FreeBSD, but osquery lumps it in the + // permissions column. + permissions += (vmentry[i].kve_flags & KVME_FLAG_COW) ? "p" : "-"; + r["permissions"] = TEXT(permissions); + + if (vmentry[i].kve_vn_fileid == 0 && r["path"].size() > 0) { + r["pseudo"] = INTEGER("1"); + } else { + r["pseudo"] = INTEGER("0"); + } + + results.push_back(r); + } + + procstat_freevmmap(pstat, vmentry); + } +} + +void genProcess(struct procstat* pstat, + struct kinfo_proc* proc, + QueryData& results) { + Row r; + static char path[PATH_MAX]; + char** args; + struct filestat_list* files = nullptr; + struct filestat* file = nullptr; + struct kinfo_vmentry* vmentry = nullptr; + unsigned int i; + unsigned int cnt = 0; + unsigned int pages = 0; + + r["pid"] = INTEGER(proc->ki_pid); + r["parent"] = INTEGER(proc->ki_ppid); + r["name"] = TEXT(proc->ki_comm); + r["uid"] = INTEGER(proc->ki_ruid); + r["euid"] = INTEGER(proc->ki_svuid); + r["gid"] = INTEGER(proc->ki_rgid); + r["egid"] = INTEGER(proc->ki_svgid); + + if (procstat_getpathname(pstat, proc, path, sizeof(path)) == 0) { + r["path"] = TEXT(path); + // 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. + r["on_disk"] = TEXT(osquery::pathExists(r["path"]).toString()); + } + + args = procstat_getargv(pstat, proc, 0); + if (args != nullptr) { + for (i = 0; args[i] != NULL; i++) { + r["cmdline"] += TEXT(args[i]); + // Need to add spaces between arguments, except last one. + if (args[i + 1] != NULL) { + r["cmdline"] += TEXT(" "); + } + } + + procstat_freeargv(pstat); + } + + files = procstat_getfiles(pstat, proc, 0); + if (files != nullptr) { + STAILQ_FOREACH(file, files, next) { + if (file->fs_uflags & PS_FST_UFLAG_CDIR) { + r["cwd"] = TEXT(file->fs_path); + } + else if (file->fs_uflags & PS_FST_UFLAG_RDIR) { + r["root"] = TEXT(file->fs_path); + } + } + + procstat_freefiles(pstat, files); + } + + vmentry = procstat_getvmmap(pstat, proc, &cnt); + if (vmentry != nullptr) { + // Add up all the resident pages for each vmmap entry. + for (i = 0; i < cnt; i++) { + pages += vmentry[i].kve_resident; + } + + // The column is in bytes. + r["resident_size"] += INTEGER(pages * getpagesize()); + + procstat_freevmmap(pstat, vmentry); + } + + // XXX: Not sure how to get these on FreeBSD yet. + r["wired_size"] = INTEGER("0"); + r["phys_footprint"] = INTEGER("0"); + + r["system_time"] = INTEGER(proc->ki_rusage.ru_stime.tv_sec); + r["user_time"] = INTEGER(proc->ki_rusage.ru_utime.tv_sec); + r["start_time"] = INTEGER(proc->ki_start.tv_sec); + + results.push_back(r); } QueryData genProcesses(QueryContext& context) { QueryData results; + struct kinfo_proc* procs = nullptr; + struct procstat* pstat = nullptr; - throw std::domain_error("Table not implemented for FreeBSD"); + auto cnt = getProcesses(context, &pstat, &procs); + for (size_t i = 0; i < cnt; i++) { + genProcess(pstat, &procs[i], results); + } + + procstatCleanup(pstat, procs); return results; } -QueryData genProcessOpenFiles(QueryContext& context) { +QueryData genProcessEnvs(QueryContext& context) { QueryData results; + struct kinfo_proc* procs = nullptr; + struct procstat* pstat = nullptr; - throw std::domain_error("Table not implemented for FreeBSD"); + auto cnt = getProcesses(context, &pstat, &procs); + for (size_t i = 0; i < cnt; i++) { + genProcessEnvironment(pstat, &procs[i], results); + } + + procstatCleanup(pstat, procs); + return results; +} + +QueryData genProcessMemoryMap(QueryContext& context) { + QueryData results; + struct kinfo_proc* procs = nullptr; + struct procstat* pstat = nullptr; + + auto cnt = getProcesses(context, &pstat, &procs); + + for (size_t i = 0; i < cnt; i++) { + genProcessMap(pstat, &procs[i], results); + } + + procstatCleanup(pstat, procs); return results; } } diff --git a/osquery/tables/system/freebsd/procstat.cpp b/osquery/tables/system/freebsd/procstat.cpp new file mode 100644 index 00000000..fbd431e2 --- /dev/null +++ b/osquery/tables/system/freebsd/procstat.cpp @@ -0,0 +1,92 @@ +/* + * 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 { +namespace tables { + +/** + * A helper function to retrieve processes using libprocstat(3). It is the + * responsibility of the caller to call procstat_freeprocs() and + * procstat_close() when done. + * + * Returns the number of processes in the "procs" list. On failure, returns 0 + * and sets pstat and procs to NULL. + * + * Errors are not well exposed in some of the calls in libprocstat(3). This is + * a bit of a bummer as libkvm(3) is much better in that respect but I would + * rather use libprocstat(3) as it provides a nicer abstraction. + */ +unsigned int getProcesses(QueryContext& context, + struct procstat** pstat, + struct kinfo_proc** procs) { + std::set pids; + unsigned int cnt = 0; + + *pstat = procstat_open_sysctl(); + if (*pstat == nullptr) { + TLOG << "Problem in procstat_open_sysctl()"; + return 0; + } + + if (context.constraints["pid"].exists()) { + pids = context.constraints["pid"].getAll(EQUALS); + + // Generate data for all pids in the vector. + // If there are comparison constraints this could apply the operator + // before generating the process structure. + for (const auto& pid : pids) { + *procs = procstat_getprocs(*pstat, KERN_PROC_PID, std::stoi(pid), &cnt); + if (*procs == nullptr) { + TLOG << "Problem retrieving processes."; + procstat_close(*pstat); + *pstat = nullptr; + return 0; + } + } + } else { + // Get all PIDS. + *procs = procstat_getprocs(*pstat, KERN_PROC_PROC, 0, &cnt); + if (*procs == nullptr) { + TLOG << "Problem retrieving processes."; + procstat_close(*pstat); + *pstat = nullptr; + return 0; + } + } + + return cnt; +} + +/** + * Helper function to cleanup the libprocstat(3) pointers used. + */ +void procstatCleanup(struct procstat* pstat, struct kinfo_proc* procs) { + if (procs != nullptr) { + procstat_freeprocs(pstat, procs); + } + + if (pstat != nullptr) { + procstat_close(pstat); + } +} + +} +} diff --git a/osquery/tables/system/freebsd/procstat.h b/osquery/tables/system/freebsd/procstat.h new file mode 100644 index 00000000..48a7460e --- /dev/null +++ b/osquery/tables/system/freebsd/procstat.h @@ -0,0 +1,29 @@ +/* + * 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. + * + */ + +#pragma once + +#include +#include +#include + +#include + +namespace osquery { +namespace tables { + +unsigned int getProcesses(QueryContext& context, + struct procstat** pstat, + struct kinfo_proc** procs); + +void procstatCleanup(struct procstat* pstat, struct kinfo_proc* procs); + +} +} diff --git a/osquery/tables/utils/tests/yara_tests.cpp b/osquery/tables/utils/tests/yara_tests.cpp index d875c686..13c60b64 100644 --- a/osquery/tables/utils/tests/yara_tests.cpp +++ b/osquery/tables/utils/tests/yara_tests.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Wesley Shields + * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the diff --git a/osquery/tables/utils/yara.cpp b/osquery/tables/utils/yara.cpp index 2f81a740..312ae7cd 100644 --- a/osquery/tables/utils/yara.cpp +++ b/osquery/tables/utils/yara.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Wesley Shields + * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the diff --git a/osquery/tables/utils/yara_utils.cpp b/osquery/tables/utils/yara_utils.cpp index 3602df0a..94abe827 100644 --- a/osquery/tables/utils/yara_utils.cpp +++ b/osquery/tables/utils/yara_utils.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Wesley Shields + * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the