Repository: microsoft/ebpf-for-windows (C)

Issue:
PROG_ARRAY_MAP ownership semantics differ from Linux
> There is an additional wrinkle here that I just discovered: `BPF_MAP_TYPE_PROG_ARRAY` does not hold on to a refcount of the program either. On Linux this is kind of crucial to be able to build tail call chains.
> 
> ```
> prog1 -> prog_array -> prog2
>                                      -> prog3
> ```
> 
> If userspace doesnt hold on to prog2 and prog3 somehow the entries are removed from the map and the tail call fails.
> 
> The semantics on Linux are that holding on to a prog_array will keep the refcount on programs alive. Losing the last user refcount on the prog_array triggers dropping the refcount on prog2 and prog3. To put it another way, you need to hold on to prog1 _and_ prog_array in user space (or via pinning) for things to work.
> 
> Not sure we want to replicate this 100% but user space shouldn't have to hold onto prog2 and prog3 imo. 

 _Originally posted by @lmb in [#4135](https://github.com/microsoft/ebpf-for-windows/issues/4135#issuecomment-2698045775)_




Discussion: Here is what Linux does: https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c9da161c6517ba12154059d3b965c2cbaf16f90f

At a high level:

- Reference counts on the prog_array map are split in two: references from user space (fds and pins) and references from kernel space
- Once the user space reference count on a prog array map drops to 0, all entries in the map are cleared and refcounts on the programs are dropped
- Once the kernel space refcount drops the prog_array is deallocated as usual



Fix (patch):
diff --git a/libs/execution_context/ebpf_core.c b/libs/execution_context/ebpf_core.c
index fd862a3274..cf29e0478c 100644
--- a/libs/execution_context/ebpf_core.c
+++ b/libs/execution_context/ebpf_core.c
@@ -1329,7 +1329,6 @@ _ebpf_core_protocol_get_pinned_object(
     _Inout_ struct _ebpf_operation_get_pinned_object_reply* reply)
 {
     EBPF_LOG_ENTRY();
-    ebpf_core_object_t* object = NULL;
     size_t path_length;
     ebpf_result_t retval = ebpf_safe_size_t_subtract(
         request->header.length, EBPF_OFFSET_OF(ebpf_operation_get_pinned_object_request_t, path), &path_length);
@@ -1346,7 +1345,6 @@ _ebpf_core_protocol_get_pinned_object(
     retval = ebpf_core_get_pinned_object(&path, &reply->handle);
 
 Done:
-    EBPF_OBJECT_RELEASE_REFERENCE((ebpf_core_object_t*)object);
     EBPF_RETURN_RESULT(retval);
 }
 
@@ -3187,7 +3185,7 @@ ebpf_core_close_context(_In_opt_ void* context)
     ebpf_epoch_enter(&epoch_state);
 
     ebpf_core_object_t* object = (ebpf_core_object_t*)context;
-    EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT((&object->base));
+    EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT_USER((&object->base));
 
     ebpf_epoch_exit(&epoch_state);
 }
diff --git a/libs/execution_context/ebpf_link.c b/libs/execution_context/ebpf_link.c
index 32dc857acf..3253de6794 100644
--- a/libs/execution_context/ebpf_link.c
+++ b/libs/execution_context/ebpf_link.c
@@ -308,7 +308,7 @@ ebpf_link_create(
     // Note: This must be the last thing done in this function as it inserts the object into the global list.
     // After this point, the object can be accessed by other threads.
     ebpf_result_t result = EBPF_OBJECT_INITIALIZE(
-        &local_link->object, EBPF_OBJECT_LINK, _ebpf_link_free, _ebpf_link_notify_reference_count_zeroed, NULL);
+        &local_link->object, EBPF_OBJECT_LINK, _ebpf_link_free, _ebpf_link_notify_reference_count_zeroed, NULL, NULL);
     if (result != EBPF_SUCCESS) {
         EBPF_LOG_MESSAGE_ERROR(
             EBPF_TRACELOG_LEVEL_ERROR, EBPF_TRACELOG_KEYWORD_LINK, "ebpf_object_initialize failed for link", result);
diff --git a/libs/execution_context/ebpf_maps.c b/libs/execution_context/ebpf_maps.c
index c774934693..bb49329bde 100644
--- a/libs/execution_context/ebpf_maps.c
+++ b/libs/execution_context/ebpf_maps.c
@@ -177,7 +177,7 @@ typedef struct _ebpf_core_lru_map
 {
     ebpf_core_map_t core_map; //< Core map structure.
     size_t partition_count;   //< Number of LRU partitions. Limited to a maximum of EBPF_LRU_MAXIMUM_PARTITIONS.
-    uint8_t padding[24];      //< Required to ensure partitions are cache aligned.
+    uint8_t padding[8];       //< Required to ensure partitions are cache aligned.
     ebpf_lru_partition_t
         partitions[1]; //< Array of LRU partitions. Limited to a maximum of EBPF_LRU_MAXIMUM_PARTITIONS.
 } ebpf_core_lru_map_t;
@@ -460,6 +460,12 @@ const ebpf_map_metadata_table_t ebpf_map_metadata_tables[];
 static inline _Ret_notnull_ const ebpf_map_metadata_table_t*
 ebpf_map_get_table(_In_range_(0, EBPF_COUNT_OF(ebpf_map_metadata_tables) - 1) const ebpf_map_type_t type);
 
+static void
+_clean_up_object_array_map(_Inout_ ebpf_core_map_t* map, ebpf_object_type_t value_type);
+
+static void
+_clean_up_object_hash_map(_Inout_ ebpf_core_map_t* map);
+
 const ebpf_map_definition_in_memory_t*
 ebpf_map_get_definition(_In_ const ebpf_map_t* map)
 {
@@ -472,6 +478,26 @@ ebpf_map_get_effective_value_size(_In_ const ebpf_map_t* map)
     return map->original_value_size;
 }
 
+static void
+_ebpf_map_object_map_zero_user_reference(_Inout_ ebpf_core_object_t* object)
+{
+    ebpf_core_map_t* core_map = EBPF_FROM_FIELD(ebpf_core_map_t, object, object);
+    ebpf_assert(object->type == EBPF_OBJECT_MAP);
+    ebpf_assert(
+        core_map->ebpf_map_definition.type == BPF_MAP_TYPE_PROG_ARRAY ||
+        core_map->ebpf_map_definition.type == BPF_MAP_TYPE_ARRAY_OF_MAPS ||
+        core_map->ebpf_map_definition.type == BPF_MAP_TYPE_HASH_OF_MAPS);
+
+    if (core_map->ebpf_map_definition.type == BPF_MAP_TYPE_ARRAY_OF_MAPS ||
+        core_map->ebpf_map_definition.type == BPF_MAP_TYPE_PROG_ARRAY) {
+        ebpf_object_type_t type =
+            core_map->ebpf_map_definition.type == BPF_MAP_TYPE_PROG_ARRAY ? EBPF_OBJECT_PROGRAM : EBPF_OBJECT_MAP;
+        _clean_up_object_array_map(core_map, type);
+    } else {
+        _clean_up_object_hash_map(core_map);
+    }
+}
+
 static ebpf_result_t
 _create_array_map_with_map_struct_size(
     size_t map_struct_size, _In_ const ebpf_map_definition_in_memory_t* map_definition, _Outptr_ ebpf_core_map_t** map)
@@ -697,16 +723,30 @@ _associate_inner_map(_Inout_ ebpf_core_object_map_t* object_map, ebpf_handle_t i
 }
 
 static void
-_delete_object_array_map(_Inout_ _Post_invalid_ ebpf_core_map_t* map, ebpf_object_type_t value_type)
+_clean_up_object_array_map(_Inout_ ebpf_core_map_t* map, ebpf_object_type_t value_type)
 {
+    ebpf_core_object_map_t* object_map = EBPF_FROM_FIELD(ebpf_core_object_map_t, core_map, map);
+    ebpf_lock_state_t lock_state = ebpf_lock_lock(&object_map->lock);
+
     // Release all entry references.
     for (uint32_t i = 0; i < map->ebpf_map_definition.max_entries; i++) {
-        ebpf_id_t id = *(ebpf_id_t*)&map->data[i * map->ebpf_map_definition.value_size];
+        uint8_t* entry = &map->data[i * map->ebpf_map_definition.value_size];
+        ebpf_id_t id = *(ebpf_id_t*)entry;
         if (id) {
-            EBPF_OBJECT_RELEASE_ID_REFERENCE(id, value_type);
+            ebpf_core_object_t* value_object = NULL;
+            ebpf_assert_success(ebpf_object_pointer_by_id(id, value_type, (ebpf_core_object_t**)&value_object));
+            EBPF_OBJECT_RELEASE_REFERENCE(value_object);
+            *(ebpf_id_t*)entry = 0;
         }
     }
 
+    ebpf_lock_unlock(&object_map->lock, lock_state);
+}
+
+static void
+_delete_object_array_map(_Inout_ _Post_invalid_ ebpf_core_map_t* map, ebpf_object_type_t value_type)
+{
+    _clean_up_object_array_map(map, value_type);
     _delete_array_map(map);
 }
 
@@ -863,6 +903,7 @@ _update_array_map_entry_with_handle(
 
     // The 'map' and 'key' arguments cannot be NULL due to caller's prior validations.
     ebpf_assert(map != NULL && key != NULL);
+    ebpf_assert(value_type == EBPF_OBJECT_MAP || value_type == EBPF_OBJECT_PROGRAM);
 
     if (option == EBPF_NOEXIST) {
         EBPF_LOG_MESSAGE_UINT64(
@@ -913,14 +954,9 @@ _update_array_map_entry_with_handle(
     uint8_t* entry = &map->data[*(uint32_t*)key * map->ebpf_map_definition.value_size];
     ebpf_id_t old_id = *(ebpf_id_t*)entry;
     if (old_id) {
-        EBPF_OBJECT_RELEASE_ID_REFERENCE(old_id, value_type);
-    }
-
-    if (value_object) {
-
-        // Acquire a reference to the id table entry for the new incoming id. This operation _cannot_ fail as we
-        // already have a valid pointer to the object.  A failure here is indicative of a fatal internal error.
-        ebpf_assert_success(EBPF_OBJECT_ACQUIRE_ID_REFERENCE(value_object->id, value_type));
+        ebpf_core_object_t* old_object = NULL;
+        ebpf_assert_success(ebpf_object_pointer_by_id(old_id, value_type, (ebpf_core_object_t**)&old_object));
+        EBPF_OBJECT_RELEASE_REFERENCE(old_object);
     }
 
     // Note that this could be an 'update to erase' operation where we don't have a valid (incoming) object.  In this
@@ -930,13 +966,7 @@ _update_array_map_entry_with_handle(
     result = EBPF_SUCCESS;
 
 Done:
-
-    if (value_object != NULL) {
-
-        // We have stored the id of the object, so let go of our reference on the object itself.  Going forward, we'll
-        // use the id to get to the object, as and when required.  Note that this is with the explicit understanding
-        // that the object may well have been since destroyed by the time we actually need to use this id. This is
-        // perfectly valid and something we need to be prepared for.
+    if (result != EBPF_SUCCESS && value_object != NULL) {
         EBPF_OBJECT_RELEASE_REFERENCE((ebpf_core_object_t*)value_object);
     }
 
@@ -974,8 +1004,10 @@ _delete_array_map_entry_with_reference(
     result = _find_array_map_entry(map, key, false, &entry);
     if (result == EBPF_SUCCESS) {
         ebpf_id_t id = *(ebpf_id_t*)entry;
+        ebpf_core_object_t* value_object = NULL;
         if (id) {
-            EBPF_OBJECT_RELEASE_ID_REFERENCE(id, value_type);
+            ebpf_assert_success(ebpf_object_pointer_by_id(id, value_type, (ebpf_core_object_t**)&value_object));
+            EBPF_OBJECT_RELEASE_REFERENCE(value_object);
         }
         _delete_array_map_entry(map, key);
     }
@@ -1106,11 +1138,15 @@ _delete_hash_map(_In_ _Post_invalid_ ebpf_core_map_t* map)
 }
 
 static void
-_delete_object_hash_map(_In_ _Post_invalid_ ebpf_core_map_t* map)
+_clean_up_object_hash_map(_Inout_ ebpf_core_map_t* map)
 {
     // Release all entry references.
+    ebpf_core_object_map_t* object_map = EBPF_FROM_FIELD(ebpf_core_object_map_t, core_map, map);
+    ebpf_lock_state_t lock_state = ebpf_lock_lock(&object_map->lock);
+    uint8_t* previous_key = NULL;
+
     uint8_t* next_key;
-    for (uint8_t* previous_key = NULL;; previous_key = next_key) {
+    for (previous_key = NULL;; previous_key = next_key) {
         uint8_t* value;
         ebpf_result_t result =
             ebpf_hash_table_next_key_pointer_and_value((ebpf_hash_table_t*)map->data, previous_key, &next_key, &value);
@@ -1119,10 +1155,27 @@ _delete_object_hash_map(_In_ _Post_invalid_ ebpf_core_map_t* map)
         }
         ebpf_id_t id = *(ebpf_id_t*)value;
         if (id) {
-            EBPF_OBJECT_RELEASE_ID_REFERENCE(id, EBPF_OBJECT_MAP);
+            ebpf_core_object_t* value_object = NULL;
+            ebpf_assert_success(ebpf_object_pointer_by_id(id, EBPF_OBJECT_MAP, (ebpf_core_object_t**)&value_object));
+            EBPF_OBJECT_RELEASE_REFERENCE(value_object);
+            *(ebpf_id_t*)value = 0;
+        }
+        if (previous_key != NULL) {
+            ebpf_assert_success(ebpf_hash_table_delete((ebpf_hash_table_t*)map->data, previous_key));
         }
     }
+    if (previous_key != NULL) {
+        ebpf_assert_success(ebpf_hash_table_delete((ebpf_hash_table_t*)map->data, previous_key));
+    }
+    ebpf_assert(ebpf_hash_table_key_count((ebpf_hash_table_t*)map->data) == 0);
 
+    ebpf_lock_unlock(&object_map->lock, lock_state);
+}
+
+static void
+_delete_object_hash_map(_In_ _Post_invalid_ ebpf_core_map_t* map)
+{
+    _clean_up_object_hash_map(map);
     _delete_hash_map(map);
 }
 
@@ -1749,17 +1802,15 @@ _update_hash_map_entry_with_handle(
         goto Done;
     }
 
-    // Release the reference on the old ID stored here, if any.
+    // Release the reference on the old object stored here, if any.
     if (old_id) {
-        EBPF_OBJECT_RELEASE_ID_REFERENCE(old_id, value_type);
+        ebpf_core_object_t* old_object = NULL;
+        ebpf_assert_success(ebpf_object_pointer_by_id(old_id, value_type, (ebpf_core_object_t**)&old_object));
+        EBPF_OBJECT_RELEASE_REFERENCE(old_object);
     }
 
-    // Acquire a reference to the id table entry for the new incoming id. This operation _cannot_ fail as we already
-    // have a valid pointer to the object.  A failure here is indicative of a fatal internal error.
-    ebpf_assert_success(EBPF_OBJECT_ACQUIRE_ID_REFERENCE(value_object->id, value_type));
-
 Done:
-    if (value_object != NULL) {
+    if (result != EBPF_SUCCESS && value_object != NULL) {
         EBPF_OBJECT_RELEASE_REFERENCE((ebpf_core_object_t*)value_object);
     }
     ebpf_lock_unlock(&object_map->lock, lock_state);
@@ -1788,7 +1839,9 @@ _delete_map_hash_map_entry(_Inout_ ebpf_core_map_t* map, _In_ const uint8_t* key
     if (result == EBPF_SUCCESS) {
         ebpf_id_t id = *(ebpf_id_t*)value;
         if (id) {
-            EBPF_OBJECT_RELEASE_ID_REFERENCE(id, EBPF_OBJECT_MAP);
+            ebpf_core_object_t* value_object = NULL;
+            ebpf_assert_success(ebpf_object_pointer_by_id(id, EBPF_OBJECT_MAP, (ebpf_core_object_t**)&value_object));
+            EBPF_OBJECT_RELEASE_REFERENCE(value_object);
         }
     }
 
@@ -3113,6 +3166,7 @@ ebpf_map_create(
     uint32_t cpu_count;
     cpu_count = ebpf_get_cpu_count();
     ebpf_map_definition_in_memory_t local_map_definition = *ebpf_map_definition;
+    ebpf_notify_user_reference_count_zeroed_t zero_user_function = NULL;
 
     if (type < 0 || type >= EBPF_COUNT_OF(ebpf_map_metadata_tables)) {
         EBPF_LOG_MESSAGE_UINT64(EBPF_TRACELOG_LEVEL_ERROR, EBPF_TRACELOG_KEYWORD_MAP, "Unsupported map type", type);
@@ -3138,6 +3192,10 @@ ebpf_map_create(
         goto Exit;
     }
 
+    if (type == BPF_MAP_TYPE_ARRAY_OF_MAPS || type == BPF_MAP_TYPE_HASH_OF_MAPS || type == BPF_MAP_TYPE_PROG_ARRAY) {
+        zero_user_function = _ebpf_map_object_map_zero_user_reference;
+    }
+
     if (table->per_cpu) {
         local_map_definition.value_size = cpu_count * EBPF_PAD_8(local_map_definition.value_size);
     }
@@ -3169,7 +3227,8 @@ ebpf_map_create(
     }
 
     ebpf_object_get_program_type_t get_program_type = (table->get_object_from_entry) ? _get_map_program_type : NULL;
-    result = EBPF_OBJECT_INITIALIZE(&local_map->object, EBPF_OBJECT_MAP, _ebpf_map_delete, NULL, get_program_type);
+    result = EBPF_OBJECT_INITIALIZE(
+        &local_map->object, EBPF_OBJECT_MAP, _ebpf_map_delete, NULL, zero_user_function, get_program_type);
     if (result != EBPF_SUCCESS) {
         goto Exit;
     }
diff --git a/libs/execution_context/ebpf_native.c b/libs/execution_context/ebpf_native.c
index a0b549bfd4..b9a693675d 100644
--- a/libs/execution_context/ebpf_native.c
+++ b/libs/execution_context/ebpf_native.c
@@ -676,15 +676,17 @@ void
 ebpf_object_update_reference_history(void* object, bool acquire, uint32_t file_id, uint32_t line);
 
 static void
-_ebpf_native_acquire_reference_internal(void* base_object, ebpf_file_id_t file_id, uint32_t line)
+_ebpf_native_acquire_reference_internal(void* base_object, bool user_reference, ebpf_file_id_t file_id, uint32_t line)
 {
+    UNREFERENCED_PARAMETER(user_reference);
     ebpf_object_update_reference_history(base_object, true, file_id, line);
     _ebpf_native_acquire_reference(base_object);
 }
 
 static void
-_ebpf_native_release_reference_internal(void* base_object, ebpf_file_id_t file_id, uint32_t line)
+_ebpf_native_release_reference_internal(void* base_object, bool user_reference, ebpf_file_id_t file_id, uint32_t line)
 {
+    UNREFERENCED_PARAMETER(user_reference);
     ebpf_object_update_reference_history(base_object, false, file_id, line);
     _ebpf_native_release_reference(base_object);
 }
diff --git a/libs/execution_context/ebpf_program.c b/libs/execution_context/ebpf_program.c
index 6d95881951..e39837d177 100644
--- a/libs/execution_context/ebpf_program.c
+++ b/libs/execution_context/ebpf_program.c
@@ -901,6 +901,7 @@ ebpf_program_create(_In_ const ebpf_program_parameters_t* program_parameters, _O
         EBPF_OBJECT_PROGRAM,
         _ebpf_program_free,
         _ebpf_program_notify_reference_count_zeroed,
+        NULL,
         _ebpf_program_get_program_type);
 
     if (retval != EBPF_SUCCESS) {
diff --git a/libs/runtime/ebpf_object.c b/libs/runtime/ebpf_object.c
index d5e9a6fbac..4c76347df9 100644
--- a/libs/runtime/ebpf_object.c
+++ b/libs/runtime/ebpf_object.c
@@ -50,7 +50,6 @@ static ebpf_lock_t _ebpf_object_tracking_list_lock = {0};
 
 typedef struct _ebpf_id_entry
 {
-    int64_t reference_count;    ///< Number of references to this entry.
     ebpf_object_type_t type;    ///< Type of object.
     ebpf_core_object_t* object; ///< Pointer to the object associated with this entry.
 } ebpf_id_entry_t;
@@ -132,7 +131,7 @@ ebpf_object_update_reference_history(void* object, bool acquire, uint32_t file_i
 }
 
 static void
-_ebpf_object_tracking_list_remove(_In_ const ebpf_core_object_t* object, ebpf_file_id_t file_id, uint32_t line)
+_ebpf_object_tracking_list_remove(_In_ const ebpf_core_object_t* object)
 {
     ebpf_id_entry_t* entry = NULL;
     ebpf_result_t return_value = ebpf_hash_table_find(_ebpf_id_table, (const uint8_t*)&object->id, (uint8_t**)&entry);
@@ -141,7 +140,7 @@ _ebpf_object_tracking_list_remove(_In_ const ebpf_core_object_t* object, ebpf_fi
     ebpf_assert(entry->object == object);
     entry->object = NULL;
 
-    ebpf_object_release_id_reference(object->id, object->type, file_id, line);
+    ebpf_assert_success(ebpf_hash_table_delete(_ebpf_id_table, (const uint8_t*)&object->id));
 }
 
 ebpf_result_t
@@ -194,6 +193,7 @@ ebpf_object_initialize(
     ebpf_object_type_t object_type,
     _In_ ebpf_free_object_t free_object,
     _In_opt_ ebpf_notify_reference_count_zeroed_t notify_reference_count_zeroed,
+    _In_opt_ ebpf_notify_user_reference_count_zeroed_t notify_user_reference_count_zeroed,
     ebpf_object_get_program_type_t get_program_type,
     ebpf_file_id_t file_id,
     uint32_t line)
@@ -209,12 +209,10 @@ ebpf_object_initialize(
     object->type = object_type;
     object->free_object = free_object;
     object->notify_reference_count_zeroed = notify_reference_count_zeroed;
+    object->notify_user_reference_count_zeroed = notify_user_reference_count_zeroed;
     object->get_program_type = get_program_type;
     object->id = ebpf_interlocked_increment_int32((volatile int32_t*)&_ebpf_next_id);
-    // Skip invalid IDs.
-    while (object->id == EBPF_ID_NONE) {
-        object->id = ebpf_interlocked_increment_int32((volatile int32_t*)&_ebpf_next_id);
-    }
+
     ebpf_list_initialize(&object->object_list_entry);
     ebpf_epoch_work_item_t* free_object_work_item = NULL;
 
@@ -226,7 +224,7 @@ ebpf_object_initialize(
 
     _update_reference_history(object, EBPF_OBJECT_CREATE, file_id, line);
 
-    ebpf_id_entry_t entry = {.reference_count = 1, .type = object_type, .object = object};
+    ebpf_id_entry_t entry = {.type = object_type, .object = object};
 
     // Use EBPF_HASH_TABLE_OPERATION_INSERT so that it fails if the key already exists.
     result = ebpf_hash_table_update(
@@ -261,7 +259,7 @@ ebpf_object_initialize(
 }
 
 void
-ebpf_object_acquire_reference(_Inout_ ebpf_core_object_t* object, uint32_t file_id, uint32_t line)
+ebpf_object_acquire_reference(_Inout_ ebpf_core_object_t* object, bool user_reference, uint32_t file_id, uint32_t line)
 {
     _update_reference_history(object, EBPF_OBJECT_ACQUIRE, file_id, line);
     if (object->base.marker != _ebpf_object_marker) {
@@ -271,6 +269,13 @@ ebpf_object_acquire_reference(_Inout_ ebpf_core_object_t* object, uint32_t file_
     if (new_ref_count == 1) {
         __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
     }
+
+    if (user_reference) {
+        new_ref_count = ebpf_interlocked_increment_int64(&object->base.user_reference_count);
+        if (new_ref_count == 0) {
+            __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
+        }
+    }
 }
 
 /**
@@ -307,7 +312,8 @@ _ebpf_object_try_acquire_reference(_Inout_ ebpf_base_object_t* object, uint32_t
 }
 
 void
-ebpf_object_release_reference(_Inout_opt_ ebpf_core_object_t* object, uint32_t file_id, uint32_t line)
+ebpf_object_release_reference(
+    _Inout_opt_ ebpf_core_object_t* object, bool user_reference, uint32_t file_id, uint32_t line)
 {
     int64_t new_ref_count;
     if (!object) {
@@ -322,13 +328,25 @@ ebpf_object_release_reference(_Inout_opt_ ebpf_core_object_t* object, uint32_t f
 
     ebpf_assert(object->base.marker == _ebpf_object_marker);
 
+    if (user_reference) {
+        // First decrement the user reference count.
+        new_ref_count = ebpf_interlocked_decrement_int64(&object->base.user_reference_count);
+        if (new_ref_count < 0) {
+            __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
+        } else if (new_ref_count == 0) {
+            if (object->notify_user_reference_count_zeroed) {
+                object->notify_user_reference_count_zeroed(object);
+            }
+        }
+    }
+
     new_ref_count = ebpf_interlocked_decrement_int64(&object->base.reference_count);
     if (new_ref_count < 0) {
         __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
     }
 
     if (new_ref_count == 0) {
-        _ebpf_object_tracking_list_remove(object, file_id, line);
+        _ebpf_object_tracking_list_remove(object);
         _update_reference_history(object, EBPF_OBJECT_DESTROY, file_id, line);
         if (object->notify_reference_count_zeroed) {
             object->notify_reference_count_zeroed(object);
@@ -550,68 +568,3 @@ ebpf_object_reference_by_handle(
     return ebpf_reference_base_object_by_handle(
         handle, _ebpf_object_compare, &object_type, (ebpf_base_object_t**)object, file_id, line);
 }
-
-_Must_inspect_result_ ebpf_result_t
-ebpf_object_acquire_id_reference(ebpf_id_t id, ebpf_object_type_t object_type, uint32_t file_id, uint32_t line)
-{
-    ebpf_id_entry_t* entry = NULL;
-    ebpf_result_t result = ebpf_hash_table_find(_ebpf_id_table, (const uint8_t*)&id, (uint8_t**)&entry);
-    if (result != EBPF_SUCCESS) {
-        goto Done;
-    }
-
-    // Skip entries that have been deleted.
-    if (entry->object == NULL) {
-        result = EBPF_INVALID_OBJECT;
-        goto Done;
-    }
-
-    // Skip entries that are not of the requested type.
-    if (entry->type != object_type) {
-        result = EBPF_INVALID_OBJECT;
-        goto Done;
-    }
-
-    // Acquire a reference on the entry.
-    int64_t new_refcount = ebpf_interlocked_increment_int64(&entry->reference_count);
-    if (new_refcount <= 0) {
-        __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
-    }
-
-    // Update the reference history.
-    ebpf_object_update_reference_history(entry, EBPF_OBJECT_ACQUIRE, file_id, line);
-
-Done:
-    return result;
-}
-
-void
-ebpf_object_release_id_reference(ebpf_id_t id, ebpf_object_type_t object_type, uint32_t file_id, uint32_t line)
-{
-    ebpf_id_entry_t* entry = NULL;
-    // Find the entry in the ID table.
-    ebpf_result_t result = ebpf_hash_table_find(_ebpf_id_table, (const uint8_t*)&id, (uint8_t**)&entry);
-    if (result != EBPF_SUCCESS) {
-        // This should never happen as it means the caller is trying to release a reference
-        // to an entry that does not exist.
-        __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
-    }
-
-    if (entry->type != object_type) {
-        __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
-    }
-
-    int64_t new_refcount = ebpf_interlocked_decrement_int64(&entry->reference_count);
-    if (new_refcount < 0) {
-        __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
-    }
-    ebpf_object_update_reference_history(entry, EBPF_OBJECT_RELEASE, file_id, line);
-
-    if (new_refcount == 0) {
-        result = ebpf_hash_table_delete(_ebpf_id_table, (const uint8_t*)&id);
-        if (result != EBPF_SUCCESS) {
-            __fastfail(FAST_FAIL_INVALID_REFERENCE_COUNT);
-        }
-        ebpf_object_update_reference_history(entry, EBPF_OBJECT_DESTROY, file_id, line);
-    }
-}
diff --git a/libs/runtime/ebpf_object.h b/libs/runtime/ebpf_object.h
index 841f76969a..9bf489fa81 100644
--- a/libs/runtime/ebpf_object.h
+++ b/libs/runtime/ebpf_object.h
@@ -34,12 +34,22 @@ extern "C"
 /**
  * @brief Macro to acquire a reference on an object and record the file and line number of the reference.
  */
-#define EBPF_OBJECT_ACQUIRE_REFERENCE(object) ebpf_object_acquire_reference(object, EBPF_FILE_ID, __LINE__)
+#define EBPF_OBJECT_ACQUIRE_REFERENCE(object) ebpf_object_acquire_reference(object, false, EBPF_FILE_ID, __LINE__)
+
+/**
+ * @brief Macro to acquire a user reference on an object and record the file and line number of the reference.
+ */
+#define EBPF_OBJECT_ACQUIRE_REFERENCE_USER(object) ebpf_object_acquire_reference(object, true, EBPF_FILE_ID, __LINE__)
 
 /**
  * @brief Macro to release a reference on an object and record the file and line number of the reference.
  */
-#define EBPF_OBJECT_RELEASE_REFERENCE(object) ebpf_object_release_reference(object, EBPF_FILE_ID, __LINE__)
+#define EBPF_OBJECT_RELEASE_REFERENCE(object) ebpf_object_release_reference(object, false, EBPF_FILE_ID, __LINE__)
+
+/**
+ * @brief Macro to release a user reference on an object and record the file and line number of the reference.
+ */
+#define EBPF_OBJECT_RELEASE_REFERENCE_USER(object) ebpf_object_release_reference(object, true, EBPF_FILE_ID, __LINE__)
 
 /**
  * @brief Macro to locate the next object in the object list and acquire a reference on it and record the file and
@@ -63,53 +73,58 @@ extern "C"
     ebpf_object_reference_by_handle(object_handle, type, object, EBPF_FILE_ID, __LINE__)
 
 /**
- * @brief Macro to acquire a reference on an object ID and record the file and line number of the reference.
- *
+ * @brief Macro to acquire a reference on an object via it's function pointers and record the file and line number
+ * of the reference.
  */
-#define EBPF_OBJECT_ACQUIRE_ID_REFERENCE(object_id, type) \
-    ebpf_object_acquire_id_reference(object_id, type, EBPF_FILE_ID, __LINE__)
+#define EBPF_OBJECT_ACQUIRE_REFERENCE_INDIRECT(base_object) \
+    base_object->acquire_reference(base_object, false, EBPF_FILE_ID, __LINE__)
 
 /**
- * @brief Macro to release a reference on an object ID and record the file and line number of the reference.
- *
+ * @brief Macro to acquire a user reference on an object via it's function pointers and record the file and line number
+ * of the reference.
  */
-#define EBPF_OBJECT_RELEASE_ID_REFERENCE(object_id, type) \
-    ebpf_object_release_id_reference(object_id, type, EBPF_FILE_ID, __LINE__)
+#define EBPF_OBJECT_ACQUIRE_REFERENCE_INDIRECT_USER(base_object) \
+    base_object->acquire_reference(base_object, true, EBPF_FILE_ID, __LINE__)
 
 /**
- * @brief Macro to acquire a reference on an object via it's function pointers and record the file and line number
+ * @brief Macro to release a reference on an object via it's function pointers and record the file and line number
  * of the reference.
  */
-#define EBPF_OBJECT_ACQUIRE_REFERENCE_INDIRECT(base_object) \
-    base_object->acquire_reference(base_object, EBPF_FILE_ID, __LINE__)
+#define EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT(base_object) \
+    base_object->release_reference(base_object, false, EBPF_FILE_ID, __LINE__)
 
 /**
- * @brief Macro to release a reference on an object via it's function pointers and record the file and line number
+ * @brief Macro to release a user reference on an object via it's function pointers and record the file and line number
  * of the reference.
  */
-#define EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT(base_object) \
-    base_object->release_reference(base_object, EBPF_FILE_ID, __LINE__)
+#define EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT_USER(base_object) \
+    base_object->release_reference(base_object, true, EBPF_FILE_ID, __LINE__)
 
 /**
  * @brief Macro to initialize an object and record the file and line number of the reference.
  *EBPF_OBJECT_INITIALIZE
  */
-#define EBPF_OBJECT_INITIALIZE(object, object_type, free_object, zero_ref_function, get_program_type) \
-    ebpf_object_initialize(                                                                           \
-        (ebpf_core_object_t*)(object),                                                                \
-        (object_type),                                                                                \
-        (free_object),                                                                                \
-        (zero_ref_function),                                                                          \
-        (get_program_type),                                                                           \
-        EBPF_FILE_ID,                                                                                 \
+#define EBPF_OBJECT_INITIALIZE(                                                                    \
+    object, object_type, free_object, zero_ref_function, zero_user_ref_function, get_program_type) \
+    ebpf_object_initialize(                                                                        \
+        (ebpf_core_object_t*)(object),                                                             \
+        (object_type),                                                                             \
+        (free_object),                                                                             \
+        (zero_ref_function),                                                                       \
+        (zero_user_ref_function),                                                                  \
+        (get_program_type),                                                                        \
+        EBPF_FILE_ID,                                                                              \
         __LINE__)
 
     typedef struct _ebpf_base_object ebpf_base_object_t;
-    typedef void (*ebpf_base_release_reference_t)(_Inout_ void* base_object, ebpf_file_id_t file_id, uint32_t line);
-    typedef void (*ebpf_base_acquire_reference_t)(_Inout_ void* base_object, ebpf_file_id_t file_id, uint32_t line);
+    typedef void (*ebpf_base_release_reference_t)(
+        _Inout_ void* base_object, bool user_reference, ebpf_file_id_t file_id, uint32_t line);
+    typedef void (*ebpf_base_acquire_reference_t)(
+        _Inout_ void* base_object, bool user_reference, ebpf_file_id_t file_id, uint32_t line);
 
     typedef struct _ebpf_core_object ebpf_core_object_t;
     typedef void (*ebpf_notify_reference_count_zeroed_t)(ebpf_core_object_t* object);
+    typedef void (*ebpf_notify_user_reference_count_zeroed_t)(ebpf_core_object_t* object);
     typedef void (*ebpf_free_object_t)(ebpf_core_object_t* object);
     typedef const ebpf_program_type_t (*ebpf_object_get_program_type_t)(_In_ const ebpf_core_object_t* object);
 
@@ -127,6 +142,7 @@ extern "C"
                          ///< is freed.
         uint32_t zero_fill;                              ///< Zero fill to make the reference count is 8-byte aligned.
         volatile int64_t reference_count;                ///< Reference count for the object.
+        volatile int64_t user_reference_count;           ///< User reference count for the object.
         ebpf_base_acquire_reference_t acquire_reference; ///< Function to acquire a reference on this object.
         ebpf_base_release_reference_t release_reference; ///< Function to release a reference on this object.
     } ebpf_base_object_t;
@@ -138,6 +154,9 @@ extern "C"
         ebpf_free_object_t free_object; ///< Function to free this object.
         ebpf_notify_reference_count_zeroed_t notify_reference_count_zeroed; ///< Function to notify the object that the
                                                                             ///< reference count has reached zero.
+        ebpf_notify_user_reference_count_zeroed_t notify_user_reference_count_zeroed; ///< Function to notify the object
+                                                                                      ///< that the user reference count
+                                                                                      ///< has reached zero.
         ebpf_object_get_program_type_t get_program_type;     ///< Function to get the program type of this object.
         ebpf_id_t id;                                        ///< ID of this object.
         ebpf_list_entry_t object_list_entry;                 ///< Entry in the object list.
@@ -167,11 +186,12 @@ extern "C"
      * @param[in, out] object ebpf_core_object_t structure to initialize.
      * @param[in] object_type The type of the object.
      * @param[in] free_object The function used to free the object.
-     * @param[in] get_program_type The function used to get a program type, or NULL.  Each program
-     * @param[in] file_id The file ID of the caller.
-     * @param[in] line The line number of the caller.
+     * @param[in] get_program_type The function used to get a program type, or NULL. Each program
      * has a program type, and hence so do maps that can contain programs, whether directly (like
      * BPF_MAP_TYPE_PROG_ARRAY) or indirectly (like BPF_MAP_TYPE_ARRAY_OF_MAPS containing a BPF_MAP_TYPE_PROG_ARRAY).
+     * @param[in] file_id The file ID of the caller.
+     * @param[in] line The line number of the caller.
+     *
      * @retval EBPF_SUCCESS Initialization succeeded.
      * @retval EBPF_NO_MEMORY Could not insert into the tracking table.
      */
@@ -181,6 +201,7 @@ extern "C"
         ebpf_object_type_t object_type,
         _In_ ebpf_free_object_t free_object,
         _In_opt_ ebpf_notify_reference_count_zeroed_t notify_reference_count_zeroed,
+        _In_opt_ ebpf_notify_user_reference_count_zeroed_t notify_user_reference_count_zeroed,
         ebpf_object_get_program_type_t get_program_type,
         ebpf_file_id_t file_id,
         uint32_t line);
@@ -189,22 +210,26 @@ extern "C"
      * @brief Acquire a reference to this object.
      *
      * @param[in,out] object Object on which to acquire a reference.
+     * @param[in] user_reference True if the reference is a user reference.
      * @param[in] file_id The file ID of the caller.
      * @param[in] line The line number of the caller.
      */
     void
-    ebpf_object_acquire_reference(_Inout_ ebpf_core_object_t* object, ebpf_file_id_t file_id, uint32_t line);
+    ebpf_object_acquire_reference(
+        _Inout_ ebpf_core_object_t* object, bool user_reference, ebpf_file_id_t file_id, uint32_t line);
 
     /**
      * @brief Release a reference on this object. If the reference count reaches
      *  zero, the free_function is invoked on the object.
      *
      * @param[in,out] object Object on which to release a reference.
+     * @param[in] user_reference True if the reference is a user reference.
      * @param[in] file_id The file ID of the caller.
      * @param[in] line The line number of the caller.
      */
     void
-    ebpf_object_release_reference(_Inout_opt_ ebpf_core_object_t* object, ebpf_file_id_t file_id, uint32_t line);
+    ebpf_object_release_reference(
+        _Inout_opt_ ebpf_core_object_t* object, bool user_reference, ebpf_file_id_t file_id, uint32_t line);
 
     /**
      * @brief Query the stored type of the object.
@@ -299,36 +324,6 @@ extern "C"
         ebpf_file_id_t file_id,
         uint32_t line);
 
-    /**
-     * @brief Find an ID in the ID table, verify the type matches,
-     *  and acquire a reference on the id table entry for this
-     *  id
-     *
-     * @param[in] id ID to find in table.
-     * @param[in] object_type Object type to match.
-     * @param[in] file_id The file ID of the caller.
-     * @param[in] line The line number of the caller.
-     * @retval EBPF_SUCCESS The operation was successful.
-     * @retval EBPF_KEY_NOT_FOUND The provided ID is not valid.
-     */
-    _Must_inspect_result_ ebpf_result_t
-    ebpf_object_acquire_id_reference(
-        ebpf_id_t start_id, ebpf_object_type_t object_type, ebpf_file_id_t file_id, uint32_t line);
-
-    /**
-     * @brief Find an ID in the ID table, verify the type matches,
-     *  and release the id table entry reference previously acquired
-     *  via ebpf_object_reference_by_id.
-     *
-     * @param[in] id ID to find in table.
-     * @param[in] object_type Object type to match.
-     * @param[in] file_id The file ID of the caller.
-     * @param[in] line The line number of the caller.
-     */
-    void
-    ebpf_object_release_id_reference(
-        ebpf_id_t start_id, ebpf_object_type_t object_type, ebpf_file_id_t file_id, uint32_t line);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/libs/runtime/ebpf_pinning_table.c b/libs/runtime/ebpf_pinning_table.c
index 7f53ab662e..7982050be6 100644
--- a/libs/runtime/ebpf_pinning_table.c
+++ b/libs/runtime/ebpf_pinning_table.c
@@ -44,7 +44,7 @@ _ebpf_pinning_entry_free(_Frees_ptr_opt_ ebpf_pinning_entry_t* pinning_entry)
     if (!pinning_entry) {
         return;
     }
-    EBPF_OBJECT_RELEASE_REFERENCE(pinning_entry->object);
+    EBPF_OBJECT_RELEASE_REFERENCE_USER(pinning_entry->object);
     ebpf_free(pinning_entry->path.value);
     ebpf_free(pinning_entry);
 }
@@ -148,7 +148,7 @@ ebpf_pinning_table_insert(
     }
 
     new_pinning_entry->object = object;
-    EBPF_OBJECT_ACQUIRE_REFERENCE(object);
+    EBPF_OBJECT_ACQUIRE_REFERENCE_USER(object);
     new_key = &new_pinning_entry->path;
 
     state = ebpf_lock_lock(&pinning_table->lock);
diff --git a/libs/runtime/kernel/ebpf_handle_kernel.c b/libs/runtime/kernel/ebpf_handle_kernel.c
index e78d152a47..7922e6ea1d 100644
--- a/libs/runtime/kernel/ebpf_handle_kernel.c
+++ b/libs/runtime/kernel/ebpf_handle_kernel.c
@@ -63,7 +63,7 @@ ebpf_handle_create(_Out_ ebpf_handle_t* handle, _Inout_ ebpf_base_object_t* obje
         goto Done;
     }
 
-    EBPF_OBJECT_ACQUIRE_REFERENCE_INDIRECT(object);
+    EBPF_OBJECT_ACQUIRE_REFERENCE_INDIRECT_USER(object);
     file_object->FsContext2 = object;
 
     *handle = (ebpf_handle_t)file_handle;
@@ -140,7 +140,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) ebpf_result_t ebpf_reference_base_object_by_h
         }
     }
 
-    local_object->acquire_reference(local_object, file_id, line);
+    local_object->acquire_reference(local_object, false, file_id, line);
     *object = local_object;
     return_value = EBPF_SUCCESS;
 
diff --git a/libs/runtime/user/ebpf_handle_user.c b/libs/runtime/user/ebpf_handle_user.c
index d30d6764eb..b2bff0eb18 100644
--- a/libs/runtime/user/ebpf_handle_user.c
+++ b/libs/runtime/user/ebpf_handle_user.c
@@ -38,7 +38,7 @@ ebpf_handle_table_terminate()
     state = ebpf_lock_lock(&_ebpf_handle_table_lock);
     for (handle = 0; handle < EBPF_COUNT_OF(_ebpf_handle_table); handle++) {
         if (_ebpf_handle_table[handle] != NULL) {
-            EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT(_ebpf_handle_table[handle]);
+            EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT_USER(_ebpf_handle_table[handle]);
             _ebpf_handle_table[handle] = NULL;
         }
     }
@@ -67,7 +67,7 @@ ebpf_handle_create(_Out_ ebpf_handle_t* handle, _Inout_ ebpf_base_object_t* obje
 
     *handle = new_handle;
     _ebpf_handle_table[new_handle] = object;
-    EBPF_OBJECT_ACQUIRE_REFERENCE_INDIRECT(object);
+    EBPF_OBJECT_ACQUIRE_REFERENCE_INDIRECT_USER(object);
 
     return_value = EBPF_SUCCESS;
 
@@ -86,7 +86,7 @@ ebpf_handle_close(ebpf_handle_t handle)
 
     state = ebpf_lock_lock(&_ebpf_handle_table_lock);
     if (((size_t)handle < EBPF_COUNT_OF(_ebpf_handle_table)) && _ebpf_handle_table[handle] != NULL) {
-        EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT(_ebpf_handle_table[handle]);
+        EBPF_OBJECT_RELEASE_REFERENCE_INDIRECT_USER(_ebpf_handle_table[handle]);
         _ebpf_handle_table[handle] = NULL;
         return_value = EBPF_SUCCESS;
     } else {
@@ -115,7 +115,7 @@ _IRQL_requires_max_(PASSIVE_LEVEL) ebpf_result_t ebpf_reference_base_object_by_h
     state = ebpf_lock_lock(&_ebpf_handle_table_lock);
     if (_ebpf_handle_table[handle] != NULL &&
         (compare_function == NULL || compare_function(_ebpf_handle_table[handle], context))) {
-        _ebpf_handle_table[handle]->acquire_reference(_ebpf_handle_table[handle], file_id, line);
+        _ebpf_handle_table[handle]->acquire_reference(_ebpf_handle_table[handle], false, file_id, line);
         *object = _ebpf_handle_table[handle];
         return_value = EBPF_SUCCESS;
     } else {