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.
This commit is contained in:
Wesley Shields 2015-05-29 04:27:10 +00:00
parent ce3ac8a7e3
commit 6558f605ff
12 changed files with 530 additions and 17 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <libprocstat.h>
#include <osquery/core.h>
#include <osquery/logger.h>
#include <osquery/tables.h>
#include "osquery/tables/system/freebsd/procstat.h"
namespace osquery {
namespace tables {
// Heavily inspired by procstat(1) on FreeBSD.
std::pair<std::string, int>& 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<std::string, int> 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<std::string, int> 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;
}
}
}

View File

@ -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

View File

@ -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 <stdlib.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/queue.h>
#include <libprocstat.h>
#include <osquery/core.h>
#include <osquery/logger.h>
#include <osquery/tables.h>
#include <osquery/filesystem.h>
#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;
}
}
}

View File

@ -8,33 +8,229 @@
*
*/
#include <string>
#include <map>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <paths.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/user.h>
#include <libprocstat.h>
#include <boost/algorithm/string/trim.hpp>
#include <osquery/core.h>
#include <osquery/tables.h>
#include <osquery/filesystem.h>
#include <osquery/logger.h>
#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;
}
}

View File

@ -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 <stdlib.h>
#include <unistd.h>
#include <sys/user.h>
#include <sys/sysctl.h>
#include <sys/queue.h>
#include <libprocstat.h>
#include <osquery/tables.h>
#include <osquery/logger.h>
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<std::string> 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);
}
}
}
}

View File

@ -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 <sys/queue.h>
#include <sys/user.h>
#include <libprocstat.h>
#include <osquery/tables.h>
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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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