diff --git a/.github/workflows/e2e_ci.yml b/.github/workflows/e2e_ci.yml index 55548045d5b..13dae7f8c11 100644 --- a/.github/workflows/e2e_ci.yml +++ b/.github/workflows/e2e_ci.yml @@ -96,6 +96,7 @@ jobs: -DUSE_BUNDLED_GTEST=ON \ .. make -j6 libsinsp_e2e_tests + sudo rm -vfr test/libsinsp_e2e/resources/_proc cd .. - name: Cache build @@ -161,9 +162,12 @@ jobs: # different workers, so we rebuild the drivers. - name: Rebuild drivers run: | - cd build + pushd build make -B driver bpf - cd .. + pushd test/libsinsp_e2e/resources/ + sudo tar xzf fake-proc.tar.gz + popd + popd - name: Run e2e tests with ${{ matrix.driver.name }} 🏎️ if: matrix.arch == 'amd64' diff --git a/test/libsinsp_e2e/CMakeLists.txt b/test/libsinsp_e2e/CMakeLists.txt index 22b44f096be..e57054b264e 100755 --- a/test/libsinsp_e2e/CMakeLists.txt +++ b/test/libsinsp_e2e/CMakeLists.txt @@ -32,6 +32,7 @@ configure_file ( ) add_executable(libsinsp_e2e_tests + capture_to_file_test.cpp container/container.cpp container/container_cgroup.cpp container/docker_utils.cpp @@ -50,6 +51,8 @@ add_executable(libsinsp_e2e_tests threadinfo.cpp thread_state.cpp udp_client_server.cpp + unix_client_server.cpp + unix_udp_client_server.cpp ) if(BUILD_BPF) @@ -103,3 +106,8 @@ file(COPY DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/resources/ ) + +execute_process( + COMMAND tar xzf ${CMAKE_CURRENT_BINARY_DIR}/resources/fake-proc.tar.gz + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/resources/ + ) diff --git a/test/libsinsp_e2e/capture_to_file_test.cpp b/test/libsinsp_e2e/capture_to_file_test.cpp new file mode 100644 index 00000000000..9302b55948a --- /dev/null +++ b/test/libsinsp_e2e/capture_to_file_test.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco 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 "sys_call_test.h" + +#include +#include + +#include + +TEST_F(sys_call_test, can_consume_a_capture_file) +{ + int callnum = 0; + + event_filter_t filter = [&](sinsp_evt* evt) + { + std::string evt_name(evt->get_name()); + return evt_name.find("stat") != std::string::npos && + m_tid_filter(evt) && evt->get_direction() == SCAP_ED_OUT; + }; + + run_callback_t test = [](concurrent_object_handle inspector_handle) + { + struct stat sb; + for(int i = 0; i < 100; i++) + { + stat("/tmp", &sb); + } + }; + + captured_event_callback_t callback = [&](const callback_param& param) { callnum++; }; + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(100, callnum); + + sinsp inspector; + sinsp_evt* event; + + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + auto filename = std::string(LIBSINSP_TEST_CAPTURES_PATH) + test_info->test_case_name() + "_" + + test_info->name() + ".scap"; + inspector.open_savefile(filename); + callnum = 0; + int32_t res; + while((res = inspector.next(&event)) != SCAP_EOF) + { + ASSERT_EQ(SCAP_SUCCESS, res); + std::string evt_name(event->get_name()); + if(evt_name.find("stat") != std::string::npos && + m_tid_filter(event) && event->get_direction() == SCAP_ED_OUT) + { + callnum++; + } + } + + ASSERT_EQ(SCAP_EOF, res); + ASSERT_EQ(100, callnum); +} diff --git a/test/libsinsp_e2e/container/container.cpp b/test/libsinsp_e2e/container/container.cpp index 8fbe783fc10..78d2cd699c7 100644 --- a/test/libsinsp_e2e/container/container.cpp +++ b/test/libsinsp_e2e/container/container.cpp @@ -446,7 +446,7 @@ TEST_F(sys_call_test, container_libvirt) if (system("virsh --help > /dev/null 2>&1") != 0) { - printf("libvirt not installed, skipping test\n"); + GTEST_SKIP() << "libvirt not installed, skipping test"; return; } @@ -722,6 +722,83 @@ static void healthcheck_helper( ASSERT_EQ(cstate.healthcheck_seen, expect_healthcheck) << capture_stats_str; } +static void healthcheck_tracefile_helper( + const std::string& dockerfile, + bool expect_healthcheck, + sinsp_threadinfo::command_category expected_cat = sinsp_threadinfo::CAT_HEALTHCHECK) +{ + container_state cstate; + + std::string build_cmdline("cd " LIBSINSP_TEST_RESOURCES_PATH "/docker/health_dockerfiles && docker build -t cont_health_ut_img -f " + + dockerfile + " . > /dev/null 2>&1"); + ASSERT_TRUE(system(build_cmdline.c_str()) == 0); + + run_callback_t test = [](concurrent_object_handle inspector_handle) + { + // --network=none speeds up the container setup a bit. + ASSERT_TRUE((system("docker run --rm --network=none --name cont_health_ut cont_health_ut_img " + "/bin/sh -c '/bin/sleep 10' > /dev/null 2>&1")) == 0); + }; + + event_filter_t filter = [&](sinsp_evt* evt) + { + std::string evt_name(evt->get_name()); + return evt_name.find("execve") != std::string::npos && + evt->get_direction() == SCAP_ED_OUT; + }; + + captured_event_callback_t callback = [&](const callback_param& param) {return;}; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + + // Now reread the file we just wrote and pass it through + // update_container_state. + + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + auto dumpfile = std::string(LIBSINSP_TEST_CAPTURES_PATH) + test_info->test_case_name() + "_" + + test_info->name() + ".scap"; + + sinsp inspector; + inspector.set_hostname_and_port_resolution_mode(false); + inspector.set_filter("evt.type=execve and evt.dir=<"); + inspector.open_savefile(dumpfile); + inspector.start_capture(); + + while (1) + { + sinsp_evt* ev; + int32_t res = inspector.next(&ev); + + if (res == SCAP_TIMEOUT) + { + continue; + } + if (res == SCAP_FILTERED_EVENT) + { + continue; + } + else if (res == SCAP_EOF) + { + break; + } + ASSERT_TRUE(res == SCAP_SUCCESS); + + update_container_state(&inspector, ev, cstate, expected_cat); + } + + std::string capture_stats_str = capture_stats(&inspector); + + inspector.stop_capture(); + inspector.close(); + + ASSERT_TRUE(cstate.root_cmd_seen) << capture_stats_str; + ASSERT_TRUE(cstate.second_cmd_seen) << capture_stats_str; + ASSERT_EQ(cstate.container_w_health_probe, expect_healthcheck) << capture_stats_str; + ASSERT_EQ(cstate.healthcheck_seen, expect_healthcheck) << capture_stats_str; +} + + // Run container w/o health check, should not find any health check // for the container. Should not identify either the entrypoint // or a second process spawned after as a health check process. @@ -791,6 +868,33 @@ TEST_F(sys_call_test, docker_container_readiness_probe) sinsp_threadinfo::CAT_READINESS_PROBE); } +// Identical to above tests, but read events from a trace file instead +// of live. Only doing selected cases. +TEST_F(sys_call_test, docker_container_healthcheck_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck", true); +} + +TEST_F(sys_call_test, docker_container_healthcheck_cmd_overlap_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck_cmd_overlap", true); +} + +TEST_F(sys_call_test, docker_container_liveness_probe_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck_liveness", + true, + sinsp_threadinfo::CAT_LIVENESS_PROBE); +} + +TEST_F(sys_call_test, docker_container_readiness_probe_trace) +{ + healthcheck_tracefile_helper("Dockerfile.healthcheck_readiness", + true, + sinsp_threadinfo::CAT_READINESS_PROBE); +} + + TEST_F(sys_call_test, docker_container_large_json) { bool saw_container_evt = false; diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/CMakeLists.txt b/test/libsinsp_e2e/resources/docker/health_dockerfiles/CMakeLists.txt new file mode 100644 index 00000000000..0ea56df7da1 --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/CMakeLists.txt @@ -0,0 +1,17 @@ +foreach( + dockerfile + Dockerfile.healthcheck + Dockerfile.healthcheck_shell + Dockerfile.healthcheck_cmd_overlap + Dockerfile.healthcheck_liveness + Dockerfile.healthcheck_readiness + Dockerfile.no_healthcheck + Dockerfile.none_healthcheck +) + + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/${dockerfile} ${CMAKE_CURRENT_BINARY_DIR}/${dockerfile} + COPYONLY + ) + +endforeach(dockerfile) diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck new file mode 100644 index 00000000000..b140f006c7a --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck @@ -0,0 +1,3 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check +HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"] diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_cmd_overlap b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_cmd_overlap new file mode 100644 index 00000000000..405b4153be0 --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_cmd_overlap @@ -0,0 +1,2 @@ +FROM busybox +HEALTHCHECK --interval=0.5s CMD ["/bin/sh", "-c", "/bin/sleep 10"] diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_liveness b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_liveness new file mode 100644 index 00000000000..91cbf0da6d4 --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_liveness @@ -0,0 +1,8 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check + +# This container runs a docker healthcheck, but due to the +# annotation.... label, it gets interpretated as if it were a k8s +# liveness check. +HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"] +LABEL annotation.kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"mysql-app\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"MYSQL_ROOT_PASSWORD\",\"value\":\"no\"}],\"image\":\"mstemm/mysql:healthcheck\",\"livenessProbe\":{\"exec\":{\"command\":[\"/bin/ut-health-check\"]},\"initialDelaySeconds\":5,\"periodSeconds\":5},\"name\":\"mysql\"}]}}\n" diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_readiness b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_readiness new file mode 100644 index 00000000000..5919b746bfb --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_readiness @@ -0,0 +1,8 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check + +# This container runs a docker healthcheck, but due to the +# annotation.... label, it gets interpretated as if it were a k8s +# readiness check. +HEALTHCHECK --interval=0.5s CMD ["/bin/ut-health-check"] +LABEL annotation.kubectl.kubernetes.io/last-applied-configuration="{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"mysql-app\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"env\":[{\"name\":\"MYSQL_ROOT_PASSWORD\",\"value\":\"no\"}],\"image\":\"mstemm/mysql:healthcheck\",\"readinessProbe\":{\"exec\":{\"command\":[\"/bin/ut-health-check\"]},\"initialDelaySeconds\":5,\"periodSeconds\":5},\"name\":\"mysql\"}]}}\n" diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_shell b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_shell new file mode 100644 index 00000000000..0c908253abb --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.healthcheck_shell @@ -0,0 +1,3 @@ +FROM busybox +RUN cp /bin/true /bin/ut-health-check +HEALTHCHECK --interval=0.5s CMD /bin/ut-health-check diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.no_healthcheck b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.no_healthcheck new file mode 100644 index 00000000000..9a3adf68b5f --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.no_healthcheck @@ -0,0 +1 @@ +FROM busybox:latest diff --git a/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.none_healthcheck b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.none_healthcheck new file mode 100644 index 00000000000..f5a257ee6f6 --- /dev/null +++ b/test/libsinsp_e2e/resources/docker/health_dockerfiles/Dockerfile.none_healthcheck @@ -0,0 +1,2 @@ +FROM busybox:latest +HEALTHCHECK NONE diff --git a/test/libsinsp_e2e/resources/fake-proc.tar.gz b/test/libsinsp_e2e/resources/fake-proc.tar.gz new file mode 100644 index 00000000000..af4c9f9b26e Binary files /dev/null and b/test/libsinsp_e2e/resources/fake-proc.tar.gz differ diff --git a/test/libsinsp_e2e/resources/unix_client_server.py b/test/libsinsp_e2e/resources/unix_client_server.py new file mode 100644 index 00000000000..f183eaac650 --- /dev/null +++ b/test/libsinsp_e2e/resources/unix_client_server.py @@ -0,0 +1,43 @@ +#!/usr/bin/python2 +# coding: utf-8 -*- +import socket +import os, os.path +import time +import sys + +PAYLOAD = "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" +NAME = "/tmp/python_unix_sockets_example" + +if sys.argv[1] == 'server': + if os.path.exists(NAME): + os.remove(NAME) + + server = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM ) + server.bind(NAME) + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + + print "STARTED" + server.listen(5) + + connect, address = server.accept() + resp = connect.recv( 1024 ) + connect.send(resp) + connect.close() + server.close() + os.remove(NAME) + +else: + sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) + + if os.path.exists(NAME): + client = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM ) + client.connect(NAME) + + print "STARTED" + + client.send(PAYLOAD) + resp = client.recv(1024) + client.close() + + else: + print >> sys.stderr, "Couldn't Connect!" diff --git a/test/libsinsp_e2e/sys_call_test.cpp b/test/libsinsp_e2e/sys_call_test.cpp index 2bdf22bb44d..fe42ac21e0d 100644 --- a/test/libsinsp_e2e/sys_call_test.cpp +++ b/test/libsinsp_e2e/sys_call_test.cpp @@ -23,6 +23,7 @@ limitations under the License. #include #include +#include #include #include @@ -351,6 +352,89 @@ TEST_F(sys_call_test, close_badfd_dropping) EXPECT_EQ(0, callnum); } +TEST_F(sys_call_test, poll_timeout) +{ + int callnum = 0; + + event_filter_t filter = [&](sinsp_evt* evt) + { + uint16_t type = evt->get_type(); + auto ti = evt->get_thread_info(false); + return (type == PPME_SYSCALL_POLL_E || + type == PPME_SYSCALL_POLL_X) && + ti->m_comm == "test_helper"; + }; + + std::string my_pipe[2]; + + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + subprocess handle(LIBSINSP_TEST_PATH "/test_helper", {"poll_timeout"}); + std::stringstream ss; + ss << handle.out(); + my_pipe[0] = ss.str(); + ss.clear(); + ss.str(" "); + ss << handle.out(); + my_pipe[1] = ss.str(); + ss.clear(); + ss.str(" "); + handle.wait(); + }; + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_POLL_E) + { + // + // stdin and stdout can be a file or a fifo depending + // on how the tests are invoked + // + std::string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p1 " + + my_pipe[1] + ":p4"; + EXPECT_EQ(expected_fds, fds) + << "Value of fds is not one of expected values: " << fds; + EXPECT_EQ("20", e->get_param_value_str("timeout")); + callnum++; + } + else if (type == PPME_SYSCALL_POLL_X) + { + std::string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p0 " + + my_pipe[1] + ":p4"; + int64_t res = std::stol(e->get_param_value_str("res")); + + EXPECT_GT(res, 0); + EXPECT_LE(res, 2); + + switch (res) + { + case 1: + EXPECT_EQ(expected_fds, fds) + << "Value of fds is not one of expected values: " << fds; + ; + break; + case 2: + // + // On EC2 called from jenkins stdin returns POLLHUP + // + EXPECT_EQ(expected_fds, fds) + << "Value of fds is not one of expected values: " << fds; + break; + default: + FAIL(); + } + + callnum++; + } + }; + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(2, callnum); +} + TEST(inspector, invalid_file_name) { sinsp inspector; @@ -1665,6 +1749,123 @@ TEST_F(sys_call_test, failing_execve) EXPECT_EQ(2, callnum); } +TEST_F(sys_call_test, large_execve) +{ + const int buf_size = 100 * 1024; + const int driver_truncation_size = getpagesize(); + const string non_existing_binary = "/non/existent"; + const string existing_binary = "/bin/true"; + + int ctid; + int callnum = 0; + + event_filter_t filter = [&](sinsp_evt* evt) { return evt->get_tid() == ctid; }; + + srandom(42); + + string buf; + while (buf.length() < buf_size) + { + buf.append(std::to_string(random())); + } + + run_callback_t test = [&](concurrent_object_handle inspector) + { + ctid = fork(); + + if (ctid < 0) + { + FAIL(); + } + + if (ctid == 0) + { + { + const char* eargv[] = {non_existing_binary.c_str(), buf.c_str(), NULL}; + + const char* eenvp[] = {buf.c_str(), NULL}; + + int ret = execve(eargv[0], (char* const*)eargv, (char* const*)eenvp); + ASSERT_TRUE(ret < 0); + } + + { + const char* eargv[] = {existing_binary.c_str(), buf.c_str(), NULL}; + + const char* eenvp[] = {buf.c_str(), NULL}; + + int ret = execve(eargv[0], (char* const*)eargv, (char* const*)eenvp); + ASSERT_TRUE(ret == 0); + } + } + else + { + wait(NULL); + sleep(1); + } + }; + + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_EXECVE_19_E || type == PPME_SYSCALL_EXECVE_18_E) + { + ++callnum; + + string filename = e->get_param_value_str("filename"); + + if (callnum == 1) + { + EXPECT_EQ(filename, non_existing_binary); + } + else if (callnum == 3) + { + EXPECT_EQ(filename, existing_binary); + } + else + { + FAIL(); + } + } + else if (type == PPME_SYSCALL_EXECVE_19_X || type == PPME_SYSCALL_EXECVE_18_X) + { + ++callnum; + + string exe = e->get_param_value_str("exe"); + string args = e->get_param_value_str("args"); + + if (callnum == 2) + { + // This is the failed execve. exe and + // args will be available, but env + // will not. + EXPECT_EQ(exe, non_existing_binary.c_str()); + EXPECT_EQ(args, + buf.substr(0, driver_truncation_size - non_existing_binary.length() - 2) + "."); + } + else if (callnum == 4) + { + string env = e->get_param_value_str("env"); + + EXPECT_EQ(exe, existing_binary); + EXPECT_EQ( + args, + buf.substr(0, driver_truncation_size - existing_binary.length() - 2) + "."); + EXPECT_EQ(env, buf.substr(0, driver_truncation_size - 1) + "."); + } + else + { + FAIL(); + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(4, callnum); +} + #ifdef __x86_64__ TEST_F(sys_call_test32, failing_execve) @@ -1771,4 +1972,728 @@ TEST_F(sys_call_test32, failing_execve) EXPECT_EQ(10, callnum); } +TEST_F(sys_call_test32, mmap) +{ + int callnum = 0; + int errno2; + + proc_started_filter ps_filter; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + return tinfo && tinfo->m_comm == "test_helper_32" + && ps_filter(evt); + }; + + uint64_t p = 0; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector_handle) + { + subprocess handle(LIBSINSP_TEST_PATH "/test_helper_32", + {"mmap_test",}); + std::stringstream tmp; + handle.out(); + tmp << handle.out(); + errno2 = std::stoi(tmp.str()); + tmp.clear(); + tmp.str(""); + tmp << handle.out(); + p = (uint64_t)std::stoul(tmp.str()); + tmp.clear(); + tmp.str(""); + handle.wait(); + }; + + uint32_t enter_vmsize; + uint32_t enter_vmrss; + uint32_t exit_vmsize; + uint32_t exit_vmrss; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_MUNMAP_E) + { + callnum++; + + enter_vmsize = e->get_thread_info(false)->m_vmsize_kb; + enter_vmrss = e->get_thread_info(false)->m_vmrss_kb; + + switch (callnum) + { + case 1: + EXPECT_EQ("50", e->get_param_value_str("addr")); + EXPECT_EQ("300", e->get_param_value_str("length")); + break; + case 7: + { + uint64_t addr = 0; + memcpy(&addr, e->get_param_by_name("addr")->m_val, sizeof(uint64_t)); +#ifdef __LP64__ + EXPECT_EQ((uint64_t)p, addr); +#else + EXPECT_EQ(((uint32_t)p), addr); #endif + EXPECT_EQ("1003520", e->get_param_value_str("length")); + break; + } + default: + EXPECT_TRUE(false); + } + } + else if (type == PPME_SYSCALL_MUNMAP_X) + { + callnum++; + + memcpy(&exit_vmsize, e->get_param_by_name("vm_size")->m_val, sizeof(uint32_t)); + memcpy(&exit_vmrss, e->get_param_by_name("vm_rss")->m_val, sizeof(uint32_t)); + EXPECT_EQ(e->get_thread_info(false)->m_vmsize_kb, exit_vmsize); + EXPECT_EQ(e->get_thread_info(false)->m_vmrss_kb, exit_vmrss); + + switch (callnum) + { + case 2: + EXPECT_EQ("EINVAL", e->get_param_value_str("res")); + EXPECT_EQ("-22", e->get_param_value_str("res", false)); + break; + case 8: + EXPECT_EQ("0", e->get_param_value_str("res")); + EXPECT_GT(enter_vmsize, exit_vmsize + 500); + EXPECT_GE(enter_vmrss, enter_vmrss); + break; + default: + EXPECT_TRUE(false); + } + } + else if (type == PPME_SYSCALL_MMAP_E || type == PPME_SYSCALL_MMAP2_E) + { + callnum++; + + enter_vmsize = e->get_thread_info(false)->m_vmsize_kb; + enter_vmrss = e->get_thread_info(false)->m_vmrss_kb; + + switch (callnum) + { + case 3: + EXPECT_EQ("0", e->get_param_value_str("addr")); + EXPECT_EQ("0", e->get_param_value_str("length")); + EXPECT_EQ("PROT_READ|PROT_WRITE|PROT_EXEC", e->get_param_value_str("prot")); + EXPECT_EQ("MAP_SHARED|MAP_PRIVATE|MAP_ANONYMOUS|MAP_DENYWRITE", + e->get_param_value_str("flags")); + EXPECT_EQ("-1", e->get_param_value_str("fd", false)); + + if (type == PPME_SYSCALL_MMAP_E) + { + EXPECT_EQ("0", e->get_param_value_str("offset")); + } + else + { + EXPECT_EQ("0", e->get_param_value_str("pgoffset")); + } + break; + case 5: + EXPECT_EQ("0", e->get_param_value_str("addr")); + EXPECT_EQ("1003520", e->get_param_value_str("length")); + EXPECT_EQ("PROT_READ|PROT_WRITE", e->get_param_value_str("prot")); + EXPECT_EQ("MAP_PRIVATE|MAP_ANONYMOUS", e->get_param_value_str("flags")); + EXPECT_EQ("-1", e->get_param_value_str("fd", false)); + + if (type == PPME_SYSCALL_MMAP_E) + { + EXPECT_EQ("0", e->get_param_value_str("offset")); + } + else + { + EXPECT_EQ("0", e->get_param_value_str("pgoffset")); + } + break; + default: + EXPECT_TRUE(false); + } + } + else if (type == PPME_SYSCALL_MMAP_X || type == PPME_SYSCALL_MMAP2_X) + { + callnum++; + + memcpy(&exit_vmsize, e->get_param_by_name("vm_size")->m_val, sizeof(uint32_t)); + memcpy(&exit_vmrss, e->get_param_by_name("vm_rss")->m_val, sizeof(uint32_t)); + EXPECT_EQ(e->get_thread_info(false)->m_vmsize_kb, exit_vmsize); + EXPECT_EQ(e->get_thread_info(false)->m_vmrss_kb, exit_vmrss); + + switch (callnum) + { + case 4: + { + uint64_t res = 0; + memcpy(&res, e->get_param_by_name("res")->m_val, sizeof(uint64_t)); + EXPECT_EQ(-errno2, (int64_t)res); + break; + } + case 6: + { + uint64_t res = 0; + memcpy(&res, e->get_param_by_name("res")->m_val, sizeof(uint64_t)); + EXPECT_EQ((uint64_t)p, res); + EXPECT_GT(exit_vmsize, enter_vmsize + 500); + EXPECT_GE(exit_vmrss, enter_vmrss); + break; + } + default: + EXPECT_TRUE(false); + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(8, callnum); +} + +TEST_F(sys_call_test32, DISABLED_quotactl_ok) +{ + int callnum = 0; + + // Clean environment + auto ret = system("umount /tmp/testquotamnt"); + ret = system("rm -rf /tmp/testquotactl /tmp/testquotamnt"); + // Setup a tmpdisk to test quotas + char command[] = + "dd if=/dev/zero of=/tmp/testquotactl bs=1M count=200 &&\n" + "echo y | mkfs.ext4 -q /tmp/testquotactl &&\n" + "mkdir -p /tmp/testquotamnt &&\n" + "mount -o usrquota,grpquota,loop /tmp/testquotactl /tmp/testquotamnt &&\n" + "quotacheck -cug /tmp/testquotamnt"; + ret = system(command); + if (ret != 0) + { + // If we don't have quota utilities, skip this test + GTEST_FAIL() << "Setup commands failed!"; + } + + std::ifstream proc_mounts("/proc/mounts"); + std::string mp; + std::string dev; + + for(std::string line; getline(proc_mounts, line);) + { + if (line.find("/tmp/testquotamnt") != string::npos) + { + std::istringstream iss(line); + iss >> dev; + iss >> mp; + } + } + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + return (evt->get_type() == PPME_SYSCALL_QUOTACTL_X || + evt->get_type() == PPME_SYSCALL_QUOTACTL_E) && + tinfo != nullptr && tinfo->m_comm == "test_helper_32"; + }; + + // + // TEST CODE + // + struct dqblk mydqblk, otherdqblk; + struct dqinfo mydqinfo, otherdqinfo; + std::stringstream ss; + run_callback_t test = [&](concurrent_object_handle inspector) + { + subprocess handle(LIBSINSP_TEST_PATH "/test_helper_32", + {"quotactl_ok", mp.c_str(), dev.c_str()}); + + ss << handle.out(); + + int expected_bytes = sizeof(uint64_t)*9; + EXPECT_EQ(expected_bytes, ss.str().size()); + + for (int i = 0; i < 72; i++) { + printf("%02x", (unsigned char)ss.str().c_str()[i]); + } + printf("\n"); + + const char* data_ptr = ss.str().c_str(); + + memcpy(&mydqblk.dqb_bhardlimit, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqblk.dqb_bsoftlimit, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqblk.dqb_curspace, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqblk.dqb_ihardlimit, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqblk.dqb_isoftlimit, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqblk.dqb_btime, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqblk.dqb_itime, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqinfo.dqi_bgrace, data_ptr, sizeof(uint64_t)); + data_ptr += sizeof(uint64_t); + memcpy(&mydqinfo.dqi_igrace, data_ptr, sizeof(uint64_t)); + + //EXPECT_EQ(pipe->readBytes(&mydqblk.dqb_bhardlimit, sizeof(uint64_t)), + // (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqblk.dqb_bsoftlimit, sizeof(uint64_t)), + // (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqblk.dqb_curspace, sizeof(uint64_t)), (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqblk.dqb_ihardlimit, sizeof(uint64_t)), + // (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqblk.dqb_isoftlimit, sizeof(uint64_t)), + // (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqblk.dqb_btime, sizeof(uint64_t)), (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqblk.dqb_itime, sizeof(uint64_t)), (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqinfo.dqi_bgrace, sizeof(uint64_t)), (int)sizeof(uint64_t)); + //EXPECT_EQ(pipe->readBytes(&mydqinfo.dqi_igrace, sizeof(uint64_t)), (int)sizeof(uint64_t)); + handle.wait(); + }; + + // + // OUTPUT VALIDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + if (type == PPME_SYSCALL_QUOTACTL_E) + { + ++callnum; + switch (callnum) + { + case 1: + EXPECT_EQ("Q_QUOTAON", e->get_param_value_str("cmd")); + EXPECT_EQ("USRQUOTA", e->get_param_value_str("type")); + EXPECT_EQ("QFMT_VFS_V0", e->get_param_value_str("quota_fmt")); + break; + case 3: + EXPECT_EQ("Q_GETQUOTA", e->get_param_value_str("cmd")); + EXPECT_EQ("USRQUOTA", e->get_param_value_str("type")); + EXPECT_EQ("0", e->get_param_value_str("id")); + break; + case 5: + EXPECT_EQ("Q_GETINFO", e->get_param_value_str("cmd")); + EXPECT_EQ("USRQUOTA", e->get_param_value_str("type")); + break; + case 7: + EXPECT_EQ("Q_QUOTAOFF", e->get_param_value_str("cmd")); + EXPECT_EQ("USRQUOTA", e->get_param_value_str("type")); + break; + } + } + else if (type == PPME_SYSCALL_QUOTACTL_X) + { + ++callnum; + switch (callnum) + { + case 2: + EXPECT_EQ("0", e->get_param_value_str("res", false)); + EXPECT_EQ(dev.c_str(), e->get_param_value_str("special")); + EXPECT_EQ(mp + "/aquota.user", e->get_param_value_str("quotafilepath")); + break; + case 4: + EXPECT_EQ("0", e->get_param_value_str("res", false)); + EXPECT_EQ(dev.c_str(), e->get_param_value_str("special")); + otherdqblk.dqb_bhardlimit = *reinterpret_cast( + e->get_param_by_name("dqb_bhardlimit")->m_val); + otherdqblk.dqb_bsoftlimit = *reinterpret_cast( + e->get_param_by_name("dqb_bsoftlimit")->m_val); + otherdqblk.dqb_curspace = + *reinterpret_cast(e->get_param_by_name("dqb_curspace")->m_val); + otherdqblk.dqb_ihardlimit = *reinterpret_cast( + e->get_param_by_name("dqb_ihardlimit")->m_val); + otherdqblk.dqb_isoftlimit = *reinterpret_cast( + e->get_param_by_name("dqb_isoftlimit")->m_val); + otherdqblk.dqb_btime = + *reinterpret_cast(e->get_param_by_name("dqb_btime")->m_val); + otherdqblk.dqb_itime = + *reinterpret_cast(e->get_param_by_name("dqb_itime")->m_val); + break; + case 6: + EXPECT_EQ("0", e->get_param_value_str("res", false)); + EXPECT_EQ(dev.c_str(), e->get_param_value_str("special")); + otherdqinfo.dqi_bgrace = + *reinterpret_cast(e->get_param_by_name("dqi_bgrace")->m_val); + otherdqinfo.dqi_igrace = + *reinterpret_cast(e->get_param_by_name("dqi_igrace")->m_val); + break; + case 8: + EXPECT_EQ("0", e->get_param_value_str("res", false)); + EXPECT_EQ(dev.c_str(), e->get_param_value_str("special")); + break; + } + } + }; + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(8, callnum); + EXPECT_EQ(otherdqblk.dqb_bhardlimit, mydqblk.dqb_bhardlimit); + EXPECT_EQ(otherdqblk.dqb_bsoftlimit, mydqblk.dqb_bsoftlimit); + EXPECT_EQ(otherdqblk.dqb_curspace, mydqblk.dqb_curspace); + EXPECT_EQ(otherdqblk.dqb_ihardlimit, mydqblk.dqb_ihardlimit); + EXPECT_EQ(otherdqblk.dqb_isoftlimit, mydqblk.dqb_isoftlimit); + EXPECT_EQ(otherdqblk.dqb_btime, mydqblk.dqb_btime); + EXPECT_EQ(otherdqblk.dqb_itime, mydqblk.dqb_itime); + EXPECT_EQ(otherdqinfo.dqi_bgrace, mydqinfo.dqi_bgrace); + EXPECT_EQ(otherdqinfo.dqi_igrace, mydqinfo.dqi_igrace); +} + +TEST_F(sys_call_test32, ppoll_timeout) +{ + int callnum = 0; + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + return (evt->get_type() == PPME_SYSCALL_PPOLL_E || + evt->get_type() == PPME_SYSCALL_PPOLL_X) && + tinfo != nullptr && tinfo->m_comm == "test_helper_32"; + }; + + std::string my_pipe[2]; + + run_callback_t test = [&](concurrent_object_handle inspector) + { + subprocess handle(LIBSINSP_TEST_PATH "/test_helper_32", + {"ppoll_timeout",}); + std::stringstream ss; + ss << handle.out(); + my_pipe[0] = ss.str(); + ss.clear(); + ss.str(" "); + ss << handle.out(); + my_pipe[1] = ss.str(); + ss.clear(); + ss.str(" "); + handle.wait(); + }; + + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_PPOLL_E) + { + // + // stdin and stdout can be a file or a fifo depending + // on how the tests are invoked + // + std::string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p1 " + + my_pipe[1] + ":p4"; + + EXPECT_EQ(expected_fds, fds); + EXPECT_EQ("1000000", e->get_param_value_str("timeout", false)); + EXPECT_EQ("SIGHUP SIGCHLD", e->get_param_value_str("sigmask", false)); + callnum++; + } + else if (type == PPME_SYSCALL_PPOLL_X) + { + int64_t res = std::stol(e->get_param_value_str("res")); + + EXPECT_GT(res, 0); + EXPECT_LE(res, 2); + + string fds = e->get_param_value_str("fds"); + std::string expected_fds = my_pipe[0] + ":p0 " + + my_pipe[1] + ":p4"; + + switch (res) + { + case 1: + EXPECT_EQ(expected_fds, fds); + break; + case 2: + // + // On EC2 called from jenkins stdin returns POLLHUP + // + EXPECT_EQ(expected_fds, fds); + break; + default: + FAIL(); + } + + callnum++; + } + }; + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(2, callnum); +} + +TEST_F(sys_call_test32, fs_preadv) +{ + int callnum = 0; + int fd = 0; + int fd1 = 0; + bool pwritev64_succeeded; + bool pwritev64_succeeded2; + proc_started_filter test_started_filter; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + auto tinfo = evt->get_thread_info(false); + if (tinfo && tinfo->m_comm == "test_helper_32") + { + auto type = evt->get_type(); + return (type == PPME_SYSCALL_PREADV_E + || type == PPME_SYSCALL_PREADV_X + || type == PPME_SYSCALL_PWRITEV_E + || type == PPME_SYSCALL_PWRITEV_X); + } + return false; + }; + + // + // TEST CODE + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + subprocess test_proc(LIBSINSP_TEST_PATH "/test_helper_32", {"preadv_pwritev"}); + fd = std::stoi(test_proc.out()); + int bool_n = std::stoi(test_proc.out()); + pwritev64_succeeded = (bool_n == 1); + bool_n = std::stoi(test_proc.out()); + pwritev64_succeeded2 = (bool_n == 1); + fd1 = std::stoi(test_proc.out()); + test_proc.wait(); + }; + + int pwrite1_res = 0; + int pwrite2_res = 0; + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + uint16_t type = e->get_type(); + + if (type == PPME_SYSCALL_PWRITEV_E) + { + if (callnum == 0) + { + EXPECT_EQ(fd, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(987654321, std::stoll(e->get_param_value_str("pos"))); + EXPECT_EQ(15, std::stoll(e->get_param_value_str("size"))); + callnum++; + } + else + { + EXPECT_EQ(fd, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(10, std::stoll(e->get_param_value_str("pos"))); + EXPECT_EQ(15, std::stoll(e->get_param_value_str("size"))); + callnum++; + } + } + else if (type == PPME_SYSCALL_PWRITEV_X) + { + if (callnum == 1) + { + pwrite1_res = std::stoi(e->get_param_value_str("res", false)); + EXPECT_EQ("aaaaabbbbbccccc", e->get_param_value_str("data")); + callnum++; + } + else + { + pwrite2_res = std::stoi(e->get_param_value_str("res", false)); + EXPECT_EQ("aaaaabbbbbccccc", e->get_param_value_str("data")); + callnum++; + } + } + else if (type == PPME_SYSCALL_PREADV_E) + { + if (callnum == 4) + { + EXPECT_EQ(fd1, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(987654321, std::stoll(e->get_param_value_str("pos"))); + callnum++; + } + else + { + EXPECT_EQ(fd1, std::stoll(e->get_param_value_str("fd", false))); + EXPECT_EQ(10, std::stoll(e->get_param_value_str("pos"))); + callnum++; + } + } + else if (type == PPME_SYSCALL_PREADV_X) + { + if (callnum == 3) + { + EXPECT_EQ(15, std::stoi(e->get_param_value_str("res", false))); + EXPECT_EQ("aaaaabbbbb", e->get_param_value_str("data")); + EXPECT_EQ(30, std::stoll(e->get_param_value_str("size"))); + callnum++; + } + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(6, callnum); + if (pwritev64_succeeded) + { + EXPECT_EQ(15, pwrite1_res); + } + else + { + EXPECT_GT(0, pwrite1_res); + } + if (pwritev64_succeeded2) + { + EXPECT_EQ(15, pwrite2_res); + } + else + { + EXPECT_EQ(-22, pwrite2_res); + } +} +#endif + +extern "C" +{ + int32_t scap_proc_read_thread(struct scap_linux_platform* linux_platform, + char* procdirname, + uint64_t tid, + struct scap_threadinfo* tinfo, + char* error, + bool scan_sockets); +} + +TEST_F(sys_call_test, thread_lookup_static) +{ + char err_buf[SCAP_LASTERR_SIZE]; + scap_threadinfo scap_tinfo; + char proc[] = LIBSINSP_TEST_RESOURCES_PATH "/_proc"; + struct stat s = {}; + if (stat(proc, &s) != 0) + { + fprintf(stderr, "%s not found, skipping test\n", proc); + FAIL(); + } + + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_type() != PPME_PROCEXIT_1_E && evt->get_tid() > 0; }; + run_callback_t test = [&](concurrent_object_handle inspector) {return;}; + captured_event_callback_t callback = [&](const callback_param& param) {return;}; + scap_linux_platform *platform; + + before_close_t before_close = [&](sinsp* inspector) + { + platform = (scap_linux_platform*)inspector->get_scap_platform(); + }; + + ASSERT_NO_FATAL_FAILURE( + { event_capture::run(test, callback, filter, event_capture::do_nothing, before_close); }); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 1, &scap_tinfo, err_buf, false)); + + EXPECT_EQ(1, scap_tinfo.tid); + EXPECT_EQ(1, scap_tinfo.pid); + EXPECT_EQ(1, scap_tinfo.vtid); + EXPECT_EQ(0, scap_tinfo.ptid); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 62725, &scap_tinfo, err_buf, false)); + EXPECT_EQ(62725, scap_tinfo.tid); + EXPECT_EQ(62725, scap_tinfo.pid); + EXPECT_EQ(62725, scap_tinfo.vtid); + EXPECT_EQ(1, scap_tinfo.ptid); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 62727, &scap_tinfo, err_buf, false)); + EXPECT_EQ(62727, scap_tinfo.tid); + EXPECT_EQ(62725, scap_tinfo.pid); + EXPECT_EQ(62727, scap_tinfo.vtid); + EXPECT_EQ(1, scap_tinfo.ptid); + +} + +TEST_F(sys_call_test, thread_lookup_live) +{ + char err_buf[SCAP_LASTERR_SIZE]; + scap_threadinfo scap_tinfo; + char proc[] = "/proc"; + + std::unordered_set seen_tids; + + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_type() != PPME_PROCEXIT_1_E && evt->get_tid() > 0; }; + run_callback_t test = [&](concurrent_object_handle inspector) + { + // a very short sleep to gather some events, + // we'll take much longer than this to process them all + usleep(1000); + }; + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* e = param.m_evt; + auto tid = e->get_tid(); + if (!seen_tids.insert(tid).second) + { + return; + } + fprintf(stderr, "looking up tid %ld in /proc\n", tid); + // In some cases scap_proc_read_thread can return SCAP_SUCCESS without + // filling in scap_tinfo + if (scap_proc_read_thread((scap_linux_platform*)param.m_inspector->get_scap_platform(), + proc, tid, &scap_tinfo, err_buf, false) == SCAP_SUCCESS) + { + auto tinfo = e->get_thread_info(false); + if (!tinfo) + { + return; + } + EXPECT_NE(0, scap_tinfo.tid); + EXPECT_NE(0, scap_tinfo.pid); + EXPECT_NE(0, scap_tinfo.vtid); + EXPECT_EQ(tinfo->m_tid, scap_tinfo.tid); + EXPECT_EQ(tinfo->m_pid, scap_tinfo.pid); + EXPECT_EQ(tinfo->m_vtid, scap_tinfo.vtid); + // Not testing scap_tinfo.ptid because it can change in between event and lookup + } + }; + + scap_linux_platform *platform; + + before_close_t before_close = [&](sinsp* inspector) + { + // close scap to maintain the num_consumers at exit == 0 assertion + //close_capture(scap, platform); + platform = (scap_linux_platform*)inspector->get_scap_platform(); + }; + ASSERT_NO_FATAL_FAILURE( + { event_capture::run(test, callback, filter, event_capture::do_nothing, before_close); }); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, getpid(), + &scap_tinfo, err_buf, false)); + EXPECT_EQ(getpid(), scap_tinfo.tid); + EXPECT_EQ(getpid(), scap_tinfo.pid); + EXPECT_EQ(getpid(), scap_tinfo.vtid); + EXPECT_EQ(getppid(), scap_tinfo.ptid); + + ASSERT_EQ(SCAP_SUCCESS, + scap_proc_read_thread(platform, proc, 1, + &scap_tinfo, err_buf, false)); + EXPECT_EQ(1, scap_tinfo.tid); + EXPECT_EQ(1, scap_tinfo.pid); + EXPECT_EQ(1, scap_tinfo.vtid); + EXPECT_EQ(0, scap_tinfo.ptid); + +} diff --git a/test/libsinsp_e2e/test_helper.cpp b/test/libsinsp_e2e/test_helper.cpp index ec41c445abd..c9ae1ca7e55 100644 --- a/test/libsinsp_e2e/test_helper.cpp +++ b/test/libsinsp_e2e/test_helper.cpp @@ -43,6 +43,7 @@ limitations under the License. #include #include +#include #include #include @@ -68,6 +69,10 @@ void mmap_test(const vector& args) { int errno2; void* p; + + printf("STARTED\n"); + fflush(stdout); + munmap((void*)0x50, 300); p = mmap(0, 0, @@ -78,7 +83,10 @@ void mmap_test(const vector& args) errno2 = errno; p = mmap(NULL, 1003520, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); munmap(p, 1003520); - cout << errno2 << " " << p << endl; + printf("%d\n", errno2); + fflush(stdout); + printf("%u\n", p); + fflush(stdout); } bool str_to_bool(const string& s) @@ -182,6 +190,8 @@ void preadv_pwritev(const vector& args) // bool pwritev64_succeeded = bytes_sent > 0; + cout << fd << endl; + cout << (pwritev64_succeeded ? 1 : 0) << endl; bytes_sent = pwritev(fd, wv, wv_count, 10); @@ -192,6 +202,8 @@ void preadv_pwritev(const vector& args) auto fd1 = open(FILENAME, O_CREAT | O_RDONLY, S_IRWXU); + cout << fd1 << endl; + wv[0].iov_len = sizeof(msg1); wv[1].iov_len = sizeof(msg2); wv[2].iov_len = sizeof(msg3); @@ -223,11 +235,12 @@ void quotactl_ok(const vector& args) { struct dqblk mydqblk; struct dqinfo mydqinfo; + std::string caddr = args[0] + "/aquota.user"; quotactl(QCMD(Q_QUOTAON, USRQUOTA), - "/dev/loop0", + args[1].c_str(), 2, - (caddr_t) "/tmp/testquotamnt/aquota.user"); // 2 => QFMT_VFS_V0 - quotactl(QCMD(Q_GETQUOTA, USRQUOTA), "/dev/loop0", 0, (caddr_t)&mydqblk); // 0 => root user + (caddr_t)caddr.c_str()); // 2 => QFMT_VFS_V0 + quotactl(QCMD(Q_GETQUOTA, USRQUOTA), args[1].c_str(), 0, (caddr_t)&mydqblk); // 0 => root user fwrite(&mydqblk.dqb_bhardlimit, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_bsoftlimit, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_curspace, 1, sizeof(uint64_t), stdout); @@ -235,10 +248,33 @@ void quotactl_ok(const vector& args) fwrite(&mydqblk.dqb_isoftlimit, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_btime, 1, sizeof(uint64_t), stdout); fwrite(&mydqblk.dqb_itime, 1, sizeof(uint64_t), stdout); - quotactl(QCMD(Q_GETINFO, USRQUOTA), "/dev/loop0", 0, (caddr_t)&mydqinfo); + quotactl(QCMD(Q_GETINFO, USRQUOTA), args[1].c_str(), 0, (caddr_t)&mydqinfo); fwrite(&mydqinfo.dqi_bgrace, 1, sizeof(uint64_t), stdout); fwrite(&mydqinfo.dqi_igrace, 1, sizeof(uint64_t), stdout); - quotactl(QCMD(Q_QUOTAOFF, USRQUOTA), "/dev/loop0", 0, NULL); + quotactl(QCMD(Q_QUOTAOFF, USRQUOTA), args[1].c_str(), 0, NULL); +} + +void poll_timeout(const vector& args) +{ + int my_pipe[2]; + auto ret = pipe(my_pipe); + if (ret != 0) + { + return; + } + + struct pollfd ufds[2]; + ufds[0].fd = my_pipe[0]; + ufds[0].events = POLLIN; + ufds[1].fd = my_pipe[1]; + ufds[1].events = POLLOUT; + + poll(ufds, 2, 20); + + printf("%d\n", my_pipe[0]); + fflush(stdout); + printf("%d\n", my_pipe[1]); + fflush(stdout); } void ppoll_timeout(const vector& args) @@ -265,6 +301,11 @@ void ppoll_timeout(const vector& args) sigaddset(&sigs, SIGHUP); sigaddset(&sigs, SIGCHLD); ppoll(ufds, 2, &timeout, &sigs); + + printf("%d\n", my_pipe[0]); + fflush(stdout); + printf("%d\n", my_pipe[1]); + fflush(stdout); } void pgid_test(const vector& args) @@ -710,6 +751,7 @@ const unordered_map&)>> func_map = { {"preadv_pwritev", preadv_pwritev}, {"quotactl_ko", quotactl_ko}, {"quotactl_ok", quotactl_ok}, + {"poll_timeout", poll_timeout}, {"ppoll_timeout", ppoll_timeout}, {"pgid_test", pgid_test}, {"custom_container", custom_container}, diff --git a/test/libsinsp_e2e/unix_client_server.cpp b/test/libsinsp_e2e/unix_client_server.cpp new file mode 100644 index 00000000000..889d3c68ff7 --- /dev/null +++ b/test/libsinsp_e2e/unix_client_server.cpp @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco 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 "event_capture.h" +#include "subprocess.h" +#include "sys_call_test.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define NAME "/tmp/python_unix_sockets_example" +#define PAYLOAD "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" + +#define PAYLOAD "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" +#define BUFFER_LENGTH (sizeof(PAYLOAD) - 1) +#define FALSE 0 + +inline void parse_tuple(const std::string& tuple, + std::string& srcstr, + std::string& dststr, bool shift = false) +{ + std::string token; + std::stringstream ss(tuple); + std::vector tst; + + int base = shift? 1 : 0; + + while (std::getline(ss, token, '>')) { + tst.push_back(token); + } + + int size = shift? 3 : 2; + EXPECT_EQ(size, (int)tst.size()); + + srcstr = tst[base].substr(0, tst[base].size() - 1); + dststr = tst[base+1]; +} + +inline bool ends_with(const std::string& value, const std::string& ending) +{ + if (ending.size() > value.size()) return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); +} + +TEST_F(sys_call_test, unix_client_server) +{ + int32_t callnum = 0; + bool first_connect_or_accept_seen = true; + std::string sport; + std::string src_addr; + std::string dest_addr; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { + sinsp_threadinfo* ti = evt->get_thread_info(false); + if (ti) + { + if (ti->get_comm() == "python" && ti->m_args.size() >= 1) + { + return ends_with(ti->m_args[0],"unix_client_server.py") || + ends_with(ti->m_args[0],"unix_client_server.py"); + } + } + + return false; + }; + + // + // INITIALIZATION + // + run_callback_t test = [](concurrent_object_handle inspector) + { + subprocess server("python2", {LIBSINSP_TEST_RESOURCES_PATH "/unix_client_server.py", "server"}); + + server.wait_for_start(); + + subprocess client("python2", {LIBSINSP_TEST_RESOURCES_PATH "/unix_client_server.py", "client"}); + server.wait(); + client.wait(); + }; + + // + // OUTPUT VALIDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* evt = param.m_evt; + + //std::cout << evt->get_name() << std::endl; + + if (evt->get_type() == PPME_SOCKET_CONNECT_X) + { + std::string tuple = evt->get_param_value_str("tuple"); + std::string addrs = tuple.substr(0, tuple.find(" ")); + std::string file = tuple.substr(tuple.find(" ") + 1); + + EXPECT_EQ(NAME, file); + + std::string srcstr; + std::string dststr; + parse_tuple(tuple, srcstr, dststr); + + EXPECT_NE("0000000000000000", srcstr); + EXPECT_NE("0000000000000000", dststr); + + // + // connect() and accept() can return + // in a different order + // + if (first_connect_or_accept_seen) + { + first_connect_or_accept_seen = false; + src_addr = srcstr.substr(1); + dest_addr = dststr; + } + else + { + EXPECT_EQ(src_addr, srcstr.substr(1)); + EXPECT_EQ(dest_addr, dststr); + } + + callnum++; + } + else if ((evt->get_type() == PPME_SOCKET_ACCEPT_5_X) || + (evt->get_type() == PPME_SOCKET_ACCEPT4_6_X)) + { + std::string tuple = evt->get_param_value_str("tuple"); + std::string addrs = tuple.substr(0, tuple.find(" ")); + std::string file = tuple.substr(tuple.find(" ") + 1); + + EXPECT_EQ(NAME, file); + + std::string srcstr; + std::string dststr; + parse_tuple(tuple, srcstr, dststr); + + EXPECT_NE("0000000000000000", srcstr); + EXPECT_NE("0000000000000000", dststr); + + // + // connect() and accept() can return + // in a different order + // + if (first_connect_or_accept_seen) + { + first_connect_or_accept_seen = false; + src_addr = srcstr.substr(1); + dest_addr = dststr; + } + else + { + EXPECT_EQ(src_addr, srcstr.substr(1)); + EXPECT_EQ(dest_addr, dststr); + } + + std::string fdtuple = evt->get_param_value_str("tuple"); + std::string fdaddrs = fdtuple.substr(0, fdtuple.find(" ")); + std::string fdfile = fdtuple.substr(fdtuple.find(" ") + 1); + + EXPECT_EQ(NAME, fdfile); + + std::string fdsrcstr; + std::string fddststr; + parse_tuple(tuple, fdsrcstr, fddststr); + + EXPECT_NE("0000000000000000", fdsrcstr); + EXPECT_NE("0000000000000000", fddststr); + + callnum++; + } + + if (callnum < 1) + { + return; + } + + // + // 32bit (and s390x) uses send() and recv(), while 64bit + // uses sendto() and recvfrom() and sets the address to NULL + // + if (evt->get_type() == PPME_SOCKET_SEND_E || evt->get_type() == PPME_SOCKET_RECV_E || + evt->get_type() == PPME_SOCKET_SENDTO_E || evt->get_type() == PPME_SOCKET_RECVFROM_E) + { + if (((evt->get_type() == PPME_SOCKET_RECVFROM_X) || + (evt->get_type() == PPME_SOCKET_RECVFROM_X)) && + (evt->get_param_value_str("tuple") != "")) + { + EXPECT_EQ("NULL", evt->get_param_value_str("tuple")); + } + + std::string fdtuple = evt->get_param_value_str("fd"); + std::string fdaddrs = fdtuple.substr(0, fdtuple.find(" ")); + std::string fdfile = fdtuple.substr(fdtuple.find(" ") + 1); + + EXPECT_EQ(NAME, fdfile); + + std::string fdsrcstr; + std::string fddststr; + parse_tuple(fdtuple, fdsrcstr, fddststr, true); + + EXPECT_NE("0", fdsrcstr); + EXPECT_NE("0", fddststr); + + callnum++; + } + else if ((evt->get_type() == PPME_SOCKET_RECV_X) || + (evt->get_type() == PPME_SOCKET_RECVFROM_X)) + { + if (evt->get_type() == PPME_SOCKET_RECVFROM_X) + { + if (callnum == 5) + { + std::string tuple = evt->get_param_value_str("tuple"); + std::string addrs = tuple.substr(0, tuple.find(" ")); + std::string file = tuple.substr(tuple.find(" ") + 1); + + EXPECT_EQ(NAME, file); + + std::string srcstr; + std::string dststr; + parse_tuple(tuple, srcstr, dststr); + + EXPECT_NE("0000000000000000", srcstr); + EXPECT_NE("0000000000000000", dststr); + } + } + EXPECT_EQ(PAYLOAD, evt->get_param_value_str("data")); + + callnum++; + } + }; + + // + // OUTPUT VALDATION + // + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_FALSE(first_connect_or_accept_seen); + EXPECT_EQ(8, callnum); +} + diff --git a/test/libsinsp_e2e/unix_udp_client_server.cpp b/test/libsinsp_e2e/unix_udp_client_server.cpp new file mode 100644 index 00000000000..39f16373b03 --- /dev/null +++ b/test/libsinsp_e2e/unix_udp_client_server.cpp @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: Apache-2.0 +/* +Copyright (C) 2024 The Falco 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 "sys_call_test.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#define NAME "/tmp/python_unix_udp_sockets_example" +#define PAYLOAD "0123456789QWERTYUIOPASDFGHJKLZXCVBNM" + +class unix_udp_server +{ +public: + unix_udp_server(bool use_recvfrom) { + m_use_recvfrom = use_recvfrom; + m_server_ready = false; + } + + void run() + { + int sock; + struct sockaddr_un name; + struct sockaddr_un caddr; + socklen_t address_length = sizeof(struct sockaddr_un); + char buf[1024]; + + m_tid = syscall(SYS_gettid); + + /* Create socket from which to read. */ + sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (sock < 0) + { + perror("opening datagram socket"); + exit(1); + } + + /* Create name. */ + name.sun_family = AF_UNIX; + strcpy(name.sun_path, NAME); + + if (::bind(sock, (struct sockaddr*)&name, SUN_LEN(&name))) + { + perror("binding name to datagram socket"); + exit(1); + } + + { + std::unique_lock lock(m_mutex); + m_server_ready = true; + m_condition_server_ready.notify_one(); + } + + /* Read from the socket. */ + if (m_use_recvfrom) + { + recvfrom(sock, buf, 1024, 0, (struct sockaddr*)&caddr, &address_length); + + recvfrom(sock, buf, 1024, 0, (struct sockaddr*)&caddr, &address_length); + } + else + { + ASSERT_TRUE(read(sock, buf, 1024) >= 0); + + ASSERT_TRUE(read(sock, buf, 1024) >= 0); + } + + close(sock); + + unlink(NAME); + } + + void wait_for_server_ready() + { + std::unique_lock lock(m_mutex); + m_condition_server_ready.wait(lock, [this]() { + return m_server_ready; + }); + m_server_ready = false; + } + + int64_t get_tid() { return m_tid; } + +private: + std::mutex m_mutex; + std::condition_variable m_condition_server_ready; + bool m_server_ready; + int64_t m_tid; + bool m_use_recvfrom; +}; + +class unix_udp_client +{ +public: + void run() + { + int sock; + struct sockaddr_un name; + + /* Create socket on which to send. */ + sock = socket(AF_UNIX, SOCK_DGRAM, 0); + if (sock < 0) + { + perror("opening datagram socket"); + exit(1); + } + + /* Construct name of socket to send to. */ + name.sun_family = AF_UNIX; + strcpy(name.sun_path, NAME); + /* Send message. */ + + sendto(sock, + PAYLOAD, + sizeof(PAYLOAD) - 1, + 0, + (struct sockaddr*)&name, + sizeof(struct sockaddr_un)); + + sendto(sock, + PAYLOAD, + sizeof(PAYLOAD) - 1, + 0, + (struct sockaddr*)&name, + sizeof(struct sockaddr_un)); + + close(sock); + } + +private: +}; + +inline void parse_tuple(const std::string& tuple, + std::string& srcstr, + std::string& dststr, int check_size) +{ + std::string token; + std::stringstream ss(tuple); + std::vector tst; + + while (std::getline(ss, token, '>')) { + tst.push_back(token); + } + + EXPECT_EQ(check_size, (int)tst.size()); + + srcstr = tst[0].substr(0, tst[0].size() - 1); + dststr = tst[1]; +} + +TEST_F(sys_call_test, unix_udp_client_server_read) +{ + std::thread server_thread; + std::shared_ptr server = std::make_shared(false); + int32_t callnum = 0; + std::string sport; + + // + // FILTER + // + event_filter_t filter = [&](sinsp_evt* evt) + { return evt->get_tid() == server->get_tid() || m_tid_filter(evt); }; + + // + // INITIALIZATION + // + run_callback_t test = [&](concurrent_object_handle inspector) + { + server_thread = std::thread(&unix_udp_server::run, server); + server->wait_for_server_ready(); + + unix_udp_client client; + client.run(); + server_thread.join(); + }; + + // + // OUTPUT VALDATION + // + captured_event_callback_t callback = [&](const callback_param& param) + { + sinsp_evt* evt = param.m_evt; + if (evt->get_type() == PPME_SOCKET_BIND_X) + { + std::string ttuple = evt->get_param_value_str("addr"); + + EXPECT_EQ(NAME, ttuple); + + callnum++; + } + + if (evt->get_type() == PPME_SOCKET_SENDTO_E) + { + std::string ttuple = evt->get_param_value_str("tuple"); + std::string taddrs = ttuple.substr(0, ttuple.find(" ")); + std::string tfile = ttuple.substr(ttuple.find(" ") + 1); + + EXPECT_EQ(NAME, tfile); + + std::string tsrcstr; + std::string tdststr; + parse_tuple(ttuple, tsrcstr, tdststr, 2); + + if (evt->get_tid() == server->get_tid()) + { + EXPECT_NE("0", tsrcstr); + EXPECT_EQ("0", tdststr); + } + else + { + EXPECT_EQ("0", tsrcstr); + EXPECT_NE("0", tdststr); + } + + std::string fdtuple = evt->get_param_value_str("fd"); + + if (fdtuple.length() > 3) + { + std::string fdaddrs = fdtuple.substr(0, fdtuple.find(" ")); + std::string fdfile = fdtuple.substr(fdtuple.find(" ") + 1); + + EXPECT_EQ(NAME, fdfile); + + std::string fdsrcstr; + std::string fddststr; + parse_tuple(fdtuple, fdsrcstr, fddststr, 3); + + EXPECT_EQ('u', fdaddrs[1]); + + if (evt->get_tid() == server->get_tid()) + { + EXPECT_NE("0", tsrcstr); + EXPECT_EQ("0", tdststr); + } + else + { + EXPECT_EQ("0", tsrcstr); + EXPECT_NE("0", tdststr); + } + } + else + { + if (fdtuple.length() == 1) + { + EXPECT_EQ("u", fdtuple); + } + } + + if (evt->get_type() == PPME_SOCKET_SENDTO_X) + { + EXPECT_EQ(PAYLOAD, evt->get_param_value_str("data")); + } + + callnum++; + } + else if (evt->get_type() == PPME_SYSCALL_READ_E) + { + if (callnum < 1) + { + return; + } + + std::string fdtuple = evt->get_param_value_str("fd"); + + EXPECT_GT(fdtuple.length(), (unsigned int)1); + + if (fdtuple.length() > 1) + { + std::string ttype = fdtuple.substr(0, fdtuple.find(">")); + std::string tfile = fdtuple.substr(fdtuple.find(">") + 1); + + EXPECT_EQ("get_type() == PPME_SYSCALL_READ_X) + { + EXPECT_EQ(PAYLOAD, evt->get_param_value_str("data")); + + callnum++; + } + }; + + ASSERT_NO_FATAL_FAILURE({ event_capture::run(test, callback, filter); }); + EXPECT_EQ(7, callnum); +}