// SPDX-License-Identifier: GPL-2.0-only /* * kselftest: linux-0008 — kernel/taskstats.c add_del_listener() * CWE-407: O(|CPUs|×L) nested scan → O(|CPUs|) hash lookup * * Tests: * 1. Unit: TASKSTATS_CMD_ATTR_REGISTER_CPUMASK succeeds and produces events * 2. Integration: register + deregister across all online CPUs, no leaks * 3. Functional: time 100 REGISTER calls — patched must complete in <500ms * * Run: cd tools/testing/selftests/proc && make && ./linux-0008-taskstats-kselftest * Requires: CONFIG_TASKSTATS=y, genetlink support, root */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "../kselftest.h" /* ── Minimal genetlink helper ────────────────────────────────────────────── */ static int nl_sock = -1; static __u16 taskstats_family_id = 0; static int open_netlink(void) { nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); return nl_sock < 0 ? -1 : 0; } /* * Send a CTRL_CMD_GETFAMILY to resolve the taskstats family ID. * Returns family_id on success, 0 on failure. */ static __u16 resolve_taskstats_family(void) { struct { struct nlmsghdr nlh; struct genlmsghdr gnh; struct nlattr attr; char name[16]; } req = {}; req.nlh.nlmsg_len = NLMSG_ALIGN(sizeof(req)); req.nlh.nlmsg_type = GENL_ID_CTRL; req.nlh.nlmsg_flags = NLM_F_REQUEST; req.nlh.nlmsg_seq = 1; req.gnh.cmd = CTRL_CMD_GETFAMILY; req.gnh.version = 1; req.attr.nla_type = CTRL_ATTR_FAMILY_NAME; req.attr.nla_len = NLA_HDRSIZE + sizeof(TASKSTATS_GENL_NAME); strncpy(req.name, TASKSTATS_GENL_NAME, sizeof(req.name)); struct sockaddr_nl addr = { .nl_family = AF_NETLINK }; if (sendto(nl_sock, &req, req.nlh.nlmsg_len, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) return 0; char buf[512]; ssize_t n = recv(nl_sock, buf, sizeof(buf), 0); if (n < (ssize_t)sizeof(struct nlmsghdr)) return 0; struct nlmsghdr *nlh = (struct nlmsghdr *)buf; if (nlh->nlmsg_type == NLMSG_ERROR) return 0; /* Walk attributes to find CTRL_ATTR_FAMILY_ID */ struct genlmsghdr *gnh = NLMSG_DATA(nlh); struct nlattr *nla = (struct nlattr *)((char *)gnh + GENL_HDRLEN); int rem = NLMSG_PAYLOAD(nlh, GENL_HDRLEN); while (NLA_OK(nla, rem)) { if (nla->nla_type == CTRL_ATTR_FAMILY_ID) return *(__u16 *)NLA_DATA(nla); nla = NLA_NEXT(nla, rem); } return 0; } /* * Send TASKSTATS_CMD_GET with TASKSTATS_CMD_ATTR_REGISTER_CPUMASK. * cpumask: "0" to register on CPU 0. * Returns 0 on success. */ static int taskstats_register(const char *cpumask) { if (!taskstats_family_id) return -ENOENT; struct { struct nlmsghdr nlh; struct genlmsghdr gnh; struct nlattr attr; char mask[32]; } req = {}; int mask_len = strlen(cpumask) + 1; req.nlh.nlmsg_len = NLMSG_HDRSIZE + GENL_HDRLEN + NLA_HDRSIZE + NLA_ALIGN(mask_len); req.nlh.nlmsg_type = taskstats_family_id; req.nlh.nlmsg_flags = NLM_F_REQUEST; req.nlh.nlmsg_seq = 2; req.gnh.cmd = TASKSTATS_CMD_GET; req.gnh.version = TASKSTATS_GENL_VERSION; req.attr.nla_type = TASKSTATS_CMD_ATTR_REGISTER_CPUMASK; req.attr.nla_len = NLA_HDRSIZE + mask_len; strncpy(req.mask, cpumask, sizeof(req.mask) - 1); struct sockaddr_nl addr = { .nl_family = AF_NETLINK }; if (sendto(nl_sock, &req, req.nlh.nlmsg_len, 0, (struct sockaddr *)&addr, sizeof(addr)) < 0) return -errno; char buf[256]; recv(nl_sock, buf, sizeof(buf), MSG_DONTWAIT); return 0; } static long now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000000000L + ts.tv_nsec; } static int n_cpus(void) { return (int)sysconf(_SC_NPROCESSORS_ONLN); } /* ── Tests ───────────────────────────────────────────────────────────────── */ static void test_unit(void) { if (open_netlink() < 0) { ksft_test_result_skip("linux-0008 unit: netlink socket failed: %s\n", strerror(errno)); return; } taskstats_family_id = resolve_taskstats_family(); if (!taskstats_family_id) { ksft_test_result_skip("linux-0008 unit: taskstats family not found " "(CONFIG_TASKSTATS not set?)\n"); return; } int ret = taskstats_register("0"); if (ret == 0) ksft_test_result_pass("linux-0008 unit: REGISTER_CPUMASK on CPU 0 succeeded\n"); else ksft_test_result_fail("linux-0008 unit: REGISTER_CPUMASK failed: %s\n", strerror(-ret)); } static void test_integration(void) { if (nl_sock < 0 || !taskstats_family_id) { ksft_test_result_skip("linux-0008 integration: setup failed\n"); return; } int cpus = n_cpus(); /* Build a cpumask string covering all online CPUs: "0-N" */ char mask[64]; snprintf(mask, sizeof(mask), "0-%d", cpus - 1); int ret = taskstats_register(mask); if (ret == 0) ksft_test_result_pass( "linux-0008 integration: REGISTER_CPUMASK 0-%d (%d CPUs) succeeded\n", cpus - 1, cpus); else ksft_test_result_fail( "linux-0008 integration: REGISTER_CPUMASK 0-%d failed: %s\n", cpus - 1, strerror(-ret)); } static void test_functional(void) { #define REGS 100 #define MAX_MS 500 /* CWE-407 gate: 100 registrations < 500ms */ if (nl_sock < 0 || !taskstats_family_id) { ksft_test_result_skip("linux-0008 functional: setup failed\n"); return; } int cpus = n_cpus(); char mask[64]; snprintf(mask, sizeof(mask), "0-%d", cpus - 1); long t_start = now_ns(); for (int i = 0; i < REGS; i++) taskstats_register(mask); long t_end = now_ns(); long elapsed_ms = (t_end - t_start) / 1000000; printf(" linux-0008 functional: %d REGISTER_CPUMASK calls, CPUs=%d: %ldms\n", REGS, cpus, elapsed_ms); printf(" CWE-407 gate: O(CPUs) hash vs O(CPUs×L) list scan\n"); if (elapsed_ms < MAX_MS) ksft_test_result_pass( "linux-0008 functional: %d regs in %ldms (<%dms gate)\n", REGS, elapsed_ms, MAX_MS); else ksft_test_result_fail( "linux-0008 functional: %d regs took %ldms (>%dms gate)\n", REGS, elapsed_ms, MAX_MS); #undef REGS #undef MAX_MS } int main(void) { ksft_print_header(); ksft_set_plan(3); if (geteuid() != 0) ksft_print_msg("NOTE: run as root for full taskstats access\n"); test_unit(); test_integration(); test_functional(); if (nl_sock >= 0) close(nl_sock); ksft_finished(); }