Skip to content

Commit

Permalink
Add ebpf_get_next_pinned_object_path (microsoft#4164)
Browse files Browse the repository at this point in the history
* Add ebpf_get_next_pinned_object_path

It's currently not possible to find pinned maps and links. Add a
function ebpf_get_next_pinned_object_path which allows doing just
that.

The paths are returned in lexicographical order, which allows user
space to performa a prefix match on the paths. Behind the scenes
this is implemented using a linear scan of the hash table, which
has quadratic running time. The same is already the case for finding
the next object ID, so going with the more desirable semantics
makes sense.

The existing ebpf_get_next_pinned_program_path() is deprecated because
it is less flexible than the new function.

Updates microsoft#4161

* Document return value of ebpf_get_next_pinned_object_path

Co-authored-by: Dave Thaler <dthaler1968@gmail.com>

* Fix possible underflow in get_next_pinned_program_path / object_path

* Fix typo in ebpf_pinning_table_get_next_path documentation

Co-authored-by: Dave Thaler <dthaler1968@gmail.com>

---------

Co-authored-by: Dave Thaler <dthaler1968@gmail.com>
  • Loading branch information
lmb and dthaler authored Feb 11, 2025
1 parent 97eb992 commit 7df6716
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 46 deletions.
1 change: 1 addition & 0 deletions ebpfapi/Source.def
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ EXPORTS
ebpf_free_sections = ebpf_free_programs
ebpf_free_string
ebpf_get_attach_type_name
ebpf_get_next_pinned_object_path
ebpf_get_next_pinned_program_path
ebpf_get_program_info_from_verifier
ebpf_get_program_type_by_name
Expand Down
24 changes: 22 additions & 2 deletions include/ebpf_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -517,10 +517,30 @@ extern "C"
*
* @retval EBPF_SUCCESS The operation was successful.
* @retval EBPF_NO_MORE_KEYS No more entries found.
* @deprecated Use ebpf_get_next_pinned_object_path() instead.
*/
__declspec(deprecated("Use ebpf_get_next_pinned_object_path() instead.")) _Must_inspect_result_ ebpf_result_t
ebpf_get_next_pinned_program_path(
_In_z_ const char* start_path, _Out_writes_z_(EBPF_MAX_PIN_PATH_LENGTH) char* next_path) EBPF_NO_EXCEPT;

/**
* @brief Retrieve the next pinned path of an eBPF object.
*
* @param[in] start_path Path to look for an entry greater than or NULL.
* @param[out] next_path Returns the next path in lexicographical order, if one exists.
* @param[in] next_path_len Length of the next path buffer.
* @param[in, out] type On input, the type of object to retrieve or EBPF_OBJECT_UNKNOWN.
* On output, the type of the object.
*
* @retval EBPF_SUCCESS The operation was successful.
* @retval other An error occurred.
*/
_Must_inspect_result_ ebpf_result_t
ebpf_get_next_pinned_program_path(
_In_z_ const char* start_path, _Out_writes_z_(EBPF_MAX_PIN_PATH_LENGTH) char* next_path) EBPF_NO_EXCEPT;
ebpf_get_next_pinned_object_path(
_In_z_ const char* start_path,
_Out_writes_z_(next_path_len) char* next_path,
size_t next_path_len,
_Inout_ ebpf_object_type_t* type) EBPF_NO_EXCEPT;

typedef struct _ebpf_program_info ebpf_program_info_t;

Expand Down
39 changes: 25 additions & 14 deletions libs/api/ebpf_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3993,6 +3993,18 @@ CATCH_NO_MEMORY_EBPF_RESULT
_Must_inspect_result_ ebpf_result_t
ebpf_get_next_pinned_program_path(
_In_z_ const char* start_path, _Out_writes_z_(EBPF_MAX_PIN_PATH_LENGTH) char* next_path) NO_EXCEPT_TRY
{
ebpf_object_type_t type = EBPF_OBJECT_PROGRAM;
return ebpf_get_next_pinned_object_path(start_path, next_path, EBPF_MAX_PIN_PATH_LENGTH, &type);
}
CATCH_NO_MEMORY_EBPF_RESULT

_Must_inspect_result_ ebpf_result_t
ebpf_get_next_pinned_object_path(
_In_z_ const char* start_path,
_Out_writes_z_(next_path_len) char* next_path,
size_t next_path_len,
_Inout_ ebpf_object_type_t* type) NO_EXCEPT_TRY
{
EBPF_LOG_ENTRY();
ebpf_assert(start_path);
Expand All @@ -4001,32 +4013,31 @@ ebpf_get_next_pinned_program_path(
size_t start_path_length = strlen(start_path);

ebpf_protocol_buffer_t request_buffer(
EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_program_path_request_t, start_path) + start_path_length);
EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_object_path_request_t, start_path) + start_path_length);
ebpf_protocol_buffer_t reply_buffer(
EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_program_path_reply_t, next_path) + EBPF_MAX_PIN_PATH_LENGTH - 1);
ebpf_operation_get_next_pinned_program_path_request_t* request =
reinterpret_cast<ebpf_operation_get_next_pinned_program_path_request_t*>(request_buffer.data());
ebpf_operation_get_next_pinned_program_path_reply_t* reply =
reinterpret_cast<ebpf_operation_get_next_pinned_program_path_reply_t*>(reply_buffer.data());
EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_object_path_reply_t, next_path) + (next_path_len - 1));
ebpf_operation_get_next_pinned_object_path_request_t* request =
reinterpret_cast<ebpf_operation_get_next_pinned_object_path_request_t*>(request_buffer.data());
ebpf_operation_get_next_pinned_object_path_reply_t* reply =
reinterpret_cast<ebpf_operation_get_next_pinned_object_path_reply_t*>(reply_buffer.data());

