| From foo@baz Tue Nov 21 13:07:02 CET 2017 |
| From: "Jason A. Donenfeld" <Jason@zx2c4.com> |
| Date: Thu, 9 Nov 2017 13:04:44 +0900 |
| Subject: af_netlink: ensure that NLMSG_DONE never fails in dumps |
| |
| From: "Jason A. Donenfeld" <Jason@zx2c4.com> |
| |
| |
| [ Upstream commit 0642840b8bb008528dbdf929cec9f65ac4231ad0 ] |
| |
| The way people generally use netlink_dump is that they fill in the skb |
| as much as possible, breaking when nla_put returns an error. Then, they |
| get called again and start filling out the next skb, and again, and so |
| forth. The mechanism at work here is the ability for the iterative |
| dumping function to detect when the skb is filled up and not fill it |
| past the brim, waiting for a fresh skb for the rest of the data. |
| |
| However, if the attributes are small and nicely packed, it is possible |
| that a dump callback function successfully fills in attributes until the |
| skb is of size 4080 (libmnl's default page-sized receive buffer size). |
| The dump function completes, satisfied, and then, if it happens to be |
| that this is actually the last skb, and no further ones are to be sent, |
| then netlink_dump will add on the NLMSG_DONE part: |
| |
| nlh = nlmsg_put_answer(skb, cb, NLMSG_DONE, sizeof(len), NLM_F_MULTI); |
| |
| It is very important that netlink_dump does this, of course. However, in |
| this example, that call to nlmsg_put_answer will fail, because the |
| previous filling by the dump function did not leave it enough room. And |
| how could it possibly have done so? All of the nla_put variety of |
| functions simply check to see if the skb has enough tailroom, |
| independent of the context it is in. |
| |
| In order to keep the important assumptions of all netlink dump users, it |
| is therefore important to give them an skb that has this end part of the |
| tail already reserved, so that the call to nlmsg_put_answer does not |
| fail. Otherwise, library authors are forced to find some bizarre sized |
| receive buffer that has a large modulo relative to the common sizes of |
| messages received, which is ugly and buggy. |
| |
| This patch thus saves the NLMSG_DONE for an additional message, for the |
| case that things are dangerously close to the brim. This requires |
| keeping track of the errno from ->dump() across calls. |
| |
| Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/netlink/af_netlink.c | 17 +++++++++++------ |
| net/netlink/af_netlink.h | 1 + |
| 2 files changed, 12 insertions(+), 6 deletions(-) |
| |
| --- a/net/netlink/af_netlink.c |
| +++ b/net/netlink/af_netlink.c |
| @@ -2136,7 +2136,7 @@ static int netlink_dump(struct sock *sk) |
| struct sk_buff *skb = NULL; |
| struct nlmsghdr *nlh; |
| struct module *module; |
| - int len, err = -ENOBUFS; |
| + int err = -ENOBUFS; |
| int alloc_min_size; |
| int alloc_size; |
| |
| @@ -2183,9 +2183,11 @@ static int netlink_dump(struct sock *sk) |
| skb_reserve(skb, skb_tailroom(skb) - alloc_size); |
| netlink_skb_set_owner_r(skb, sk); |
| |
| - len = cb->dump(skb, cb); |
| + if (nlk->dump_done_errno > 0) |
| + nlk->dump_done_errno = cb->dump(skb, cb); |
| |
| - if (len > 0) { |
| + if (nlk->dump_done_errno > 0 || |
| + skb_tailroom(skb) < nlmsg_total_size(sizeof(nlk->dump_done_errno))) { |
| mutex_unlock(nlk->cb_mutex); |
| |
| if (sk_filter(sk, skb)) |
| @@ -2195,13 +2197,15 @@ static int netlink_dump(struct sock *sk) |
| return 0; |
| } |
| |
| - nlh = nlmsg_put_answer(skb, cb, NLMSG_DONE, sizeof(len), NLM_F_MULTI); |
| - if (!nlh) |
| + nlh = nlmsg_put_answer(skb, cb, NLMSG_DONE, |
| + sizeof(nlk->dump_done_errno), NLM_F_MULTI); |
| + if (WARN_ON(!nlh)) |
| goto errout_skb; |
| |
| nl_dump_check_consistent(cb, nlh); |
| |
| - memcpy(nlmsg_data(nlh), &len, sizeof(len)); |
| + memcpy(nlmsg_data(nlh), &nlk->dump_done_errno, |
| + sizeof(nlk->dump_done_errno)); |
| |
| if (sk_filter(sk, skb)) |
| kfree_skb(skb); |
| @@ -2273,6 +2277,7 @@ int __netlink_dump_start(struct sock *ss |
| } |
| |
| nlk->cb_running = true; |
| + nlk->dump_done_errno = INT_MAX; |
| |
| mutex_unlock(nlk->cb_mutex); |
| |
| --- a/net/netlink/af_netlink.h |
| +++ b/net/netlink/af_netlink.h |
| @@ -34,6 +34,7 @@ struct netlink_sock { |
| wait_queue_head_t wait; |
| bool bound; |
| bool cb_running; |
| + int dump_done_errno; |
| struct netlink_callback cb; |
| struct mutex *cb_mutex; |
| struct mutex cb_def_mutex; |