Skip to content

Commit ae9b2ea

Browse files
committed
feat: enhance perf_event attachment with atomic duplicate checks and locking mechanisms
1 parent 413ac2d commit ae9b2ea

2 files changed

Lines changed: 62 additions & 51 deletions

File tree

src/userspace_codegen.ml

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3776,7 +3776,7 @@ let generate_complete_userspace_program_from_ir ?(config_declarations = []) ?(ta
37763776
(* For header generation, use all global maps if there are pinned maps, otherwise use the filtered list *)
37773777
let maps_for_headers = if has_any_pinned_maps then global_maps else used_global_maps_with_exec in
37783778

3779-
let uses_bpf_functions = all_usage.uses_load || all_usage.uses_attach || all_usage.uses_detach in
3779+
let uses_bpf_functions = all_usage.uses_load || all_usage.uses_attach || all_usage.uses_detach || all_usage.uses_attach_perf in
37803780
let base_includes = generate_headers_for_maps ~uses_bpf_functions maps_for_headers in
37813781
let bpf_attach_includes = if uses_bpf_functions then
37823782
"#include <sys/ioctl.h>\n"
@@ -4061,38 +4061,8 @@ struct attachment_entry {
40614061
static struct attachment_entry *attached_programs = NULL;
40624062
static pthread_mutex_t attachment_mutex = PTHREAD_MUTEX_INITIALIZER;
40634063

4064-
// Helper function to find attachment entry
4065-
static struct attachment_entry *find_attachment(int prog_fd) {
4066-
pthread_mutex_lock(&attachment_mutex);
4067-
struct attachment_entry *current = attached_programs;
4068-
while (current) {
4069-
if (current->prog_fd == prog_fd) {
4070-
pthread_mutex_unlock(&attachment_mutex);
4071-
return current;
4072-
}
4073-
current = current->next;
4074-
}
4075-
pthread_mutex_unlock(&attachment_mutex);
4076-
return NULL;
4077-
}
4078-
4079-
// Helper function to remove attachment entry
4080-
static void remove_attachment(int prog_fd) {
4081-
pthread_mutex_lock(&attachment_mutex);
4082-
struct attachment_entry **current = &attached_programs;
4083-
while (*current) {
4084-
if ((*current)->prog_fd == prog_fd) {
4085-
struct attachment_entry *to_remove = *current;
4086-
*current = (*current)->next;
4087-
free(to_remove);
4088-
break;
4089-
}
4090-
current = &(*current)->next;
4091-
}
4092-
pthread_mutex_unlock(&attachment_mutex);
4093-
}
4094-
4095-
// Helper function to add attachment entry
4064+
// Helper function to add attachment entry.
4065+
// Duplicate check is performed atomically under the same lock as insertion.
40964066
static int add_attachment(int prog_fd, const char *target, uint32_t flags,
40974067
struct bpf_link *link, int ifindex, int perf_fd,
40984068
enum bpf_prog_type type) {
@@ -4112,6 +4082,17 @@ static int add_attachment(int prog_fd, const char *target, uint32_t flags,
41124082
entry->type = type;
41134083

41144084
pthread_mutex_lock(&attachment_mutex);
4085+
/* Reject duplicate insertions atomically */
4086+
struct attachment_entry *existing = attached_programs;
4087+
while (existing) {
4088+
if (existing->prog_fd == prog_fd) {
4089+
pthread_mutex_unlock(&attachment_mutex);
4090+
free(entry);
4091+
fprintf(stderr, "Program with fd %d is already attached. Use detach() first.\n", prog_fd);
4092+
return -1;
4093+
}
4094+
existing = existing->next;
4095+
}
41154096
entry->next = attached_programs;
41164097
attached_programs = entry;
41174098
pthread_mutex_unlock(&attachment_mutex);
@@ -4141,12 +4122,6 @@ static struct bpf_program *find_prog_by_fd(int prog_fd) {
41414122
return -1;
41424123
}
41434124

4144-
// Check if program is already attached
4145-
if (find_attachment(prog_fd)) {
4146-
fprintf(stderr, "Program with fd %d is already attached. Use detach() first.\n", prog_fd);
4147-
return -1;
4148-
}
4149-
41504125
// Get program type from file descriptor
41514126
struct bpf_prog_info info = {};
41524127
uint32_t info_len = sizeof(info);
@@ -4353,8 +4328,21 @@ static struct bpf_program *find_prog_by_fd(int prog_fd) {
43534328
return;
43544329
}
43554330

4356-
// Find the attachment entry
4357-
struct attachment_entry *entry = find_attachment(prog_fd);
4331+
/* Atomically extract the entry from the list so concurrent detach/perf_read
4332+
* cannot dereference a freed pointer. */
4333+
pthread_mutex_lock(&attachment_mutex);
4334+
struct attachment_entry *entry = NULL;
4335+
struct attachment_entry **cur = &attached_programs;
4336+
while (*cur) {
4337+
if ((*cur)->prog_fd == prog_fd) {
4338+
entry = *cur;
4339+
*cur = entry->next;
4340+
break;
4341+
}
4342+
cur = &(*cur)->next;
4343+
}
4344+
pthread_mutex_unlock(&attachment_mutex);
4345+
43584346
if (!entry) {
43594347
fprintf(stderr, "No active attachment found for program fd %%d\n", prog_fd);
43604348
return;
@@ -4412,8 +4400,7 @@ static struct bpf_program *find_prog_by_fd(int prog_fd) {
44124400
break;
44134401
}
44144402
4415-
// Remove from tracking
4416-
remove_attachment(prog_fd);
4403+
free(entry);
44174404
}|} detach_perf_case
44184405
else "" in
44194406
@@ -4619,8 +4606,13 @@ int ks_attach_perf_event(int prog_fd, ks_perf_options opts, int flags) {
46194606
fprintf(stderr, "Invalid program file descriptor: %d\n", prog_fd);
46204607
return -1;
46214608
}
4622-
if (find_attachment(prog_fd)) {
4623-
fprintf(stderr, "Program with fd %d is already attached. Use detach() first.\n", prog_fd);
4609+
/* Verify the program is actually a @perf_event program */
4610+
struct bpf_prog_info prog_info = {};
4611+
uint32_t info_len = sizeof(prog_info);
4612+
if (bpf_obj_get_info_by_fd(prog_fd, &prog_info, &info_len) == 0 &&
4613+
prog_info.type != BPF_PROG_TYPE_PERF_EVENT) {
4614+
fprintf(stderr, "ks_attach_perf_event: fd %d is not a @perf_event program (type=%u)\n",
4615+
prog_fd, prog_info.type);
46244616
return -1;
46254617
}
46264618
@@ -4693,16 +4685,29 @@ int64_t ks_read_perf_count(int perf_fd) {
46934685
/* Read the counter for the perf_event program bound to prog_fd.
46944686
* Looks up the perf_fd from the attachment table and calls ks_read_perf_count. */
46954687
int64_t ks_perf_read(int prog_fd) {
4696-
struct attachment_entry *entry = find_attachment(prog_fd);
4697-
if (!entry) {
4688+
/* Read perf_fd under the lock so the entry cannot be freed concurrently */
4689+
pthread_mutex_lock(&attachment_mutex);
4690+
int found = 0;
4691+
int perf_fd = -1;
4692+
struct attachment_entry *cur = attached_programs;
4693+
while (cur) {
4694+
if (cur->prog_fd == prog_fd) {
4695+
found = 1;
4696+
perf_fd = cur->perf_fd;
4697+
break;
4698+
}
4699+
cur = cur->next;
4700+
}
4701+
pthread_mutex_unlock(&attachment_mutex);
4702+
if (!found) {
46984703
fprintf(stderr, "ks_perf_read: no active attachment for program fd %d\n", prog_fd);
46994704
return -1;
47004705
}
4701-
if (entry->perf_fd < 0) {
4706+
if (perf_fd < 0) {
47024707
fprintf(stderr, "ks_perf_read: program fd %d is not a perf_event program\n", prog_fd);
47034708
return -1;
47044709
}
4705-
return ks_read_perf_count(entry->perf_fd);
4710+
return ks_read_perf_count(perf_fd);
47064711
}
47074712
47084713
/* Print the current counter value for a named event to stdout.

tests/test_perf_event_attach.ml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@ let test_perf_read_count_function_generated () =
262262
check bool "read error message present" true
263263
(contains_substr code "ks_read_perf_count: read failed on perf_fd");
264264
check bool "short read diagnostic present" true
265-
(contains_substr code "short read")
265+
(contains_substr code "short read");
266+
check bool "ks_perf_read reads perf_fd under the lock" true
267+
(contains_substr code "Read perf_fd under the lock")
266268

267269
let test_perf_attach_event_function_generated () =
268270
(* attach(prog, perf_options{...}, 0) must generate ks_attach_perf_event which
@@ -285,7 +287,11 @@ let test_perf_attach_event_function_generated () =
285287
check bool "no snprintf perf_fd string hack" false
286288
(contains_substr code "snprintf(%s, sizeof(%s),");
287289
check bool "find_prog_by_fd helper used for program lookup" true
288-
(contains_substr code "find_prog_by_fd")
290+
(contains_substr code "find_prog_by_fd");
291+
check bool "perf attach rejects wrong program type at runtime" true
292+
(contains_substr code "is not a @perf_event program");
293+
check bool "add_attachment performs atomic duplicate check" true
294+
(contains_substr code "Reject duplicate insertions atomically")
289295

290296
(* ── Type-checking regression tests ───────────────────────────────────── *)
291297

0 commit comments

Comments
 (0)