ebpf_program_t computes hash of program info on first attach (#1948)

* ebpf_program_t computes hash of program info on first attach

Signed-off-by: Alan Jowett <alanjo@microsoft.com>

* Provider reload test

Signed-off-by: Alan Jowett <alanjo@microsoft.com>

Signed-off-by: Alan Jowett <alanjo@microsoft.com>
Co-authored-by: Anurag Saxena <43585259+saxena-anurag@users.noreply.github.com>
This commit is contained in:
Alan Jowett 2023-01-31 09:14:12 -07:00 коммит произвёл GitHub
Родитель 39c1490fb5
Коммит 44602dcb1d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 175 добавлений и 25 удалений

Просмотреть файл

@ -128,7 +128,7 @@ _ebpf_program_detach_links(_Inout_ ebpf_program_t* program)
}
static ebpf_result_t
_ebpf_program_verify_program_info_hash(_In_ const ebpf_program_t* program);
_ebpf_program_initialize_or_verify_program_info_hash(_Inout_ ebpf_program_t* program);
static ebpf_result_t
_ebpf_program_program_info_provider_changed(
@ -207,17 +207,15 @@ _ebpf_program_program_info_provider_changed(
goto Exit;
}
if (program->parameters.program_info_hash != NULL) {
return_value = _ebpf_program_verify_program_info_hash(program);
if (return_value != EBPF_SUCCESS) {
EBPF_LOG_MESSAGE_GUID(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_PROGRAM,
"The program info used to verify the program doesn't match the program info provided by the "
"extension",
program->parameters.program_type);
goto Exit;
}
return_value = _ebpf_program_initialize_or_verify_program_info_hash(program);
if (return_value != EBPF_SUCCESS) {
EBPF_LOG_MESSAGE_GUID(
EBPF_TRACELOG_LEVEL_ERROR,
EBPF_TRACELOG_KEYWORD_PROGRAM,
"The program info used to verify the program doesn't match the program info provided by the "
"extension",
program->parameters.program_type);
goto Exit;
}
return_value = _ebpf_program_update_helpers(program);
@ -491,6 +489,7 @@ ebpf_program_initialize(_Inout_ ebpf_program_t* program, _In_ const ebpf_program
return_value = EBPF_INVALID_ARGUMENT;
goto Done;
}
if (program_parameters->program_name.length >= BPF_OBJ_NAME_LEN) {
EBPF_LOG_MESSAGE_UINT64(
EBPF_TRACELOG_LEVEL_ERROR,
@ -1479,7 +1478,19 @@ _ebpf_helper_id_to_index_compare(const void* lhs, const void* rhs)
/**
* @brief Compute the hash of the program info and compare it with the hash stored in the program. If the hash does not
* match then the program was verified against the wrong program info.
* match then the program was verified against the wrong program info. If the hash is not present then store the hash
* in the program so it can be compared when the program information provider reattaches.
*
* Notes on why this works:
* 1) The user application creates an ebpf_program_t object and sets the program type.
* 2) During initialization, the program binds to the program information provider.
* 3) During the attach callback, the program information is hashed and stored.
* 4) The verifier then queries the program information from the ebpf_program_t object and uses it to verify the program
* safety.
* 5) If the program information provider is reattached, the program information is hashed and compared with the
* hash stored in the program and the program is rejected if the hash does not match. This ensures that the program
* information the verifier uses to verify the program safety is the same as the program information the program uses to
* execute.
*
* @param[in] program Program to validate.
* @param[in] program_info Program info to validate against.
@ -1487,7 +1498,7 @@ _ebpf_helper_id_to_index_compare(const void* lhs, const void* rhs)
* @return EBPF_INVALID_ARGUMENT the program info hash does not match.
*/
static ebpf_result_t
_ebpf_program_verify_program_info_hash(_In_ const ebpf_program_t* program)
_ebpf_program_initialize_or_verify_program_info_hash(_Inout_ ebpf_program_t* program)
{
EBPF_LOG_ENTRY();
ebpf_result_t result;
@ -1615,14 +1626,26 @@ _ebpf_program_verify_program_info_hash(_In_ const ebpf_program_t* program)
goto Exit;
}
if (output_hash_length != program->parameters.program_info_hash_length) {
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}
if (program->parameters.program_info_hash_length == 0) {
// This is the first time the program info hash is being computed.
uint8_t* new_hash = ebpf_allocate(output_hash_length);
if (new_hash == NULL) {
result = EBPF_NO_MEMORY;
goto Exit;
}
program->parameters.program_info_hash = new_hash;
program->parameters.program_info_hash_length = output_hash_length;
memcpy((uint8_t*)program->parameters.program_info_hash, hash, output_hash_length);
} else {
if (output_hash_length != program->parameters.program_info_hash_length) {
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}
if (memcmp(hash, program->parameters.program_info_hash, output_hash_length) != 0) {
result = EBPF_INVALID_ARGUMENT;
goto Exit;
if (memcmp(hash, program->parameters.program_info_hash, output_hash_length) != 0) {
result = EBPF_INVALID_ARGUMENT;
goto Exit;
}
}
result = EBPF_SUCCESS;

Просмотреть файл

@ -78,8 +78,7 @@ _ebpf_extension_client_attach_provider(
NTSTATUS status;
ebpf_extension_client_t* local_client_context = (ebpf_extension_client_t*)client_context;
ebpf_extension_client_binding_context_t* local_client_binding_context = NULL;
ExInitializeRundownProtection(&local_client_context->nmr_rundown_ref);
bool rundown_ref_initialized = false;
// Check that the provider module Id matches the client's expected provider module Id.
ebpf_assert(provider_registration_instance->ModuleId != NULL);
@ -110,6 +109,10 @@ _ebpf_extension_client_attach_provider(
goto Done;
}
// Only initialize the rundown reference after we know we are going to try to attach.
ExInitializeRundownProtection(&local_client_context->nmr_rundown_ref);
rundown_ref_initialized = true;
local_client_binding_context =
(ebpf_extension_client_binding_context_t*)ebpf_allocate(sizeof(ebpf_extension_client_binding_context_t));
@ -148,6 +151,10 @@ _ebpf_extension_client_attach_provider(
}
Done:
if (!NT_SUCCESS(status) && rundown_ref_initialized) {
// Mark the nmr_rundown_ref as rundown if the attach fails.
ExWaitForRundownProtectionRelease(&local_client_context->nmr_rundown_ref);
}
EBPF_RETURN_NTSTATUS(status);
}

Просмотреть файл

@ -2725,3 +2725,121 @@ TEST_CASE("test_ebpf_object_set_execution_type", "[end_to_end]")
bpf_object__close(jit_object);
}
static void
extension_reload_test(ebpf_execution_type_t execution_type)
{
_test_helper_end_to_end test_helper;
// Create a 0-byte UDP packet.
auto packet0 = prepare_udp_packet(0, ETHERNET_TYPE_IPV4);
// Test that we drop the packet and increment the map.
xdp_md_t ctx0{packet0.data(), packet0.data() + packet0.size(), 0, TEST_IFINDEX};
// Try loading without the extension loaded.
bpf_object* droppacket_object;
int program_fd = -1;
const char* error_message = nullptr;
// Should fail.
REQUIRE(
ebpf_program_load(
execution_type == EBPF_EXECUTION_NATIVE ? "droppacket_um.dll" : "droppacket.o",
BPF_PROG_TYPE_UNSPEC,
execution_type,
&droppacket_object,
&program_fd,
&error_message) != 0);
ebpf_free((void*)error_message);
// Load the program with the extension loaded.
{
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP, EBPF_ATTACH_TYPE_XDP);
program_info_provider_t xdp_program_info(EBPF_PROGRAM_TYPE_XDP);
REQUIRE(
ebpf_program_load(
execution_type == EBPF_EXECUTION_NATIVE ? "droppacket_um.dll" : "droppacket.o",
BPF_PROG_TYPE_UNSPEC,
execution_type,
&droppacket_object,
&program_fd,
&error_message) == 0);
uint32_t if_index = TEST_IFINDEX;
bpf_link* link = nullptr;
// Attach only to the single interface being tested.
REQUIRE(hook.attach_link(program_fd, &if_index, sizeof(if_index), &link) == EBPF_SUCCESS);
bpf_link__disconnect(link);
bpf_link__destroy(link);
// Program should run.
int hook_result;
REQUIRE(hook.fire(&ctx0, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_PASS);
// Unload the extension (xdp_program_info and hook will be destroyed).
}
// Reload the extension provider with unchanged data.
{
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP, EBPF_ATTACH_TYPE_XDP);
program_info_provider_t xdp_program_info(EBPF_PROGRAM_TYPE_XDP);
// Program should re-attach to the hook.
// Program should run.
int hook_result;
REQUIRE(hook.fire(&ctx0, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result == XDP_PASS);
}
// Reload the extension provider with missing helper function.
{
ebpf_helper_function_addresses_t changed_helper_function_address_table =
_test_ebpf_xdp_helper_function_address_table;
ebpf_program_data_t changed_program_data = _ebpf_xdp_program_data;
changed_program_data.program_type_specific_helper_function_addresses = &changed_helper_function_address_table;
changed_helper_function_address_table.helper_function_count = 0;
ebpf_extension_data_t changed_provider_data = {
TEST_NET_EBPF_EXTENSION_NPI_PROVIDER_VERSION, sizeof(changed_program_data), &changed_program_data};
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP, EBPF_ATTACH_TYPE_XDP);
program_info_provider_t xdp_program_info(EBPF_PROGRAM_TYPE_XDP, &changed_provider_data);
// Program should re-attach to the hook.
// Program should not run.
int hook_result;
REQUIRE(hook.fire(&ctx0, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result != XDP_PASS);
}
// Reload the extension provider with changed helper function data.
{
ebpf_program_info_t changed_program_info = _ebpf_xdp_program_info;
ebpf_helper_function_prototype_t helper_function_prototypes[] = {
_xdp_ebpf_extension_helper_function_prototype[0]};
helper_function_prototypes[0].return_type = EBPF_RETURN_TYPE_PTR_TO_MAP_VALUE_OR_NULL;
changed_program_info.program_type_specific_helper_prototype = helper_function_prototypes;
ebpf_program_data_t changed_program_data = _ebpf_xdp_program_data;
changed_program_data.program_info = &changed_program_info;
ebpf_extension_data_t changed_provider_data = {
TEST_NET_EBPF_EXTENSION_NPI_PROVIDER_VERSION, sizeof(changed_program_data), &changed_program_data};
single_instance_hook_t hook(EBPF_PROGRAM_TYPE_XDP, EBPF_ATTACH_TYPE_XDP);
program_info_provider_t xdp_program_info(EBPF_PROGRAM_TYPE_XDP, &changed_provider_data);
// Program should re-attach to the hook.
// Program should not run.
int hook_result;
REQUIRE(hook.fire(&ctx0, &hook_result) == EBPF_SUCCESS);
REQUIRE(hook_result != XDP_PASS);
}
}
DECLARE_ALL_TEST_CASES("extension_reload_test", "[end_to_end]", extension_reload_test);

Просмотреть файл

@ -434,10 +434,12 @@ static ebpf_extension_data_t _test_ebpf_sample_extension_program_info_provider_d
typedef class _program_info_provider
{
public:
_program_info_provider(ebpf_program_type_t program_type)
_program_info_provider(ebpf_program_type_t program_type, ebpf_extension_data_t* custom_provider_data = nullptr)
: program_type(program_type), provider(nullptr), provider_data(nullptr)
{
if (program_type == EBPF_PROGRAM_TYPE_XDP) {
if (custom_provider_data != nullptr) {
provider_data = custom_provider_data;
} else if (program_type == EBPF_PROGRAM_TYPE_XDP) {
provider_data = &_ebpf_xdp_program_info_provider_data;
} else if (program_type == EBPF_PROGRAM_TYPE_BIND) {
provider_data = &_ebpf_bind_program_info_provider_data;