core/unittest/common/ProcParserUnittest.cpp (335 lines of code) (raw):
// Copyright 2023 iLogtail Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <coolbpf/security/bpf_process_event_type.h>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <memory>
#include <string>
#include "common/ProcParser.h"
#include "unittest/Unittest.h"
namespace logtail {
class ProcParserUnittest : public ::testing::Test {
public:
void TestGetPIDCmdline();
void TestGetPIDComm();
void TestGetPIDEnviron();
void TestGetPIDCWD();
void TestGetPIDDockerId();
void TestGetPIDExePath();
void TestGetLoginUid();
void TestGetPIDNsInode();
void TestProcsFilename();
void TestReadStat();
void TestReadStatus();
protected:
void SetUp() override {
mTestRoot = std::filesystem::path(GetProcessExecutionDir()) / "ProcParserUnittestDir";
mProcDir = mTestRoot / "proc";
std::filesystem::create_directories(mProcDir);
mParser = std::make_unique<ProcParser>(mTestRoot.string());
}
void TearDown() override { std::filesystem::remove_all(mTestRoot); }
void WriteStringWithNulls(const std::filesystem::path& path, const char* data, size_t size) {
std::ofstream ofs(path, std::ios::binary);
ofs.write(data, size);
}
void CreateProcTestFiles(int pid) {
auto pidDir = mProcDir / std::to_string(pid);
std::filesystem::create_directories(pidDir);
// Create cmdline file with null separators
const char cmdline[] = {'t', 'e', 's', 't', ' ', 'p', 'r', 'o', 'g', 'r', 'a',
'm', '\0', 'a', 'r', 'g', '1', '\0', 'a', 'r', 'g', '2'};
WriteStringWithNulls(pidDir / "cmdline", cmdline, sizeof(cmdline));
// Create comm file
std::ofstream(pidDir / "comm") << "test_program";
// Create environ file with null separators
const char environ[]
= {'P', 'A', 'T', 'H', '=', '/', 'u', 's', 'r', '/', 'b', 'i', 'n', '\0', 'U', 'S', 'E', 'R',
'=', 'r', 'o', 'o', 't', '\0', 'H', 'O', 'M', 'E', '=', '/', 'r', 'o', 'o', 't', '\0'};
WriteStringWithNulls(pidDir / "environ", environ, sizeof(environ));
// Create status file
std::ofstream status(pidDir / "status");
status << "Name: test_program\n"
<< "Uid: 1000 1000 1000 1000\n"
<< "Gid: 1000 1000 1000 1000\n";
status.close();
// Create loginuid file
std::ofstream(pidDir / "loginuid") << "1000";
// Create cgroup file
std::ofstream(pidDir / "cgroup")
<< "0::/docker/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
// Create exe symlink
std::filesystem::create_directories(mTestRoot / "usr" / "bin");
std::ofstream(mTestRoot / "usr" / "bin" / "test_program") << "test program binary";
std::filesystem::create_symlink(mTestRoot / "usr" / "bin" / "test_program", pidDir / "exe");
// Create cwd symlink
std::filesystem::create_directories(mTestRoot / "home" / "user");
std::filesystem::create_symlink(mTestRoot / "home" / "user", pidDir / "cwd");
// Create ns directory and net symlink
std::filesystem::create_directories(pidDir / "ns");
std::filesystem::create_symlink("net:[4026531992]", pidDir / "ns" / "net");
}
void CreateProcTestFile(int pid, const std::string& filename, const std::string& cgroupContent) {
auto pidDir = mProcDir / std::to_string(pid);
std::filesystem::create_directories(pidDir);
std::ofstream(pidDir / filename) << cgroupContent;
}
void CreateProcCgroupTestFile(int pid, const std::string& cgroupContent) {
CreateProcTestFile(pid, "cgroup", cgroupContent);
}
void CreateProcStatTestFile(int pid, const std::string& cgroupContent) {
CreateProcTestFile(pid, "stat", cgroupContent);
}
void CreateProcStatusTestFile(int pid, const std::string& cgroupContent) {
CreateProcTestFile(pid, "status", cgroupContent);
}
private:
std::filesystem::path mTestRoot;
std::filesystem::path mProcDir;
std::unique_ptr<ProcParser> mParser;
};
void ProcParserUnittest::TestGetPIDCmdline() {
const int testPid = 12345;
CreateProcTestFiles(testPid);
std::string cmdline = mParser->GetPIDCmdline(testPid);
const char expected[] = "test program\0arg1\0arg2";
APSARA_TEST_TRUE_DESC(cmdline == std::string(expected, sizeof(expected) - 1), "Cmdline should match");
}
void ProcParserUnittest::TestGetPIDComm() {
const int testPid = 12345;
CreateProcTestFiles(testPid);
std::string comm = mParser->GetPIDComm(testPid);
APSARA_TEST_STREQ_DESC(comm.c_str(), "test_program", "Comm should match");
}
void ProcParserUnittest::TestGetPIDEnviron() {
const int testPid = 12345;
CreateProcTestFiles(testPid);
std::string environ = mParser->GetPIDEnviron(testPid);
APSARA_TEST_TRUE(environ.find("PATH=/usr/bin") != std::string::npos);
APSARA_TEST_TRUE(environ.find("USER=root") != std::string::npos);
APSARA_TEST_TRUE(environ.find("HOME=/root") != std::string::npos);
}
void ProcParserUnittest::TestGetPIDCWD() {
const int testPid = 12345;
CreateProcTestFiles(testPid);
std::string cwd;
uint32_t flags = mParser->GetPIDCWD(testPid, cwd);
APSARA_TEST_TRUE(cwd.find("/home/user") != std::string::npos);
APSARA_TEST_EQUAL(flags & static_cast<uint32_t>(EVENT_ROOT_CWD), 0U);
}
void ProcParserUnittest::TestReadStat() {
const int testPid = 661798;
CreateProcStatTestFile(
testPid,
R"(661798 (docker-init) S 661766 661798 661798 0 -1 1077936384 1010 0 0 0 4115 7535 0 0 20 0 1 0 1149556817 1060864 1 18446744073709551615 1 1 0 0 0 0 0 3145728 0 0 0 0 17 86 0 0 0 0 0 0 0 0 0 0 0 0 0)");
ProcessStat ps;
APSARA_TEST_TRUE(mParser->ReadProcessStat(testPid, ps));
APSARA_TEST_EQUAL(661798, ps.pid);
APSARA_TEST_EQUAL("docker-init", ps.name);
APSARA_TEST_EQUAL('S', ps.state);
APSARA_TEST_EQUAL(661766, ps.parentPid);
APSARA_TEST_EQUAL(0, ps.tty);
APSARA_TEST_EQUAL(1010UL, ps.minorFaults);
APSARA_TEST_EQUAL(0UL, ps.majorFaults);
APSARA_TEST_EQUAL(4115UL, ps.utimeTicks);
APSARA_TEST_EQUAL(7535UL, ps.stimeTicks);
APSARA_TEST_EQUAL(0UL, ps.cutimeTicks);
APSARA_TEST_EQUAL(0UL, ps.cstimeTicks);
APSARA_TEST_EQUAL(20, ps.priority);
APSARA_TEST_EQUAL(0, ps.nice);
APSARA_TEST_EQUAL(1, ps.numThreads);
APSARA_TEST_EQUAL(1149556817L, ps.startTicks);
APSARA_TEST_EQUAL(1060864UL, ps.vSize);
APSARA_TEST_EQUAL(1UL, ps.rss);
APSARA_TEST_EQUAL(86, ps.processor);
}
void ProcParserUnittest::TestReadStatus() {
const int testPid = 661798;
CreateProcStatusTestFile(testPid, R"(Name: docker-init
Umask: 0022
State: S (sleeping)
Tgid: 661798
Ngid: 0
Pid: 661798
PPid: 661766
TracerPid: 0
Uid: 10 10 10 10
Gid: 10 10 10 10
FDSize: 64
Groups: 0 1 2 3 4 6 10 11 20 26 27
NStgid: 661798 1
NSpid: 661798 1
NSpgid: 661798 1
NSsid: 661798 1
VmPeak: 1036 kB
VmSize: 1036 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 4 kB
VmRSS: 4 kB
RssAnon: 4 kB
RssFile: 0 kB
RssShmem: 0 kB
VmData: 172 kB
VmStk: 132 kB
VmExe: 708 kB
VmLib: 8 kB
VmPTE: 32 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
CoreDumping: 0
THP_enabled: 1
Threads: 1
SigQ: 10/1543275
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000300000
SigCgt: 0000000000000000
CapInh: 000000000000ffff
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
CapAmb: 000000000000ffff
NoNewPrivs: 0
Seccomp: 0
Seccomp_filters: 0
Speculation_Store_Bypass: thread vulnerable
Cpus_allowed: ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-95
Mems_allowed: 00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 9245512
nonvoluntary_ctxt_switches: 5468)");
ProcessStatus ps;
APSARA_TEST_TRUE(mParser->ReadProcessStatus(testPid, ps));
APSARA_TEST_EQUAL(661798, ps.pid);
// Test UID information (all values are 0 in the test data)
APSARA_TEST_EQUAL(10U, ps.realUid);
APSARA_TEST_EQUAL(10U, ps.effectiveUid);
APSARA_TEST_EQUAL(10U, ps.savedUid);
APSARA_TEST_EQUAL(10U, ps.fsUid);
// Test GID information (all values are 0 in the test data)
APSARA_TEST_EQUAL(10U, ps.realGid);
APSARA_TEST_EQUAL(10U, ps.effectiveGid);
APSARA_TEST_EQUAL(10U, ps.savedGid);
APSARA_TEST_EQUAL(10U, ps.fsGid);
// Test namespace thread group IDs
APSARA_TEST_EQUAL(2UL, ps.nstgid.size());
if (ps.nstgid.size() >= 2) {
APSARA_TEST_EQUAL(661798, ps.nstgid[0]);
APSARA_TEST_EQUAL(1, ps.nstgid[1]);
}
// Test process capabilities (as hex values from the status file)
APSARA_TEST_EQUAL(0x000000000000ffffUL, ps.capInh); // 000000000000ffff
APSARA_TEST_EQUAL(0x000001ffffffffffUL, ps.capPrm); // 000001ffffffffff
APSARA_TEST_EQUAL(0x000001ffffffffffUL, ps.capEff); // 000001ffffffffff
}
void ProcParserUnittest::TestGetPIDDockerId() {
const int testPid = 12345;
// K8s containerd cgroup file
CreateProcCgroupTestFile(
testPid,
R"(13:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
12:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
11:perf_event:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
10:freezer:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
9:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
8:cpuset:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
7:net_cls,net_prio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
6:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
5:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
4:ioasids:/
3:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
2:rdma:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod4d40fb1a_bbeb_4ba7_b4d7_29e268072415.slice/cri-containerd-8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09.scope
0::/
)");
std::string dockerId;
int offset = mParser->GetPIDDockerId(testPid, dockerId);
APSARA_TEST_STREQ_DESC(
dockerId.c_str(), "8a53be7205b36249cca2cd327abc1932233426c717a5a9008eac6724dfc62f09", "Docker ID should match");
APSARA_TEST_EQUAL(15, offset);
// K8s docker cgroup file
CreateProcCgroupTestFile(
testPid,
R"(12:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
11:rdma:/
10:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
9:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
8:cpuset:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
7:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
6:perf_event:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
5:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
4:freezer:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
3:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
2:net_cls,net_prio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-poddc9b221f_eb6e_4af5_8328_c9cd9058c064.slice/docker-212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60.scope
)");
offset = mParser->GetPIDDockerId(testPid, dockerId);
APSARA_TEST_STREQ_DESC(
dockerId.c_str(), "212634dc854800cbb27247c97d5314161b09b9f592bb3da2245b2f8805d81f60", "Docker ID should match");
APSARA_TEST_EQUAL(7, offset);
// local docker cgroup file
CreateProcCgroupTestFile(testPid,
R"(12:blkio:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
11:ioasids:/
10:hugetlb:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
9:pids:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
8:rdma:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
7:devices:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
6:freezer:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
5:perf_event:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
4:memory:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
3:cpuset,cpu,cpuacct:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
2:net_cls,net_prio:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
1:name=systemd:/docker/f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b
0::/
)");
offset = mParser->GetPIDDockerId(testPid, dockerId);
APSARA_TEST_STREQ_DESC(
dockerId.c_str(), "f1549dc3c94d8114b4de51487eb0c31cb8169bde5f798dea5cb980ea2018771b", "Docker ID should match");
APSARA_TEST_EQUAL(0, offset);
// no containerid cgroup file
CreateProcCgroupTestFile(testPid,
R"(12:blkio:/user.slice
11:ioasids:/
10:hugetlb:/
9:pids:/user.slice/user-2917.slice/session-168660.scope
8:rdma:/
7:devices:/
6:freezer:/
5:perf_event:/
4:memory:/user.slice/user-2917.slice/session-168660.scope
3:cpuset,cpu,cpuacct:/
2:net_cls,net_prio:/
1:name=systemd:/user.slice/user-2917.slice/session-168660.scope
0::/
)");
offset = mParser->GetPIDDockerId(testPid, dockerId);
APSARA_TEST_STREQ_DESC(dockerId.c_str(), "", "Docker ID should match");
APSARA_TEST_EQUAL(-1, offset);
}
void ProcParserUnittest::TestGetPIDExePath() {
const int testPid = 12345;
CreateProcTestFiles(testPid);
std::string exePath = mParser->GetPIDExePath(testPid);
APSARA_TEST_TRUE(exePath.find("/usr/bin/test_program") != std::string::npos);
}
void ProcParserUnittest::TestGetLoginUid() {
const int testPid = 12345;
CreateProcTestFiles(testPid);
APSARA_TEST_EQUAL(mParser->GetLoginUid(testPid), 1000U);
}
void ProcParserUnittest::TestGetPIDNsInode() {
const int testPid = 12345;
CreateProcTestFiles(testPid);
uint32_t nsInode = mParser->GetPIDNsInode(testPid, "net");
APSARA_TEST_EQUAL(nsInode, 4026531992U);
}
void ProcParserUnittest::TestProcsFilename() {
const char args[] = {'t', 'e', 's', 't', '\0', 'p', 'r', 'o', 'g', 'r', 'a',
'm', '\0', 'a', 'r', 'g', '1', '\0', 'a', 'r', 'g', '2'};
std::string argsStr(args, sizeof(args));
auto [cmds, filename] = mParser->ProcsFilename(argsStr);
auto idx = argsStr.find('\0');
auto fn = argsStr.substr(0, idx);
auto cmd = argsStr.substr(idx);
APSARA_TEST_STREQ_DESC(filename.c_str(), "test", "Filename should match");
APSARA_TEST_TRUE(cmds.find("program") != std::string::npos);
}
UNIT_TEST_CASE(ProcParserUnittest, TestGetPIDCmdline);
UNIT_TEST_CASE(ProcParserUnittest, TestGetPIDComm);
UNIT_TEST_CASE(ProcParserUnittest, TestGetPIDEnviron);
UNIT_TEST_CASE(ProcParserUnittest, TestGetPIDCWD);
UNIT_TEST_CASE(ProcParserUnittest, TestGetPIDDockerId);
UNIT_TEST_CASE(ProcParserUnittest, TestGetPIDExePath);
UNIT_TEST_CASE(ProcParserUnittest, TestGetLoginUid);
UNIT_TEST_CASE(ProcParserUnittest, TestGetPIDNsInode);
UNIT_TEST_CASE(ProcParserUnittest, TestProcsFilename);
UNIT_TEST_CASE(ProcParserUnittest, TestReadStat);
UNIT_TEST_CASE(ProcParserUnittest, TestReadStatus);
} // namespace logtail
UNIT_TEST_MAIN