diff --git a/include/ebpf_nethooks.h b/include/ebpf_nethooks.h index 40b5a22de4..e7196b8858 100644 --- a/include/ebpf_nethooks.h +++ b/include/ebpf_nethooks.h @@ -279,10 +279,13 @@ typedef struct _process_md uint64_t parent_process_id; ///< Parent process ID. uint64_t creating_process_id; ///< Creating process ID. uint64_t creating_thread_id; ///< Creating thread ID. - uint64_t creation_status; ///< Process creation status. + int32_t creation_status; ///< The NTSTATUS value to return for the process-creation operation. process_operation_t operation; ///< Operation to do. } process_md_t; +typedef int +process_hook_t(process_md_t* context); + #ifdef _MSC_VER #pragma warning(pop) #endif diff --git a/netebpfext/net_ebpf_ext_process.c b/netebpfext/net_ebpf_ext_process.c index 6216dca7de..4a689244c9 100644 --- a/netebpfext/net_ebpf_ext_process.c +++ b/netebpfext/net_ebpf_ext_process.c @@ -328,16 +328,29 @@ _ebpf_process_create_process_notify_routine_ex( net_ebpf_extension_hook_get_next_attached_client(_ebpf_process_hook_provider_context, NULL); while (client_context != NULL) { uint32_t return_value = 0; - net_ebpf_extension_hook_client_enter_rundown(client_context); - result = - net_ebpf_extension_hook_invoke_program(client_context, &process_notify_context.process_md, &return_value); - if (result != EBPF_SUCCESS) { + if (net_ebpf_extension_hook_client_enter_rundown(client_context)) { + result = net_ebpf_extension_hook_invoke_program( + client_context, &process_notify_context.process_md, &return_value); + if (result != EBPF_SUCCESS) { + NET_EBPF_EXT_LOG_MESSAGE( + NET_EBPF_EXT_TRACELOG_LEVEL_ERROR, + NET_EBPF_EXT_TRACELOG_KEYWORD_PROCESS, + "net_ebpf_extension_hook_invoke_program failed"); + } else { + create_info->CreationStatus = process_notify_context.process_md.creation_status; + } + net_ebpf_extension_hook_client_leave_rundown(client_context); + } else { NET_EBPF_EXT_LOG_MESSAGE( NET_EBPF_EXT_TRACELOG_LEVEL_ERROR, NET_EBPF_EXT_TRACELOG_KEYWORD_PROCESS, - "net_ebpf_extension_hook_invoke_program failed"); + "net_ebpf_extension_hook_client_enter_rundown failed"); } - net_ebpf_extension_hook_client_leave_rundown(client_context); + // If the client returns a non-zero value, stop calling the other clients. + if (create_info->CreationStatus != STATUS_SUCCESS) { + break; + } + client_context = net_ebpf_extension_hook_get_next_attached_client(_ebpf_process_hook_provider_context, client_context); } diff --git a/tests/end_to_end/helpers.h b/tests/end_to_end/helpers.h index f8ef633754..e914bc3b6a 100644 --- a/tests/end_to_end/helpers.h +++ b/tests/end_to_end/helpers.h @@ -695,6 +695,12 @@ static ebpf_extension_data_t _test_ebpf_sample_extension_program_info_provider_d sizeof(_test_ebpf_sample_extension_program_data), &_test_ebpf_sample_extension_program_data}; +// Process. +static ebpf_program_data_t _ebpf_process_program_data = {&_ebpf_process_program_info, NULL}; + +static ebpf_extension_data_t _ebpf_process_program_info_provider_data = { + TEST_NET_EBPF_EXTENSION_NPI_PROVIDER_VERSION, sizeof(_ebpf_process_program_data), &_ebpf_process_program_data}; + typedef class _program_info_provider { public: @@ -722,6 +728,8 @@ typedef class _program_info_provider provider_data = &_ebpf_sock_ops_program_info_provider_data; } else if (program_type == EBPF_PROGRAM_TYPE_SAMPLE) { provider_data = &_test_ebpf_sample_extension_program_info_provider_data; + } else if (program_type == EBPF_PROGRAM_TYPE_PROCESS) { + provider_data = &_ebpf_process_program_info_provider_data; } else { // Unsupported program type. return EBPF_INVALID_ARGUMENT; diff --git a/tests/netebpfext_unit/netebpfext_unit.cpp b/tests/netebpfext_unit/netebpfext_unit.cpp index 141b0a52dc..805ebb508d 100644 --- a/tests/netebpfext_unit/netebpfext_unit.cpp +++ b/tests/netebpfext_unit/netebpfext_unit.cpp @@ -48,8 +48,10 @@ TEST_CASE("query program info", "[netebpfext]") EBPF_PROGRAM_TYPE_CGROUP_SOCK_ADDR, EBPF_PROGRAM_TYPE_SOCK_OPS, EBPF_PROGRAM_TYPE_BIND, - EBPF_PROGRAM_TYPE_XDP_TEST}; - std::vector expected_program_names = {"sock_addr", "sockops", "bind", "xdp_test"}; + EBPF_PROGRAM_TYPE_XDP_TEST, + EBPF_PROGRAM_TYPE_PROCESS, + }; + std::vector expected_program_names = {"sock_addr", "sockops", "bind", "xdp_test", "process"}; auto guid_less = [](const GUID& lhs, const GUID& rhs) { return memcmp(&lhs, &rhs, sizeof(lhs)) < 0; }; @@ -900,6 +902,7 @@ netebpfext_unit_invoke_process_program( test_process_client_context_t* client_context = (test_process_client_context_t*)client_process_context; client_context->process_context = *process_context; + process_context->creation_status = STATUS_ACCESS_DENIED; *result = 0; return EBPF_SUCCESS; @@ -927,7 +930,7 @@ TEST_CASE("process_invoke", "[netebpfext]") create_info.ParentProcessId = (HANDLE)4; create_info.CreatingThreadId.UniqueProcess = (HANDLE)5; create_info.CreatingThreadId.UniqueThread = (HANDLE)6; - create_info.CreationStatus = 7; + create_info.CreationStatus = STATUS_SUCCESS; RtlInitUnicodeString(&process_name_unicode, process_name.c_str()); RtlInitUnicodeString(&command_line_unicode, command_line.c_str()); @@ -935,7 +938,7 @@ TEST_CASE("process_invoke", "[netebpfext]") struct { uint64_t some_value; - } fake_eprocess; + } fake_eprocess = {}; usersime_invoke_process_creation_notify_routine( reinterpret_cast(&fake_eprocess), (HANDLE)1, &create_info); @@ -950,7 +953,7 @@ TEST_CASE("process_invoke", "[netebpfext]") REQUIRE((HANDLE)client_context.process_context.parent_process_id == create_info.ParentProcessId); REQUIRE((HANDLE)client_context.process_context.creating_process_id == create_info.CreatingThreadId.UniqueProcess); REQUIRE((HANDLE)client_context.process_context.creating_thread_id == create_info.CreatingThreadId.UniqueThread); - REQUIRE(client_context.process_context.creation_status == create_info.CreationStatus); + REQUIRE(create_info.CreationStatus == STATUS_ACCESS_DENIED); REQUIRE(client_context.process_context.operation == PROCESS_OPERATION_CREATE); } diff --git a/tests/sample/process_monitor.c b/tests/sample/process_monitor.c new file mode 100644 index 0000000000..e65bb42487 --- /dev/null +++ b/tests/sample/process_monitor.c @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation +// SPDX-License-Identifier: MIT + +// clang -O2 -Werror -c bindmonitor.c -o bindmonitor_jit.o +// +// For bpf code: clang -target bpf -O2 -Werror -c bindmonitor.c -o bindmonitor.o +// this passes the checker + +// Whenever this sample program changes, bpf2c_tests will fail unless the +// expected files in tests\bpf2c_tests\expected are updated. The following +// script can be used to regenerate the expected files: +// generate_expected_bpf2c_output.ps1 +// +// Usage: +// .\scripts\generate_expected_bpf2c_output.ps1 +// Example: +// .\scripts\generate_expected_bpf2c_output.ps1 .\x64\Debug\ + +#include "bpf_helpers.h" +#include "ebpf_nethooks.h" + +typedef struct +{ + uint64_t parent_process_id; + uint8_t command_line[256]; +} proces_entry_t; + +struct +{ + __uint(type, BPF_MAP_TYPE_HASH); + __type(key, uint64_t); + __type(value, proces_entry_t); + __uint(max_entries, 1024); +} process_map SEC(".maps"); + +// For debug builds, limit the number of iterations in the loop to 16 to prevent the verifier from +// running for too long. For release builds, limit the number of iterations to 256. +#if defined(NDEBUG) +#define BOUNDED_MEMCPY_LIMIT 256 +#else +#define BOUNDED_MEMCPY_LIMIT 16 +#endif + +__attribute__((always_inline)) +// Copy the first 'source_size' bytes from 'source' to 'destination' and bound the copy to 'destination_size' bytes. +void +bounded_memcpy(uint8_t* destination, const uint8_t* source, uint32_t destination_size, uint32_t source_size) +{ +// Prevail verifier doesn't correctly compute the number of iterations in the loop. +// Unroll the loop to avoid the verifier error. +// Issue: # +#pragma unroll + for (uint32_t index = 0; index < BOUNDED_MEMCPY_LIMIT; index++) { + if (index < destination_size && index < source_size) { + destination[index] = source[index]; + } + } +} + +// The following line is optional, but is used to verify +// that the ProcesMonitor prototype is correct or the compiler +// would complain when the function is actually defined below. +process_hook_t ProcesMonitor; + +SEC("process") +int +ProcessMonitor(process_md_t* ctx) +{ + if (ctx->operation == PROCESS_OPERATION_CREATE) { + proces_entry_t entry; + __builtin_memset(&entry, 0, sizeof(entry)); + entry.parent_process_id = ctx->parent_process_id; + uint64_t process_id = ctx->process_id; + + bounded_memcpy( + entry.command_line, + ctx->command_start, + sizeof(entry.command_line), + (uint32_t)(ctx->command_end - ctx->command_start)); + + bpf_map_update_elem(&process_map, &process_id, &entry, BPF_ANY); + } else if (ctx->operation == PROCESS_OPERATION_DELETE) { + uint64_t process_id = ctx->process_id; + bpf_map_delete_elem(&process_map, &process_id); + } + return 0; +} diff --git a/tests/unit/libbpf_test.cpp b/tests/unit/libbpf_test.cpp index 7f656160e3..1f949daa4a 100644 --- a/tests/unit/libbpf_test.cpp +++ b/tests/unit/libbpf_test.cpp @@ -9,6 +9,7 @@ #include "capture_helper.hpp" #include "catch_wrapper.hpp" #include "common_tests.h" +#include "ebpf_nethooks.h" #include "ebpf_platform.h" #include "ebpf_tracelog.h" #include "ebpf_vm_isa.hpp" @@ -3363,4 +3364,72 @@ TEST_CASE("hash_of_map", "[libbpf]") #endif _hash_of_map_initial_value_test(EBPF_EXECUTION_NATIVE); -} \ No newline at end of file +} + +typedef struct +{ + uint64_t parent_process_id; + uint8_t command_line[256]; +} proces_entry_t; + +static void +_process_hook_test(ebpf_execution_type_t execution_type) +{ + _test_helper_end_to_end test_helper; + const char dll_name[] = "process_monitor_um.dll"; + const char obj_name[] = "process_monitor.o"; + test_helper.initialize(); + single_instance_hook_t hook(EBPF_PROGRAM_TYPE_PROCESS, EBPF_ATTACH_TYPE_PROCESS); + REQUIRE(hook.initialize() == EBPF_SUCCESS); + program_info_provider_t sample_program_info; + REQUIRE(sample_program_info.initialize(EBPF_PROGRAM_TYPE_PROCESS) == EBPF_SUCCESS); + + const char* file_name = (execution_type == EBPF_EXECUTION_NATIVE ? dll_name : obj_name); + struct bpf_object* process_object = bpf_object__open(file_name); + REQUIRE(process_object != nullptr); + + // Load the program(s). + REQUIRE(bpf_object__load(process_object) == 0); + + struct bpf_program* caller = bpf_object__find_program_by_name(process_object, "ProcessMonitor"); + REQUIRE(caller != nullptr); + + struct bpf_map* process_map = bpf_object__find_map_by_name(process_object, "process_map"); + REQUIRE(process_map != nullptr); + + bpf_link_ptr link(bpf_program__attach(caller)); + REQUIRE(link != nullptr); + + // Now run the ebpf program. + process_md_t ctx{0}; + ctx.process_id = 1234; + ctx.operation = PROCESS_OPERATION_CREATE; + ctx.parent_process_id = 5678; + ctx.command_start = (uint8_t*)"test_process"; + ctx.command_end = ctx.command_start + strlen((char*)ctx.command_start); + + uint32_t result; + REQUIRE(hook.fire(&ctx, &result) == EBPF_SUCCESS); + + // Check if the entry was added to the map. + uint64_t key = 1234; + proces_entry_t value; + REQUIRE(bpf_map_lookup_elem(bpf_map__fd(process_map), &key, &value) == 0); + + // Verify the entry. + REQUIRE(value.parent_process_id == 5678); + REQUIRE(strcmp((char*)value.command_line, "test_process") == 0); + + // Test process termination. + ctx.operation = PROCESS_OPERATION_DELETE; + REQUIRE(hook.fire(&ctx, &result) == EBPF_SUCCESS); + + // Check if the entry was removed from the map. + REQUIRE(bpf_map_lookup_elem(bpf_map__fd(process_map), &key, &value) != 0); + + result = bpf_link__destroy(link.release()); + REQUIRE(result == 0); + bpf_object__close(process_object); +} + +DECLARE_ALL_TEST_CASES("process_hook", "[libbf]", _process_hook_test); \ No newline at end of file