request->header.id = ebpf_operation_id_t::EBPF_OPERATION_GET_NEXT_PINNED_PROGRAM_PATH;
request->header.id = ebpf_operation_id_t::EBPF_OPERATION_GET_NEXT_PINNED_OBJECT_PATH;
request->header.length = static_cast<uint16_t>(request_buffer.size());
reply->header.length = static_cast<uint16_t>(reply_buffer.size());

request->type = *type;
memcpy(request->start_path, start_path, start_path_length);

uint32_t error = invoke_ioctl(request_buffer, reply_buffer);
ebpf_result_t result = win32_error_code_to_ebpf_result(error);
if (result != EBPF_SUCCESS) {
EBPF_RETURN_RESULT(result);
}
ebpf_assert(reply->header.id == ebpf_operation_id_t::EBPF_OPERATION_GET_NEXT_PINNED_PROGRAM_PATH);
size_t next_path_length =
reply->header.length - EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_program_path_reply_t, next_path);
memcpy(next_path, reply->next_path, next_path_length);

next_path[next_path_length] = '\0';

ebpf_assert(reply->header.id == ebpf_operation_id_t::EBPF_OPERATION_GET_NEXT_PINNED_OBJECT_PATH);
size_t reply_next_path_len =
reply->header.length - EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_object_path_reply_t, next_path);
strncpy_s(next_path, next_path_len, (char*)reply->next_path, reply_next_path_len);
*type = reply->type;
EBPF_RETURN_RESULT(EBPF_SUCCESS);
}
CATCH_NO_MEMORY_EBPF_RESULT
Expand Down
3 changes: 2 additions & 1 deletion libs/ebpfnetsh/pins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ handle_ebpf_show_pins(
// Read all pin paths. Currently we get them in a non-deterministic
// order, so we use a std::set to sort them in code point order.
char pinpath[EBPF_MAX_PIN_PATH_LENGTH] = "";
ebpf_object_type_t object_type = EBPF_OBJECT_PROGRAM;
std::set<std::string> paths;
while (ebpf_get_next_pinned_program_path(pinpath, pinpath) == EBPF_SUCCESS) {
while (ebpf_get_next_pinned_object_path(pinpath, pinpath, sizeof(pinpath), &object_type) == EBPF_SUCCESS) {
paths.insert(pinpath);
}

Expand Down
3 changes: 2 additions & 1 deletion libs/ebpfnetsh/programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,9 @@ _unpin_program_by_id(ebpf_id_t id)
// Read all pin paths. Currently we get them in a non-deterministic
// order, so we use a std::set to sort them in code point order.
char pinpath[EBPF_MAX_PIN_PATH_LENGTH] = "";
ebpf_object_type_t object_type = EBPF_OBJECT_PROGRAM;
std::set<std::string> paths;
while (ebpf_get_next_pinned_program_path(pinpath, pinpath) == EBPF_SUCCESS) {
while (ebpf_get_next_pinned_object_path(pinpath, pinpath, sizeof(pinpath), &object_type) == EBPF_SUCCESS) {
paths.insert(pinpath);
}

Expand Down
54 changes: 51 additions & 3 deletions libs/execution_context/ebpf_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1979,11 +1979,17 @@ _ebpf_core_protocol_get_next_pinned_program_path(
}
start_path.length = path_length;
start_path.value = (uint8_t*)request->start_path;
next_path.length = reply_length - EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_program_path_reply_t, next_path);

result = ebpf_safe_size_t_subtract(
reply_length, EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_program_path_reply_t, next_path), &path_length);
if (result != EBPF_SUCCESS) {
EBPF_RETURN_RESULT(result);
}
next_path.length = path_length;
next_path.value = (uint8_t*)reply->next_path;

result =
ebpf_pinning_table_get_next_path(_ebpf_core_map_pinning_table, EBPF_OBJECT_PROGRAM, &start_path, &next_path);
ebpf_object_type_t object_type = EBPF_OBJECT_PROGRAM;
result = ebpf_pinning_table_get_next_path(_ebpf_core_map_pinning_table, &object_type, &start_path, &next_path);

if (result == EBPF_SUCCESS) {
reply->header.length =
Expand All @@ -1992,6 +1998,46 @@ _ebpf_core_protocol_get_next_pinned_program_path(
EBPF_RETURN_RESULT(result);
}

static ebpf_result_t
_ebpf_core_protocol_get_next_pinned_object_path(
_In_ const ebpf_operation_get_next_pinned_object_path_request_t* request,
_Out_ ebpf_operation_get_next_pinned_object_path_reply_t* reply,
uint16_t reply_length)
{
EBPF_LOG_ENTRY();
cxplat_utf8_string_t start_path;
cxplat_utf8_string_t next_path;

size_t path_length;
ebpf_result_t result = ebpf_safe_size_t_subtract(
request->header.length,
EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_object_path_request_t, start_path),
&path_length);
if (result != EBPF_SUCCESS) {
EBPF_RETURN_RESULT(result);
}
start_path.length = path_length;
start_path.value = (uint8_t*)request->start_path;

result = ebpf_safe_size_t_subtract(
reply_length, EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_object_path_reply_t, next_path), &path_length);
if (result != EBPF_SUCCESS) {
EBPF_RETURN_RESULT(result);
}
next_path.length = path_length;
next_path.value = (uint8_t*)reply->next_path;

ebpf_object_type_t object_type = request->type;
result = ebpf_pinning_table_get_next_path(_ebpf_core_map_pinning_table, &object_type, &start_path, &next_path);

if (result == EBPF_SUCCESS) {
reply->header.length =
(uint16_t)next_path.length + EBPF_OFFSET_OF(ebpf_operation_get_next_pinned_object_path_reply_t, next_path);
reply->type = object_type;
}
EBPF_RETURN_RESULT(result);
}

