2017-12-19 00:04:06 +00:00
|
|
|
/**
|
2016-02-11 19:48:58 +00:00
|
|
|
* Copyright (c) 2014-present, Facebook, Inc.
|
2014-12-18 18:50:47 +00:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
2017-12-19 00:04:06 +00:00
|
|
|
* This source code is licensed under both the Apache 2.0 license (found in the
|
|
|
|
* LICENSE file in the root directory of this source tree) and the GPLv2 (found
|
|
|
|
* in the COPYING file in the root directory of this source tree).
|
|
|
|
* You may select, at your option, one of the above-listed licenses.
|
2014-12-18 18:50:47 +00:00
|
|
|
*/
|
2014-11-19 22:53:42 +00:00
|
|
|
|
2015-02-13 10:45:43 +00:00
|
|
|
#include <linux/limits.h>
|
2014-11-19 22:53:42 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include <boost/filesystem.hpp>
|
|
|
|
|
2014-12-03 23:14:02 +00:00
|
|
|
#include <osquery/filesystem.h>
|
2015-01-11 10:17:10 +00:00
|
|
|
#include <osquery/logger.h>
|
2014-11-19 22:53:42 +00:00
|
|
|
|
2018-02-22 00:36:51 +00:00
|
|
|
#include "osquery/core/conversions.h"
|
|
|
|
#include "osquery/filesystem/linux/proc.h"
|
|
|
|
|
2014-11-19 22:53:42 +00:00
|
|
|
namespace osquery {
|
2018-02-22 00:36:51 +00:00
|
|
|
const std::vector<std::string> kUserNamespaceList = {
|
|
|
|
"cgroup", "ipc", "mnt", "net", "pid", "user", "uts"};
|
2014-11-19 22:53:42 +00:00
|
|
|
|
2018-02-22 00:36:51 +00:00
|
|
|
Status procGetNamespaceInode(ino_t& inode,
|
|
|
|
const std::string& namespace_name,
|
|
|
|
const std::string& process_namespace_root) {
|
|
|
|
inode = 0;
|
2014-11-19 22:53:42 +00:00
|
|
|
|
2018-02-22 00:36:51 +00:00
|
|
|
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<ino_t>(
|
|
|
|
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(0, "OK");
|
|
|
|
}
|
|
|
|
|
|
|
|
Status procGetProcessNamespaces(const std::string& process_id,
|
|
|
|
ProcessNamespaceList& namespace_list,
|
|
|
|
std::vector<std::string> 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()) {
|
2018-03-29 18:39:30 +00:00
|
|
|
continue;
|
2014-11-19 22:53:42 +00:00
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
|
|
|
namespace_list[namespace_name] = namespace_inode;
|
2014-11-19 22:53:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return Status(0, "OK");
|
|
|
|
}
|
|
|
|
|
2018-02-22 00:36:51 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
// Two of the fields are the local/remote address/port pairs.
|
|
|
|
auto locals = osquery::split(fields[1], ":");
|
|
|
|
auto remotes = osquery::split(fields[2], ":");
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
result.push_back(std::move(socket_info));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Status(0);
|
2018-02-22 00:36:51 +00:00
|
|
|
}
|
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
// 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));
|
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
return Status(0);
|
2018-02-22 00:36:51 +00:00
|
|
|
}
|
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
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);
|
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
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) {
|
2018-02-22 00:36:51 +00:00
|
|
|
return true;
|
2014-11-19 22:53:42 +00:00
|
|
|
}
|
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
std::string inode = link.substr(8, link.size() - 9);
|
|
|
|
result[inode] = {pid, fd};
|
|
|
|
return true;
|
|
|
|
};
|
2018-02-22 00:36:51 +00:00
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
return procEnumerateProcessDescriptors<decltype(result)>(
|
|
|
|
pid, result, callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
Status procProcesses(std::set<std::string>& processes) {
|
|
|
|
auto callback = [](const std::string& pid,
|
|
|
|
std::set<std::string>& processes) -> bool {
|
|
|
|
processes.insert(pid);
|
2018-02-22 00:36:51 +00:00
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2018-03-29 18:40:43 +00:00
|
|
|
return procEnumerateProcesses<decltype(processes)>(processes, callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
Status procDescriptors(const std::string& process,
|
|
|
|
std::map<std::string, std::string>& descriptors) {
|
|
|
|
auto callback = [](const std::string& pid,
|
|
|
|
const std::string& fd,
|
|
|
|
const std::string& link_name,
|
|
|
|
std::map<std::string, std::string>& descriptors) -> bool {
|
|
|
|
|
|
|
|
descriptors[fd] = link_name;
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
return procEnumerateProcessDescriptors<decltype(descriptors)>(
|
|
|
|
process, descriptors, callback);
|
2014-11-19 22:53:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Status procReadDescriptor(const std::string& process,
|
|
|
|
const std::string& descriptor,
|
|
|
|
std::string& result) {
|
|
|
|
auto link = kLinuxProcPath + "/" + process + "/fd/" + descriptor;
|
|
|
|
|
2017-09-08 06:08:27 +00:00
|
|
|
char result_path[PATH_MAX] = {0};
|
|
|
|
auto size = readlink(link.c_str(), result_path, sizeof(result_path) - 1);
|
|
|
|
if (size >= 0) {
|
2014-11-19 22:53:42 +00:00
|
|
|
result = std::string(result_path);
|
2017-09-08 06:08:27 +00:00
|
|
|
return Status(0);
|
2018-03-29 18:40:43 +00:00
|
|
|
} else {
|
|
|
|
return Status(1, "Could not read path");
|
2014-11-19 22:53:42 +00:00
|
|
|
}
|
|
|
|
}
|
2018-03-29 18:40:43 +00:00
|
|
|
|
2018-02-22 00:36:51 +00:00
|
|
|
} // namespace osquery
|