Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cgroup parsing to retrieve container ID from /proc/[pid]/cgroup #123

Merged
merged 18 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,36 @@ target_link_libraries(EventProcessorTests ${Boost_LIBRARIES}

add_test(EventProcessor ${CMAKE_BINARY_DIR}/EventProcessorTests --log_sink=EventProcessorTests.log --report_sink=EventProcessorTests.report)

add_executable(ProcessInfoTests
auoms_version.h
ProcessInfoTests.cpp
Event.cpp
Signals.cpp
Logger.cpp
Config.cpp
UserDB.cpp
RunBase.cpp
ProcessInfo.cpp
ProcFilter.cpp
ProcessTree.cpp
FiltersEngine.cpp
StringUtils.cpp
ExecveConverter.cpp
)

target_link_libraries(ProcessInfoTests ${Boost_LIBRARIES}
libre2.a
dl
pthread
rt
)

if(NOT DO_STATIC_LINK)
target_compile_definitions(ProcessInfoTests PUBLIC BOOST_TEST_DYN_LINK=1)
endif()

add_test(NAME ProcessInfoTests COMMAND ProcessInfoTests)

add_executable(ProcessTreeTests
auoms_version.h
ProcessTreeTests.cpp
Expand Down
85 changes: 85 additions & 0 deletions ProcessInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,79 @@ int ProcessInfo::read_and_parse_status(int pid) {
return 0;
}

int ProcessInfo::ExtractCGroupContainerId(const std::string& content) {
const char* ptr = content.c_str();
const char* line_end = nullptr;

const char *containerd_prefix = "/containerd-";
const size_t containerd_prefix_len = strlen(containerd_prefix);
const char *docker_prefix = "/docker/";
const size_t docker_prefix_len = strlen(docker_prefix);
const char *system_docker_prefix = "/system.slice/docker-";
const size_t system_docker_prefix_len = strlen(system_docker_prefix);

while ((line_end = strchr(ptr, '\n')) != nullptr) {
// Check for containerd format
const char *containerd_pos = strstr(ptr, containerd_prefix);
if (containerd_pos != nullptr && containerd_pos < line_end) {
if (containerd_pos + containerd_prefix_len + 12 <= line_end) {
_container_id = std::string(containerd_pos + containerd_prefix_len, 12); // Extract the first 12 characters of the container ID
return 0;
}
}

// Check for Docker format
const char *docker_pos = strstr(ptr, docker_prefix);
if (docker_pos != nullptr && docker_pos < line_end) {
if (docker_pos + docker_prefix_len + 12 <= line_end) {
_container_id = std::string(docker_pos + docker_prefix_len, 12); // Extract the first 12 characters of the container ID
return 0;
}
}

// Check for system.slice Docker format
const char *system_docker_pos = strstr(ptr, system_docker_prefix);
if (system_docker_pos != nullptr && system_docker_pos < line_end) {
if (system_docker_pos + system_docker_prefix_len + 12 <= line_end) {
_container_id = std::string(system_docker_pos + system_docker_prefix_len, 12); // Extract the first 12 characters of the container ID
return 0;
}
}

ptr = line_end + 1;
}

return 1;
}

int ProcessInfo::read_and_parse_cgroup(int pid) {
std::array<char, 64> path;
std::array<char, 2048> data;

snprintf(path.data(), path.size(), "/proc/%d/cgroup", pid);

int fd = ::open(path.data(), O_RDONLY | O_CLOEXEC);
if (fd < 0) {
if (errno != ENOENT && errno != ESRCH) {
Logger::Warn("Failed to open /proc/%d/cgroup: %s", pid, strerror(errno));
}
return -1;
}

auto nr = ::read(fd, data.data(), data.size());
if (nr <= 0) {
close(fd);
if (nr < 0 && errno != ENOENT && errno != ESRCH) {
Logger::Warn("Failed to read /proc/%d/cgroup: %s", pid, strerror(errno));
}
return -1;
}
close(fd);

std::string content(data.data(), nr);
return ExtractCGroupContainerId(content);
}

bool ProcessInfo::read(int pid) {
std::array<char, 64> path;

Expand All @@ -390,6 +463,17 @@ bool ProcessInfo::read(int pid) {
return false;
}

pret = read_and_parse_cgroup(pid);
if (pret != 0) {
if (pret > 0) {
Logger::Warn("Failed to parse /proc/%d/cgroup", pid);
}
else{
Logger::Warn("Wrong cgroup format for /proc/%d/cgroup", pid);
}
return false;
}

auto exe_status = read_link(path.data(), _exe);
if (exe_status < 0) {
// EACCES (Permission denied) will be seen occasionally (probably due to racy nature of /proc iteration)
Expand Down Expand Up @@ -471,6 +555,7 @@ void ProcessInfo::clear() {
_cmdline.clear();
_cmdline_truncated = false;
_starttime_str.clear();
_container_id.clear();
}

std::unique_ptr<ProcessInfo> ProcessInfo::Open(int cmdline_size_limit) {
Expand Down
10 changes: 10 additions & 0 deletions ProcessInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class ProcessInfo {
inline int egid() { return _egid; }
inline int sgid() { return _sgid; }
inline int fsgid() { return _fsgid; }
inline std::string_view container_id() { return std::string_view(_container_id.data(), _container_id.size()); }

inline std::string_view comm() { return std::string_view(_comm.data(), _comm_size); }
inline std::string exe() { return _exe; }
Expand All @@ -55,12 +56,17 @@ class ProcessInfo {
std::string starttime();

inline bool is_cmdline_truncated() { return _cmdline_truncated; }

protected:
int ExtractCGroupContainerId(const std::string& content);


private:
explicit ProcessInfo(void* dp, int cmdline_size_limit);

int read_and_parse_stat(int pid);
int read_and_parse_status(int pid);
int read_and_parse_cgroup(int pid);

bool read(int pid);
void clear();
Expand Down Expand Up @@ -89,6 +95,10 @@ class ProcessInfo {
std::vector<uint8_t> _cmdline;
std::string _starttime_str;
bool _cmdline_truncated;
std::string _container_id;

// Declare the test class as a friend
friend class ProcessInfoTests;
};

#endif //AUOMS_PROCESS_INFO_H
57 changes: 57 additions & 0 deletions ProcessInfoTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
microsoft-oms-auditd-plugin

Copyright (c) Microsoft Corporation

All rights reserved.

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#define BOOST_TEST_MODULE "ProcessInfoTests"
#include <boost/test/included/unit_test.hpp>

#include "ProcessInfo.h"
#include "Logger.h"
#include "StringUtils.h"
#include <vector>

class ProcessInfoTests {
public:
static int ExtractCGroupContainerId(ProcessInfo& processInfo, const std::string& content) {
return processInfo.ExtractCGroupContainerId(content);
}
};

BOOST_AUTO_TEST_CASE(container_id_extraction_test) {
// Create an instance of ProcessInfo using the Open method
auto processInfo = ProcessInfo::Open(0);

// Ensure the instance is valid
BOOST_REQUIRE(processInfo != nullptr);

// Test containerd format
std::string containerd_line = "some text /containerd-ebe83cd204c5 more text\n";
BOOST_CHECK_EQUAL(ProcessInfoTests::ExtractCGroupContainerId(*processInfo, containerd_line), 0);
BOOST_CHECK_EQUAL(processInfo->container_id(), "ebe83cd204c5");

// Test Docker format
std::string docker_line = "some text /docker/ebe83cd204c5 more text\n";
BOOST_CHECK_EQUAL(ProcessInfoTests::ExtractCGroupContainerId(*processInfo, docker_line), 0);
BOOST_CHECK_EQUAL(processInfo->container_id(), "ebe83cd204c5");

// Test system.slice Docker format
std::string system_docker_line = "some text /system.slice/docker-ebe83cd204c5 more text\n";
BOOST_CHECK_EQUAL(ProcessInfoTests::ExtractCGroupContainerId(*processInfo, system_docker_line), 0);
BOOST_CHECK_EQUAL(processInfo->container_id(), "ebe83cd204c5");

// Test invalid format
std::string invalid_line = "some text without container id\n";
BOOST_CHECK_NE(ProcessInfoTests::ExtractCGroupContainerId(*processInfo, invalid_line), 0);
}
29 changes: 29 additions & 0 deletions ProcessTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ std::shared_ptr<ProcessTreeItem> ProcessTree::AddProcess(enum ProcessTreeSource
std::shared_ptr<ProcessTreeItem> process;

std::string containerid = ExtractContainerId(exe, cmdline);

auto it = _processes.find(pid);
if (it != _processes.end()) {
process = it->second;
Expand Down Expand Up @@ -426,6 +427,27 @@ std::shared_ptr<ProcessTreeItem> ProcessTree::AddProcess(enum ProcessTreeSource
ApplyFlags(process);
_processes[pid] = process;
}

// The purpose of extracting the container ID from cgroup is to accurately identify
// the container in which a process is running. This is particularly important for
// monitoring and logging purposes in containerized environments, where applications
// are packaged and run in containers. By testing the extraction logic with various
// cgroup formats, we aim to ensure that the container ID is correctly identified in
// all scenarios.
// This method complements the existing logic that extracts the container ID from the
// command line arguments by providing an additional, more reliable source of information.
// This is especially relevant when the container ID is not found through the existing
// logic, which can happen when there is no parent process or when the parent process
// does not have the container ID set. This situation can occur in various scenarios,
// such as when a process is the root process of a container or when the process is
// started by a web service or another system service that does not pass the container
// ID through the command line arguments.
if (process->_containerid.empty()) {
auto p_temp = ReadProcEntry(pid);
if (p_temp) {
process->_containerid = p_temp->_cgroupContainerId;
}
}

return process;
}
Expand Down Expand Up @@ -497,6 +519,12 @@ std::shared_ptr<ProcessTreeItem> ProcessTree::GetInfoForPid(int pid)
struct Ancestor anc = {process->_ppid, parentproc->_exe};
process->_ancestors.emplace_back(anc);
}

// If container ID is still empty, set it to be the cgroup container ID
if (process->_containerid.empty()) {
process->_containerid = process->_cgroupContainerId;
}

_processes[pid] = process;
ApplyFlags(process);
}
Expand Down Expand Up @@ -660,6 +688,7 @@ std::shared_ptr<ProcessTreeItem> ProcessTree::ReadProcEntry(int pid)
process->_gid = pinfo->gid();
process->_ppid = pinfo->ppid();
process->_exe = pinfo->exe();
process->_cgroupContainerId = pinfo->container_id();
pinfo->format_cmdline(process->_cmdline);
process->_containeridfromhostprocess = ExtractContainerId(process->_exe, process->_cmdline);
return process;
Expand Down
1 change: 1 addition & 0 deletions ProcessTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ class ProcessTreeItem {
std::string _exe;
std::string _containerid;
std::string _containeridfromhostprocess;
std::string _cgroupContainerId;
std::string _cmdline;
std::bitset<FILTER_BITSET_SIZE> _flags;
bool _exited;
Expand Down
Loading