static ebpf_result_t
_ebpf_core_protocol_bind_map(_In_ const ebpf_operation_bind_map_request_t* request)
{
Expand Down Expand Up @@ -2778,6 +2824,8 @@ static ebpf_protocol_handler_t _ebpf_protocol_handlers[] = {
DECLARE_PROTOCOL_HANDLER_VARIABLE_REQUEST_VARIABLE_REPLY(
map_get_next_key_value_batch, previous_key, data, PROTOCOL_ALL_MODES),
DECLARE_PROTOCOL_HANDLER_FIXED_REQUEST_NO_REPLY(program_set_flags, PROTOCOL_ALL_MODES),
DECLARE_PROTOCOL_HANDLER_VARIABLE_REQUEST_VARIABLE_REPLY(
get_next_pinned_object_path, start_path, next_path, PROTOCOL_ALL_MODES),
};

_Must_inspect_result_ ebpf_result_t
Expand Down
17 changes: 16 additions & 1 deletion libs/execution_context/ebpf_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ typedef enum _ebpf_operation_id
EBPF_OPERATION_GET_NEXT_MAP_ID,
EBPF_OPERATION_GET_NEXT_PROGRAM_ID,
EBPF_OPERATION_GET_OBJECT_INFO,
EBPF_OPERATION_GET_NEXT_PINNED_PROGRAM_PATH,
EBPF_OPERATION_GET_NEXT_PINNED_PROGRAM_PATH, /* deprecated */
EBPF_OPERATION_BIND_MAP,
EBPF_OPERATION_RING_BUFFER_MAP_QUERY_BUFFER,
EBPF_OPERATION_RING_BUFFER_MAP_ASYNC_QUERY,
Expand All @@ -47,6 +47,7 @@ typedef enum _ebpf_operation_id
EBPF_OPERATION_MAP_DELETE_ELEMENT_BATCH,
EBPF_OPERATION_MAP_GET_NEXT_KEY_VALUE_BATCH,
EBPF_OPERATION_PROGRAM_SET_FLAGS,
EBPF_OPERATION_GET_NEXT_PINNED_OBJECT_PATH,
} ebpf_operation_id_t;

