Skip to content

Commit 2ad5dc3

Browse files
almostivanNipaLocal
authored andcommitted
netlink: Fix wraparound of sk->sk_rmem_alloc
For netlink sockets, when comparing allocated rmem memory with the rcvbuf limit, the comparison is done using signed values. This means that if rcvbuf is near INT_MAX, then sk->sk_rmem_alloc may become negative in the comparison with rcvbuf which will yield incorrect results. This can be reproduced by using the program from SOCK_DIAG(7) with some slight modifications. First, setting sk->sk_rcvbuf to INT_MAX using SO_RCVBUFFORCE and then secondly running the "send_query()" in a loop while not calling "receive_responses()". In this case, the value of sk->sk_rmem_alloc will continuously wrap around and thus more memory is allocated than the sk->sk_rcvbuf limit. This will eventually fill all of memory leading to an out of memory condition with skbs filling up the slab. Let's fix this in a similar manner to: commit 5a465a0 ("udp: Fix multiple wraparounds of sk->sk_rmem_alloc.") As noted in that fix, if there are multiple threads writing to a netlink socket it's possible to slightly exceed rcvbuf value. But as noted this avoids an expensive 'atomic_add_return()' for the common case. I've confirmed that with the fix the modified program from SOCK_DIAG(7) can no longer fill memory and the sk->sk_rcvbuf limit is enforced. Signed-off-by: Jason Baron <[email protected]> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: NipaLocal <nipa@local>
1 parent fe0bc1e commit 2ad5dc3

File tree

1 file changed

+21
-14
lines changed

1 file changed

+21
-14
lines changed

net/netlink/af_netlink.c

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,8 +1216,8 @@ int netlink_attachskb(struct sock *sk, struct sk_buff *skb,
12161216

12171217
nlk = nlk_sk(sk);
12181218

1219-
if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
1220-
test_bit(NETLINK_S_CONGESTED, &nlk->state))) {
1219+
if (!sock_rcvbuf_has_space(sk, skb) ||
1220+
test_bit(NETLINK_S_CONGESTED, &nlk->state)) {
12211221
DECLARE_WAITQUEUE(wait, current);
12221222
if (!*timeo) {
12231223
if (!ssk || netlink_is_kernel(ssk))
@@ -1230,7 +1230,7 @@ int netlink_attachskb(struct sock *sk, struct sk_buff *skb,
12301230
__set_current_state(TASK_INTERRUPTIBLE);
12311231
add_wait_queue(&nlk->wait, &wait);
12321232

1233-
if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
1233+
if ((!sock_rcvbuf_has_space(sk, skb) ||
12341234
test_bit(NETLINK_S_CONGESTED, &nlk->state)) &&
12351235
!sock_flag(sk, SOCK_DEAD))
12361236
*timeo = schedule_timeout(*timeo);
@@ -1383,12 +1383,15 @@ EXPORT_SYMBOL_GPL(netlink_strict_get_check);
13831383
static int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
13841384
{
13851385
struct netlink_sock *nlk = nlk_sk(sk);
1386+
unsigned int rmem, rcvbuf;
13861387

1387-
if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
1388+
if (sock_rcvbuf_has_space(sk, skb) &&
13881389
!test_bit(NETLINK_S_CONGESTED, &nlk->state)) {
13891390
netlink_skb_set_owner_r(skb, sk);
13901391
__netlink_sendskb(sk, skb);
1391-
return atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1);
1392+
rmem = atomic_read(&sk->sk_rmem_alloc);
1393+
rcvbuf = READ_ONCE(sk->sk_rcvbuf);
1394+
return rmem > (rcvbuf >> 1);
13921395
}
13931396
return -1;
13941397
}
@@ -1895,6 +1898,7 @@ static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
18951898
struct netlink_sock *nlk = nlk_sk(sk);
18961899
size_t copied, max_recvmsg_len;
18971900
struct sk_buff *skb, *data_skb;
1901+
unsigned int rmem, rcvbuf;
18981902
int err, ret;
18991903

19001904
if (flags & MSG_OOB)
@@ -1960,12 +1964,15 @@ static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
19601964

19611965
skb_free_datagram(sk, skb);
19621966

1963-
if (READ_ONCE(nlk->cb_running) &&
1964-
atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2) {
1965-
ret = netlink_dump(sk, false);
1966-
if (ret) {
1967-
WRITE_ONCE(sk->sk_err, -ret);
1968-
sk_error_report(sk);
1967+
if (READ_ONCE(nlk->cb_running)) {
1968+
rmem = atomic_read(&sk->sk_rmem_alloc);
1969+
rcvbuf = READ_ONCE(sk->sk_rcvbuf);
1970+
if (rmem <= (rcvbuf >> 1)) {
1971+
ret = netlink_dump(sk, false);
1972+
if (ret) {
1973+
WRITE_ONCE(sk->sk_err, -ret);
1974+
sk_error_report(sk);
1975+
}
19691976
}
19701977
}
19711978

@@ -2258,9 +2265,6 @@ static int netlink_dump(struct sock *sk, bool lock_taken)
22582265
goto errout_skb;
22592266
}
22602267

2261-
if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf)
2262-
goto errout_skb;
2263-
22642268
/* NLMSG_GOODSIZE is small to avoid high order allocations being
22652269
* required, but it makes sense to _attempt_ a 32KiB allocation
22662270
* to reduce number of system calls on dump operations, if user
@@ -2283,6 +2287,9 @@ static int netlink_dump(struct sock *sk, bool lock_taken)
22832287
if (!skb)
22842288
goto errout_skb;
22852289

2290+
if (!sock_rcvbuf_has_space(sk, skb))
2291+
goto errout_skb;
2292+
22862293
/* Trim skb to allocated size. User is expected to provide buffer as
22872294
* large as max(min_dump_alloc, 32KiB (max_recvmsg_len capped at
22882295
* netlink_recvmsg())). dump will pack as many smaller messages as

0 commit comments

Comments
 (0)