Skip to content

Commit

Permalink
Additional testing
Browse files Browse the repository at this point in the history
Signed-off-by: Alan Jowett (from Dev Box) <[email protected]>
  • Loading branch information
Alan-Jowett committed Mar 1, 2024
1 parent dfe54b7 commit b273179
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 13 deletions.
5 changes: 4 additions & 1 deletion include/ebpf_nethooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 19 additions & 6 deletions netebpfext/net_ebpf_ext_process.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
8 changes: 8 additions & 0 deletions tests/end_to_end/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 8 additions & 5 deletions tests/netebpfext_unit/netebpfext_unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> expected_program_names = {"sock_addr", "sockops", "bind", "xdp_test"};
EBPF_PROGRAM_TYPE_XDP_TEST,
EBPF_PROGRAM_TYPE_PROCESS,
};
std::vector<std::string> 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; };

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -927,15 +930,15 @@ 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());

struct
{
uint64_t some_value;
} fake_eprocess;
} fake_eprocess = {};

usersime_invoke_process_creation_notify_routine(
reinterpret_cast<PEPROCESS>(&fake_eprocess), (HANDLE)1, &create_info);
Expand All @@ -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);
}

Expand Down
87 changes: 87 additions & 0 deletions tests/sample/process_monitor.c
Original file line number Diff line number Diff line change
@@ -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 <build_output_path>
// 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: #<to be determined>
#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;
}
71 changes: 70 additions & 1 deletion tests/unit/libbpf_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -3363,4 +3364,72 @@ TEST_CASE("hash_of_map", "[libbpf]")

#endif
_hash_of_map_initial_value_test(EBPF_EXECUTION_NATIVE);
}
}

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);

0 comments on commit b273179

Please sign in to comment.