typedef enum _ebpf_code_type
Expand Down Expand Up @@ -350,6 +351,20 @@ typedef struct _ebpf_operation_get_next_pinned_program_path_reply
uint8_t next_path[1];
} ebpf_operation_get_next_pinned_program_path_reply_t;

typedef struct _ebpf_operation_get_next_pinned_object_path_request
{
struct _ebpf_operation_header header;
ebpf_object_type_t type;
uint8_t start_path[1];
} ebpf_operation_get_next_pinned_object_path_request_t;

typedef struct _ebpf_operation_get_next_pinned_object_path_reply
{
struct _ebpf_operation_header header;
ebpf_object_type_t type;
uint8_t next_path[1];
} ebpf_operation_get_next_pinned_object_path_reply_t;

typedef struct _ebpf_operation_get_object_info_request
{
struct _ebpf_operation_header header;
Expand Down
83 changes: 61 additions & 22 deletions libs/runtime/ebpf_pinning_table.c
Original file line number Diff line number Diff line change
Expand Up @@ -336,15 +336,52 @@ ebpf_pinning_table_enumerate_entries(
EBPF_RETURN_RESULT(result);
}

static bool
_ebpf_pinning_table_match_object_type(_In_ void* filter_context, _In_ const uint8_t* key, _In_ const uint8_t* value)
{
ebpf_object_type_t object_type = *(ebpf_object_type_t*)filter_context;
ebpf_pinning_entry_t* entry = *(ebpf_pinning_entry_t**)value;
UNREFERENCED_PARAMETER(key);

if (object_type == EBPF_OBJECT_UNKNOWN) {
return true;
}

return ebpf_object_get_type(entry->object) == object_type;
}

static int
_ebpf_pinning_table_compare(_In_ const uint8_t* key1, _In_ const uint8_t* key2)
{
const cxplat_utf8_string_t* str1 = *(const cxplat_utf8_string_t**)key1;
const cxplat_utf8_string_t* str2 = *(const cxplat_utf8_string_t**)key2;
size_t min_length = (str1->length < str2->length) ? str1->length : str2->length;

int result = memcmp(str1->value, str2->value, min_length);
if (result != 0) {
return result;
}

if (str1->length < str2->length) {
return -1;
}

if (str1->length > str2->length) {
return 1;
}

return 0;
}

_Must_inspect_result_ ebpf_result_t
ebpf_pinning_table_get_next_path(
_Inout_ ebpf_pinning_table_t* pinning_table,
ebpf_object_type_t object_type,
_Inout_ ebpf_object_type_t* object_type,
_In_ const cxplat_utf8_string_t* start_path,
_Inout_ cxplat_utf8_string_t* next_path)
{
EBPF_LOG_ENTRY();
if ((pinning_table == NULL) || (start_path == NULL) || (next_path == NULL)) {
if ((pinning_table == NULL) || (start_path == NULL) || (next_path == NULL) || (object_type == NULL)) {
EBPF_RETURN_RESULT(EBPF_INVALID_ARGUMENT);
}

Expand All @@ -355,29 +392,31 @@ ebpf_pinning_table_get_next_path(
ebpf_result_t result;
ebpf_pinning_entry_t** next_pinning_entry = NULL;

for (;;) {
// Get the next entry in the table.
cxplat_utf8_string_t* next_object_path;
result = ebpf_hash_table_next_key_and_value(
pinning_table->hash_table, previous_key, (uint8_t*)&next_object_path, (uint8_t**)&next_pinning_entry);
if (result != EBPF_SUCCESS) {
break;
}
// Get the next entry in the table.
cxplat_utf8_string_t* next_object_path;
result = ebpf_hash_table_next_key_and_value_sorted(
pinning_table->hash_table,
previous_key,
_ebpf_pinning_table_compare,
object_type,
_ebpf_pinning_table_match_object_type,
(uint8_t*)&next_object_path,
(uint8_t**)&next_pinning_entry);
if (result != EBPF_SUCCESS) {
goto Exit;
}

// See if the entry matches the object type the caller is interested in.
if (object_type == ebpf_object_get_type((*next_pinning_entry)->object)) {
if (next_path->length < (*next_pinning_entry)->path.length) {
result = EBPF_INSUFFICIENT_BUFFER;
} else {
next_path->length = (*next_pinning_entry)->path.length;
memcpy(next_path->value, (*next_pinning_entry)->path.value, next_path->length);
result = EBPF_SUCCESS;
}
break;
}
previous_key = (uint8_t*)&next_object_path;
if (next_path->length < (*next_pinning_entry)->path.length) {
result = EBPF_INSUFFICIENT_BUFFER;
goto Exit;
}

next_path->length = (*next_pinning_entry)->path.length;
memcpy(next_path->value, (*next_pinning_entry)->path.value, next_path->length);
*object_type = ebpf_object_get_type((*next_pinning_entry)->object);
result = EBPF_SUCCESS;

Exit:
ebpf_lock_unlock(&pinning_table->lock, state);
EBPF_RETURN_RESULT(result);
}
Expand Down
4 changes: 2 additions & 2 deletions libs/runtime/ebpf_pinning_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ extern "C"
* @brief Gets the next path in the pinning table after a given path.
*
* @param[in, out] pinning_table Pinning table to enumerate.
* @param[in] object_type Object type.
* @param[in, out] object_type Object type, may be EBPF_OBJECT_UNKNOWN.
* @param[in] start_path Path to look for an entry greater than.
* @param[in, out] next_path Returns the next path, if one exists.
* @retval EBPF_SUCCESS The operation was successful.
Expand All @@ -114,7 +114,7 @@ extern "C"
_Must_inspect_result_ ebpf_result_t
ebpf_pinning_table_get_next_path(
_Inout_ ebpf_pinning_table_t* pinning_table,
ebpf_object_type_t object_type,
_Inout_ ebpf_object_type_t* object_type,
_In_ const cxplat_utf8_string_t* start_path,
_Inout_ cxplat_utf8_string_t* next_path);

Expand Down
Loading

0 comments on commit 7df6716

Please sign in to comment.