1 // SPDX-License-Identifier: GPL-2.0-only
2
3 /* kselftest for allocinfo ioctl
4 * allocinfo ioctl retrieves allocinfo data through ioctl
5 * Copyright (C) 2026 Google, Inc.
6 */
7
8 #include <errno.h>
9 #include <fcntl.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <stdbool.h>
14 #include <unistd.h>
15 #include <sys/ioctl.h>
16 #include <linux/types.h>
17 #include <linux/alloc_tag.h>
18 #include "../kselftest.h"
19
20 #define MAX_LINE_LEN 512
21 #define ALLOCINFO_PROC "/proc/allocinfo"
22
23 enum ioctl_ret {
24 IOCTL_SUCCESS = 0,
25 IOCTL_FAILURE = 1,
26 IOCTL_INVALID_DATA = 2,
27 };
28
29 #define VEC_MAX_ENTRIES 32
30
31 struct allocinfo_tag_data_vec {
32 struct allocinfo_tag_data tag[VEC_MAX_ENTRIES];
33 __u64 count;
34 };
35
__allocinfo_get_content_id(int dev_fd,struct allocinfo_content_id * params)36 static inline int __allocinfo_get_content_id(int dev_fd, struct allocinfo_content_id *params)
37 {
38 return ioctl(dev_fd, ALLOCINFO_IOC_CONTENT_ID, params);
39 }
40
__allocinfo_get_at(int dev_fd,struct allocinfo_get_at * params)41 static inline int __allocinfo_get_at(int dev_fd, struct allocinfo_get_at *params)
42 {
43 return ioctl(dev_fd, ALLOCINFO_IOC_GET_AT, params);
44 }
45
__allocinfo_get_next(int dev_fd,struct allocinfo_tag_data * params)46 static inline int __allocinfo_get_next(int dev_fd, struct allocinfo_tag_data *params)
47 {
48 return ioctl(dev_fd, ALLOCINFO_IOC_GET_NEXT, params);
49 }
50
match_entry(const struct allocinfo_tag_data * procfs_entry,const struct allocinfo_tag_data * tag_data,bool match_bytes,bool match_calls,bool match_lineno,bool match_function,bool match_filename)51 static bool match_entry(const struct allocinfo_tag_data *procfs_entry,
52 const struct allocinfo_tag_data *tag_data,
53 bool match_bytes, bool match_calls, bool match_lineno,
54 bool match_function, bool match_filename)
55 {
56 if (match_bytes && tag_data->counter.bytes != procfs_entry->counter.bytes) {
57 ksft_print_msg("size retrieved through ioctl does not match procfs\n");
58 return false;
59 }
60
61 if (match_calls && tag_data->counter.calls != procfs_entry->counter.calls) {
62 ksft_print_msg("call count retrieved through ioctl does not match procfs\n");
63 return false;
64 }
65
66 if (match_lineno && tag_data->tag.lineno != procfs_entry->tag.lineno) {
67 ksft_print_msg("lineno retrieved through ioctl does not match procfs\n");
68 return false;
69 }
70
71 if (match_function &&
72 strncmp(tag_data->tag.function, procfs_entry->tag.function, ALLOCINFO_STR_SIZE)) {
73 ksft_print_msg("function retrieved through ioctl does not match procfs\n");
74 return false;
75 }
76
77 if (match_filename &&
78 strncmp(tag_data->tag.filename, procfs_entry->tag.filename, ALLOCINFO_STR_SIZE)) {
79 ksft_print_msg("filename retrieved through ioctl does not match procfs\n");
80 return false;
81 }
82 return true;
83 }
84
match_entries(const struct allocinfo_tag_data_vec * procfs_entries,const struct allocinfo_tag_data_vec * tags,bool match_bytes,bool match_calls,bool match_lineno,bool match_function,bool match_filename)85 static bool match_entries(const struct allocinfo_tag_data_vec *procfs_entries,
86 const struct allocinfo_tag_data_vec *tags,
87 bool match_bytes, bool match_calls, bool match_lineno,
88 bool match_function, bool match_filename)
89 {
90 __u64 i;
91
92 if (procfs_entries->count != tags->count) {
93 ksft_print_msg("Entry count mismatch. ioctl entries: %llu, proc entries: %llu\n",
94 tags->count, procfs_entries->count);
95 return false;
96 }
97 for (i = 0; i < procfs_entries->count; i++) {
98 if (!match_entry(&procfs_entries->tag[i], &tags->tag[i],
99 match_bytes, match_calls, match_lineno,
100 match_function, match_filename)) {
101 ksft_print_msg("%lluth entry does not match.\n", i);
102 return false;
103 }
104 }
105 return true;
106 }
107
allocinfo_str(const char * str)108 static const char *allocinfo_str(const char *str)
109 {
110 size_t len = strlen(str);
111
112 if (len >= ALLOCINFO_STR_SIZE)
113 str += (len - ALLOCINFO_STR_SIZE) + 1;
114 return str;
115 }
116
allocinfo_copy_str(char * dest,const char * src)117 static void allocinfo_copy_str(char *dest, const char *src)
118 {
119 strncpy(dest, allocinfo_str(src), ALLOCINFO_STR_SIZE - 1);
120 dest[ALLOCINFO_STR_SIZE - 1] = '\0';
121 }
122
get_filtered_procfs_entries(struct allocinfo_tag_data_vec * procfs_entries,const struct allocinfo_filter * filter)123 static int get_filtered_procfs_entries(struct allocinfo_tag_data_vec *procfs_entries,
124 const struct allocinfo_filter *filter)
125 {
126 FILE *fp = fopen(ALLOCINFO_PROC, "r");
127 char line[MAX_LINE_LEN];
128 int matches;
129 struct allocinfo_tag_data procfs_entry;
130
131 if (!fp) {
132 ksft_print_msg("Failed to open " ALLOCINFO_PROC " for reading\n");
133 return 1;
134 }
135 memset(procfs_entries, 0, sizeof(*procfs_entries));
136 while (fgets(line, sizeof(line), fp) && procfs_entries->count < VEC_MAX_ENTRIES) {
137 char filename[MAX_LINE_LEN];
138 char function[MAX_LINE_LEN];
139
140 memset(&procfs_entry, 0, sizeof(procfs_entry));
141 matches = sscanf(line, "%llu %llu %[^:]:%llu func:%s",
142 &procfs_entry.counter.bytes,
143 &procfs_entry.counter.calls,
144 filename,
145 &procfs_entry.tag.lineno,
146 function);
147
148 if (matches != 5)
149 continue;
150
151 allocinfo_copy_str(procfs_entry.tag.filename, filename);
152 allocinfo_copy_str(procfs_entry.tag.function, function);
153
154 if (filter->mask & ALLOCINFO_FILTER_MASK_FILENAME) {
155 if (strncmp(procfs_entry.tag.filename,
156 filter->fields.filename, ALLOCINFO_STR_SIZE))
157 continue;
158 }
159 if (filter->mask & ALLOCINFO_FILTER_MASK_FUNCTION) {
160 if (strncmp(procfs_entry.tag.function,
161 filter->fields.function, ALLOCINFO_STR_SIZE))
162 continue;
163 }
164 if (filter->mask & ALLOCINFO_FILTER_MASK_LINENO) {
165 if (procfs_entry.tag.lineno != filter->fields.lineno)
166 continue;
167 }
168 if (filter->mask & ALLOCINFO_FILTER_MASK_MIN_SIZE) {
169 if (procfs_entry.counter.bytes < filter->min_size)
170 continue;
171 }
172 if (filter->mask & ALLOCINFO_FILTER_MASK_MAX_SIZE) {
173 if (procfs_entry.counter.bytes > filter->max_size)
174 continue;
175 }
176
177 memcpy(&procfs_entries->tag[procfs_entries->count++], &procfs_entry,
178 sizeof(procfs_entry));
179 }
180 fclose(fp);
181 return 0;
182 }
183
get_filtered_ioctl_entries(struct allocinfo_tag_data_vec * tags,const struct allocinfo_filter * filter,__u64 start_pos)184 static enum ioctl_ret get_filtered_ioctl_entries(struct allocinfo_tag_data_vec *tags,
185 const struct allocinfo_filter *filter,
186 __u64 start_pos)
187 {
188 int fd = open(ALLOCINFO_PROC, O_RDONLY);
189
190 if (fd < 0) {
191 ksft_print_msg("Failed to open " ALLOCINFO_PROC " for IOCTL\n");
192 return IOCTL_FAILURE;
193 }
194
195 struct allocinfo_content_id start_cont_id, end_cont_id;
196 struct allocinfo_get_at get_at_params;
197 const int max_retries = 10;
198 int retry_count = 0;
199 int status;
200
201 /*
202 * __allocinfo_get_content_id may return different values if a kernel module was loaded
203 * between the two calls. If that happens, the data gathered cannot be considered consistent
204 * and hence needs to be fetched again to avoid flakiness.
205 */
206 do {
207 if (__allocinfo_get_content_id(fd, &start_cont_id)) {
208 ksft_print_msg("allocinfo_get_content_id failed\n");
209 status = IOCTL_FAILURE;
210 break;
211 }
212
213 memset(tags, 0, sizeof(*tags));
214 memset(&get_at_params, 0, sizeof(get_at_params));
215 memcpy(&get_at_params.filter, filter, sizeof(*filter));
216 get_at_params.pos = start_pos;
217 if (__allocinfo_get_at(fd, &get_at_params)) {
218 ksft_print_msg("allocinfo_get_at failed\n");
219 status = IOCTL_FAILURE;
220 break;
221 }
222 memcpy(&tags->tag[tags->count++], &get_at_params.data, sizeof(get_at_params.data));
223
224 while (tags->count < VEC_MAX_ENTRIES &&
225 __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
226 tags->count++;
227
228 if (__allocinfo_get_content_id(fd, &end_cont_id)) {
229 ksft_print_msg("allocinfo_get_content_id failed\n");
230 status = IOCTL_FAILURE;
231 break;
232 }
233
234 if (start_cont_id.id == end_cont_id.id) {
235 status = IOCTL_SUCCESS;
236 } else {
237 ksft_print_msg("allocinfo_get_content_id mismatch, retrying...\n");
238 status = IOCTL_INVALID_DATA;
239 }
240 } while (status == IOCTL_INVALID_DATA && retry_count++ < max_retries);
241
242 close(fd);
243 return status;
244 }
245
run_filter_test(const struct allocinfo_filter * filter)246 static int run_filter_test(const struct allocinfo_filter *filter)
247 {
248 struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
249 struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
250 int ioctl_status;
251 int ret = KSFT_PASS;
252
253 if (!tags || !procfs_entries) {
254 ksft_print_msg("Memory allocation failed.\n");
255 ret = KSFT_FAIL;
256 goto exit;
257 }
258
259 if (get_filtered_procfs_entries(procfs_entries, filter)) {
260 ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
261 ret = KSFT_SKIP;
262 goto exit;
263 }
264
265 if (procfs_entries->count == 0) {
266 ksft_print_msg("No entries found in " ALLOCINFO_PROC ", skipping test\n");
267 ret = KSFT_SKIP;
268 goto exit;
269 }
270
271 ioctl_status = get_filtered_ioctl_entries(tags, filter, 0);
272 if (ioctl_status == IOCTL_INVALID_DATA) {
273 ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
274 ret = KSFT_SKIP;
275 goto exit;
276 }
277 if (ioctl_status == IOCTL_FAILURE) {
278 ksft_print_msg("Error retrieving IOCTL entries.\n");
279 ret = KSFT_FAIL;
280 goto exit;
281 }
282
283 if (!match_entries(procfs_entries, tags, false, false, true, true, true))
284 ret = KSFT_FAIL;
285
286 exit:
287 free(tags);
288 free(procfs_entries);
289 return ret;
290 }
291
test_filename_filter(void)292 static int test_filename_filter(void)
293 {
294 struct allocinfo_filter filter;
295 const char *target_filename = "mm/memory.c";
296
297 memset(&filter, 0, sizeof(filter));
298 filter.mask |= ALLOCINFO_FILTER_MASK_FILENAME;
299 strncpy(filter.fields.filename, target_filename, ALLOCINFO_STR_SIZE);
300
301 return run_filter_test(&filter);
302 }
303
test_function_filter(void)304 static int test_function_filter(void)
305 {
306 struct allocinfo_filter filter;
307 const char *target_function = "dup_mm";
308
309 memset(&filter, 0, sizeof(filter));
310 filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
311 strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
312
313 return run_filter_test(&filter);
314 }
315
test_size_filter(void)316 static int test_size_filter(void)
317 {
318 int fd;
319 struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
320 struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
321 struct allocinfo_filter filter;
322 int ret = KSFT_PASS;
323 __u64 target_size, i, pos;
324 struct allocinfo_tag_data *found_tag = NULL;
325 const char *target_function = "do_init_module";
326 struct allocinfo_content_id start_cont_id, end_cont_id;
327 int retry = 0;
328 const int max_retries = 10;
329
330 if (!tags || !procfs_entries) {
331 ksft_print_msg("Memory allocation failed.\n");
332 ret = KSFT_FAIL;
333 goto freemem;
334 }
335
336 fd = open(ALLOCINFO_PROC, O_RDONLY);
337 if (fd < 0) {
338 ksft_print_msg("Failed to open " ALLOCINFO_PROC ": %s\n", strerror(errno));
339 ret = KSFT_SKIP;
340 goto freemem;
341 }
342
343 do {
344 found_tag = NULL;
345 pos = 0;
346
347 if (__allocinfo_get_content_id(fd, &start_cont_id)) {
348 ksft_print_msg("allocinfo_get_content_id failed\n");
349 ret = KSFT_FAIL;
350 goto exit;
351 }
352
353 memset(&filter, 0, sizeof(filter));
354 filter.mask |= ALLOCINFO_FILTER_MASK_FUNCTION;
355 strncpy(filter.fields.function, target_function, ALLOCINFO_STR_SIZE);
356
357 if (get_filtered_procfs_entries(procfs_entries, &filter)) {
358 ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
359 ret = KSFT_SKIP;
360 goto exit;
361 }
362
363 if (procfs_entries->count == 0) {
364 ksft_print_msg("Function %s not found in procfs\n", target_function);
365 ret = KSFT_SKIP;
366 goto exit;
367 }
368
369 target_size = procfs_entries->tag[0].counter.bytes;
370
371 memset(&filter, 0, sizeof(filter));
372 filter.mask |= ALLOCINFO_FILTER_MASK_MIN_SIZE | ALLOCINFO_FILTER_MASK_MAX_SIZE;
373 filter.min_size = target_size;
374 filter.max_size = target_size;
375
376 while (1) {
377 struct allocinfo_get_at get_at_params;
378
379 memset(&get_at_params, 0, sizeof(get_at_params));
380 memcpy(&get_at_params.filter, &filter, sizeof(filter));
381 get_at_params.pos = pos;
382
383 if (__allocinfo_get_at(fd, &get_at_params))
384 break;
385
386 tags->count = 0;
387 memcpy(&tags->tag[tags->count++], &get_at_params.data,
388 sizeof(get_at_params.data));
389
390 while (tags->count < VEC_MAX_ENTRIES &&
391 __allocinfo_get_next(fd, &tags->tag[tags->count]) == 0)
392 tags->count++;
393
394 for (i = 0; i < tags->count; i++) {
395 if (strcmp(tags->tag[i].tag.function, target_function) == 0) {
396 found_tag = &tags->tag[i];
397 break;
398 }
399 }
400
401 if (found_tag || tags->count < VEC_MAX_ENTRIES)
402 break;
403
404 pos += tags->count;
405 }
406
407 if (__allocinfo_get_content_id(fd, &end_cont_id)) {
408 ksft_print_msg("allocinfo_get_content_id failed\n");
409 ret = KSFT_FAIL;
410 goto exit;
411 }
412
413 if (start_cont_id.id == end_cont_id.id)
414 break;
415
416 ksft_print_msg("Module load detected during size verification, retrying...\n");
417 } while (retry++ < max_retries);
418
419 if (start_cont_id.id == end_cont_id.id && !found_tag) {
420 ksft_print_msg("Entry with function %s not found in IOCTL results\n",
421 target_function);
422 ret = KSFT_FAIL;
423 } else if (start_cont_id.id != end_cont_id.id) {
424 ksft_print_msg("Failed to match content_ids for procfs and IOCTL, skipping...\n");
425 ret = KSFT_SKIP;
426 } else if (found_tag && found_tag->counter.bytes != target_size) {
427 ksft_print_msg("IOCTL entry size %llu does not match target size %llu\n",
428 found_tag->counter.bytes, target_size);
429 ret = KSFT_FAIL;
430 }
431
432 exit:
433 close(fd);
434 freemem:
435 free(tags);
436 free(procfs_entries);
437 return ret;
438 }
439
test_lineno_filter(void)440 static int test_lineno_filter(void)
441 {
442 struct allocinfo_tag_data_vec *tags = malloc(sizeof(*tags));
443 struct allocinfo_tag_data_vec *procfs_entries = malloc(sizeof(*procfs_entries));
444 struct allocinfo_filter filter;
445 enum ioctl_ret ioctl_status;
446 int ret = KSFT_PASS;
447 __u64 target_lineno, i;
448 struct allocinfo_tag_data *target_tag;
449 bool found = false;
450
451 if (!tags || !procfs_entries) {
452 ksft_print_msg("Memory allocation failed.\n");
453 ret = KSFT_FAIL;
454 goto exit;
455 }
456
457 memset(&filter, 0, sizeof(filter));
458
459 if (get_filtered_procfs_entries(procfs_entries, &filter)) {
460 ksft_print_msg("Error retrieving entries from " ALLOCINFO_PROC "\n");
461 ret = KSFT_SKIP;
462 goto exit;
463 }
464 if (procfs_entries->count == 0) {
465 ksft_print_msg("Could not retrieve procfs entries\n");
466 ret = KSFT_SKIP;
467 goto exit;
468 }
469 /*
470 * We depend on the procfs results to determine the line number for the filter before
471 * making the ioctl query. Hence, we cannot reuse run_filter_test here.
472 */
473 target_tag = &procfs_entries->tag[0];
474 target_lineno = target_tag->tag.lineno;
475
476 filter.mask |= ALLOCINFO_FILTER_MASK_LINENO;
477 filter.fields.lineno = target_lineno;
478
479 ioctl_status = get_filtered_ioctl_entries(tags, &filter, 0);
480 if (ioctl_status == IOCTL_INVALID_DATA) {
481 ksft_print_msg("Trouble retrieving valid IOCTL entries, skipping.\n");
482 ret = KSFT_SKIP;
483 goto exit;
484 }
485 if (ioctl_status == IOCTL_FAILURE) {
486 ksft_print_msg("Error retrieving IOCTL entries.\n");
487 ret = KSFT_FAIL;
488 goto exit;
489 }
490
491 for (i = 0; i < tags->count; i++) {
492 if (tags->tag[i].tag.lineno != target_lineno) {
493 ksft_print_msg("IOCTL entry %llu has incorrect lineno %llu.\n",
494 i, tags->tag[i].tag.lineno);
495 ret = KSFT_FAIL;
496 goto exit;
497 }
498
499 if (strncmp(tags->tag[i].tag.function, target_tag->tag.function,
500 ALLOCINFO_STR_SIZE) == 0 &&
501 strncmp(tags->tag[i].tag.filename, target_tag->tag.filename,
502 ALLOCINFO_STR_SIZE) == 0)
503 found = true;
504 }
505
506 if (!found) {
507 ksft_print_msg("Original procfs entry not found in IOCTL lineno filter results.\n");
508 ret = KSFT_FAIL;
509 }
510
511 exit:
512 free(tags);
513 free(procfs_entries);
514 return ret;
515 }
516
main(int argc,char * argv[])517 int main(int argc, char *argv[])
518 {
519 int ret;
520
521 ksft_set_plan(4);
522
523 ret = test_filename_filter();
524 if (ret == KSFT_SKIP)
525 ksft_test_result_skip("Skipping test_filename_filter\n");
526 else
527 ksft_test_result(ret == KSFT_PASS, "test_filename_filter\n");
528
529 ret = test_function_filter();
530 if (ret == KSFT_SKIP)
531 ksft_test_result_skip("Skipping test_function_filter\n");
532 else
533 ksft_test_result(ret == KSFT_PASS, "test_function_filter\n");
534
535 ret = test_size_filter();
536 if (ret == KSFT_SKIP)
537 ksft_test_result_skip("Skipping test_size_filter\n");
538 else
539 ksft_test_result(ret == KSFT_PASS, "test_size_filter\n");
540
541 ret = test_lineno_filter();
542 if (ret == KSFT_SKIP)
543 ksft_test_result_skip("Skipping test_lineno_filter\n");
544 else
545 ksft_test_result(ret == KSFT_PASS, "test_lineno_filter\n");
546
547 ksft_finished();
548 }
549