// SPDX-License-Identifier: GPL-2.0+ /* * nlr.c - userspace RTM_GETLINK dump hammer for the rtnl_link_ops_get() * verification (audit-report Case 1). * * Sends NLM_F_DUMP RTM_GETLINK requests carrying a nested * IFLA_LINKINFO{ IFLA_INFO_KIND= } filter, which drives exactly: * * rtnl_dump_ifinfo -> linkinfo_to_kind_ops -> rtnl_link_ops_get(kind) * -> (inlined) list_for_each_entry_rcu + strcmp + srcu_read_lock(&ops->srcu) * * i.e. the audited reader path. The dump walk itself runs without RTNL * (RTM_GETLINK is registered RTNL_FLAG_DUMP_UNLOCKED), so it is fully * concurrent with rtnl_link_unregister(). * * Usage: nlr [seconds] */ #include #include #include #include #include #include #include #include #include #include #ifndef RTNL_FAMILY_MAX #define RTNL_FAMILY_MAX 33 #endif #define NLR_BUF 65536 static int mk_dump_req(char *b, int blen, const char *kind, uint32_t seq) { struct nlmsghdr *nh = (struct nlmsghdr *)b; struct ifinfomsg *ifi; struct rtattr *linfo, *ikind; int klen = strlen(kind) + 1; memset(b, 0, blen); nh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi)); nh->nlmsg_type = RTM_GETLINK; nh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; nh->nlmsg_seq = seq; nh->nlmsg_pid = 0; ifi = NLMSG_DATA(nh); ifi->ifi_family = AF_UNSPEC; /* nested IFLA_LINKINFO { IFLA_INFO_KIND = kind } */ linfo = (struct rtattr *)((char *)b + NLMSG_ALIGN(nh->nlmsg_len)); linfo->rta_type = IFLA_LINKINFO; linfo->rta_len = RTA_LENGTH(0); ikind = (struct rtattr *)((char *)linfo + RTA_ALIGN(linfo->rta_len)); ikind->rta_type = IFLA_INFO_KIND; ikind->rta_len = RTA_LENGTH(klen); memcpy(RTA_DATA(ikind), kind, klen); linfo->rta_len = (char *)ikind + RTA_ALIGN(ikind->rta_len) - (char *)linfo; nh->nlmsg_len = (char *)ikind + RTA_ALIGN(ikind->rta_len) - b; return nh->nlmsg_len; } int main(int argc, char **argv) { const char *kind = argc > 1 ? argv[1] : "rtnlrace"; int secs = argc > 2 ? atoi(argv[2]) : 30; int fd, iters = 0, errs = 0; uint32_t seq = 1; char req[NLR_BUF], buf[NLR_BUF]; struct timeval tv = { .tv_sec = 1, .tv_usec = 0 }; time_t t0 = time(NULL); fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); if (fd < 0) { perror("socket"); return 1; } setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); fprintf(stderr, "nlr: hammering RTM_GETLINK dumps with kind=%s for %ds\n", kind, secs); while (time(NULL) - t0 < secs) { struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; struct iovec iov = { buf, sizeof(buf) }; struct msghdr msg = { .msg_name = &sa, .msg_namelen = sizeof(sa), .msg_iov = &iov, .msg_iovlen = 1, }; int len = mk_dump_req(req, sizeof(req), kind, seq++); ssize_t n; if (sendto(fd, req, len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) { errs++; continue; } /* drain until DONE or timeout/error */ for (;;) { n = recvmsg(fd, &msg, 0); if (n <= 0) { errs++; break; } { struct nlmsghdr *nh = (struct nlmsghdr *)buf; for (; NLMSG_OK(nh, (unsigned)n); nh = NLMSG_NEXT(nh, n)) { if (nh->nlmsg_type == NLMSG_DONE || nh->nlmsg_type == NLMSG_ERROR) goto next_iter; } } } next_iter: iters++; if ((iters & 0x3ff) == 0) fprintf(stderr, "nlr: %d iters (%d errs)\n", iters, errs); } fprintf(stderr, "nlr: done: %d iters, %d errs\n", iters, errs); return 0; }