* [PATCH 0/2] netlink: Bounds-check struct nlmsgerr creation @ 2022-09-01 3:06 Kees Cook 2022-09-01 3:06 ` [PATCH 1/2] netlink: Bounds-check nlmsg_len() Kees Cook 2022-09-01 3:06 ` [PATCH 2/2] netlink: Bounds-check struct nlmsgerr creation Kees Cook 0 siblings, 2 replies; 8+ messages in thread From: Kees Cook @ 2022-09-01 3:06 UTC (permalink / raw) To: Jakub Kicinski Cc: Kees Cook, Pablo Neira Ayuso, David S. Miller, Eric Dumazet, Paolo Abeni, Jozsef Kadlecsik, Florian Westphal, syzbot, Yajun Deng, Oliver Hartkopp, Harshit Mogalapalli, linux-kernel, netdev, netfilter-devel, coreteam, linux-hardening Hi, In order to avoid triggering the coming runtime memcpy() bounds checking, the length of the destination needs to be "visible" to the compiler in some way. However, netlink is constructed in a rather hidden fashion, and my attempts to wrangle it have resulted in this series, which perform explicit bounds checking before using unsafe_memcpy(). -Kees Kees Cook (2): netlink: Bounds-check nlmsg_len() netlink: Bounds-check struct nlmsgerr creation include/net/netlink.h | 10 ++++++- net/netfilter/ipset/ip_set_core.c | 10 +++++-- net/netlink/af_netlink.c | 49 +++++++++++++++++++++---------- 3 files changed, 49 insertions(+), 20 deletions(-) -- 2.34.1 ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/2] netlink: Bounds-check nlmsg_len() 2022-09-01 3:06 [PATCH 0/2] netlink: Bounds-check struct nlmsgerr creation Kees Cook @ 2022-09-01 3:06 ` Kees Cook 2022-09-01 3:18 ` Jakub Kicinski 2022-09-01 3:06 ` [PATCH 2/2] netlink: Bounds-check struct nlmsgerr creation Kees Cook 1 sibling, 1 reply; 8+ messages in thread From: Kees Cook @ 2022-09-01 3:06 UTC (permalink / raw) To: Jakub Kicinski Cc: Kees Cook, David S. Miller, Eric Dumazet, Paolo Abeni, Pablo Neira Ayuso, Jozsef Kadlecsik, Florian Westphal, syzbot, Yajun Deng, netdev, netfilter-devel, coreteam, Oliver Hartkopp, Harshit Mogalapalli, linux-kernel, linux-hardening The nlmsg_len() helper returned "int" from a u32 calculation that could possible go negative. WARN() if this calculation ever goes negative (instead returning 0), or if the result would be larger than INT_MAX (instead returning INT_MAX). Cc: "David S. Miller" <davem@davemloft.net> Cc: Eric Dumazet <edumazet@google.com> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Paolo Abeni <pabeni@redhat.com> Cc: Pablo Neira Ayuso <pablo@netfilter.org> Cc: Jozsef Kadlecsik <kadlec@netfilter.org> Cc: Florian Westphal <fw@strlen.de> Cc: syzbot <syzkaller@googlegroups.com> Cc: Yajun Deng <yajun.deng@linux.dev> Cc: netdev@vger.kernel.org Cc: netfilter-devel@vger.kernel.org Cc: coreteam@netfilter.org Signed-off-by: Kees Cook <keescook@chromium.org> --- include/net/netlink.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/include/net/netlink.h b/include/net/netlink.h index 7a2a9d3144ba..f8cb0543635e 100644 --- a/include/net/netlink.h +++ b/include/net/netlink.h @@ -576,7 +576,15 @@ static inline void *nlmsg_data(const struct nlmsghdr *nlh) */ static inline int nlmsg_len(const struct nlmsghdr *nlh) { - return nlh->nlmsg_len - NLMSG_HDRLEN; + u32 nlmsg_contents_len; + + if (WARN_ON_ONCE(check_sub_overflow(nlh->nlmsg_len, + (u32)NLMSG_HDRLEN, + &nlmsg_contents_len))) + return 0; + if (WARN_ON_ONCE(nlmsg_contents_len > INT_MAX)) + return INT_MAX; + return nlmsg_contents_len; } /** -- 2.34.1 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] netlink: Bounds-check nlmsg_len() 2022-09-01 3:06 ` [PATCH 1/2] netlink: Bounds-check nlmsg_len() Kees Cook @ 2022-09-01 3:18 ` Jakub Kicinski 2022-09-01 6:27 ` Kees Cook 0 siblings, 1 reply; 8+ messages in thread From: Jakub Kicinski @ 2022-09-01 3:18 UTC (permalink / raw) To: Kees Cook Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Pablo Neira Ayuso, Jozsef Kadlecsik, Florian Westphal, syzbot, Yajun Deng, netdev, netfilter-devel, coreteam, Oliver Hartkopp, Harshit Mogalapalli, linux-kernel, linux-hardening On Wed, 31 Aug 2022 20:06:09 -0700 Kees Cook wrote: > static inline int nlmsg_len(const struct nlmsghdr *nlh) > { > - return nlh->nlmsg_len - NLMSG_HDRLEN; > + u32 nlmsg_contents_len; > + > + if (WARN_ON_ONCE(check_sub_overflow(nlh->nlmsg_len, > + (u32)NLMSG_HDRLEN, > + &nlmsg_contents_len))) > + return 0; > + if (WARN_ON_ONCE(nlmsg_contents_len > INT_MAX)) > + return INT_MAX; > + return nlmsg_contents_len; We check the messages on input, making sure the length is valid wrt skb->len, and sane, ie > NLMSG_HDRLEN. See netlink_rcv_skb(). Can we not, pretty please? :( ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] netlink: Bounds-check nlmsg_len() 2022-09-01 3:18 ` Jakub Kicinski @ 2022-09-01 6:27 ` Kees Cook 2022-09-01 19:49 ` Jakub Kicinski 0 siblings, 1 reply; 8+ messages in thread From: Kees Cook @ 2022-09-01 6:27 UTC (permalink / raw) To: Jakub Kicinski Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Pablo Neira Ayuso, Jozsef Kadlecsik, Florian Westphal, syzbot, Yajun Deng, netdev, netfilter-devel, coreteam, Oliver Hartkopp, Harshit Mogalapalli, linux-kernel, linux-hardening On Wed, Aug 31, 2022 at 08:18:25PM -0700, Jakub Kicinski wrote: > On Wed, 31 Aug 2022 20:06:09 -0700 Kees Cook wrote: > > static inline int nlmsg_len(const struct nlmsghdr *nlh) > > { > > - return nlh->nlmsg_len - NLMSG_HDRLEN; > > + u32 nlmsg_contents_len; > > + > > + if (WARN_ON_ONCE(check_sub_overflow(nlh->nlmsg_len, > > + (u32)NLMSG_HDRLEN, > > + &nlmsg_contents_len))) > > + return 0; > > + if (WARN_ON_ONCE(nlmsg_contents_len > INT_MAX)) > > + return INT_MAX; > > + return nlmsg_contents_len; > > We check the messages on input, making sure the length is valid wrt > skb->len, and sane, ie > NLMSG_HDRLEN. See netlink_rcv_skb(). > > Can we not, pretty please? :( This would catch corrupted values... Is the concern the growth in image size? The check_sub_overflow() isn't large at all -- it's just adding a single overflow bit test. The WARNs are heavier, but they're all out-of-line. -- Kees Cook ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] netlink: Bounds-check nlmsg_len() 2022-09-01 6:27 ` Kees Cook @ 2022-09-01 19:49 ` Jakub Kicinski 2022-09-01 20:54 ` Eric Dumazet 0 siblings, 1 reply; 8+ messages in thread From: Jakub Kicinski @ 2022-09-01 19:49 UTC (permalink / raw) To: Kees Cook Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Pablo Neira Ayuso, Jozsef Kadlecsik, Florian Westphal, syzbot, Yajun Deng, netdev, netfilter-devel, coreteam, Oliver Hartkopp, Harshit Mogalapalli, linux-kernel, linux-hardening On Wed, 31 Aug 2022 23:27:08 -0700 Kees Cook wrote: > This would catch corrupted values... > > Is the concern the growth in image size? The check_sub_overflow() isn't > large at all -- it's just adding a single overflow bit test. The WARNs > are heavier, but they're all out-of-line. It turns the most obvious function into a noodle bar :( Looking at this function in particular is quite useful, because it clearly indicates that the nlmsg_len includes the header. How about we throw in a WARN_ON_ONCE(nlh->nlmsg_len < NLMSG_HDRLEN || nlh->nlmsg_len > INT_MAX); but leave the actual calculation human readable C? ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] netlink: Bounds-check nlmsg_len() 2022-09-01 19:49 ` Jakub Kicinski @ 2022-09-01 20:54 ` Eric Dumazet 0 siblings, 0 replies; 8+ messages in thread From: Eric Dumazet @ 2022-09-01 20:54 UTC (permalink / raw) To: Jakub Kicinski Cc: Kees Cook, David S. Miller, Paolo Abeni, Pablo Neira Ayuso, Jozsef Kadlecsik, Florian Westphal, syzbot, Yajun Deng, netdev, netfilter-devel, coreteam, Oliver Hartkopp, Harshit Mogalapalli, LKML, linux-hardening On Thu, Sep 1, 2022 at 12:49 PM Jakub Kicinski <kuba@kernel.org> wrote: > > On Wed, 31 Aug 2022 23:27:08 -0700 Kees Cook wrote: > > This would catch corrupted values... > > > > Is the concern the growth in image size? The check_sub_overflow() isn't > > large at all -- it's just adding a single overflow bit test. The WARNs > > are heavier, but they're all out-of-line. > > It turns the most obvious function into a noodle bar :( > > Looking at this function in particular is quite useful, because > it clearly indicates that the nlmsg_len includes the header. > > How about we throw in a > > WARN_ON_ONCE(nlh->nlmsg_len < NLMSG_HDRLEN || > nlh->nlmsg_len > INT_MAX); > > but leave the actual calculation human readable C? This is inlined, and will add a lot of extra code. We are making the kernel slower at each release. What about letting fuzzers like syzbot find the potential issues ? DEBUG_NET_WARN_ON_ONCE(...); ^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/2] netlink: Bounds-check struct nlmsgerr creation 2022-09-01 3:06 [PATCH 0/2] netlink: Bounds-check struct nlmsgerr creation Kees Cook 2022-09-01 3:06 ` [PATCH 1/2] netlink: Bounds-check nlmsg_len() Kees Cook @ 2022-09-01 3:06 ` Kees Cook 2022-09-01 3:20 ` Jakub Kicinski 1 sibling, 1 reply; 8+ messages in thread From: Kees Cook @ 2022-09-01 3:06 UTC (permalink / raw) To: Jakub Kicinski Cc: Kees Cook, Pablo Neira Ayuso, Jozsef Kadlecsik, Florian Westphal, David S. Miller, Eric Dumazet, Paolo Abeni, syzbot, netfilter-devel, coreteam, netdev, Yajun Deng, Oliver Hartkopp, Harshit Mogalapalli, linux-kernel, linux-hardening For 32-bit systems, it might be possible to wrap lnmsgerr content lengths beyond SIZE_MAX. Explicitly test for all overflows, and mark the memcpy() as being unable to internally diagnose overflows. This also excludes netlink from the coming runtime bounds check on memcpy(), since it's an unusual case of open-coded sizing and allocation. Cc: Pablo Neira Ayuso <pablo@netfilter.org> Cc: Jozsef Kadlecsik <kadlec@netfilter.org> Cc: Florian Westphal <fw@strlen.de> Cc: "David S. Miller" <davem@davemloft.net> Cc: Eric Dumazet <edumazet@google.com> Cc: Jakub Kicinski <kuba@kernel.org> Cc: Paolo Abeni <pabeni@redhat.com> Cc: syzbot <syzkaller@googlegroups.com> Cc: netfilter-devel@vger.kernel.org Cc: coreteam@netfilter.org Cc: netdev@vger.kernel.org Signed-off-by: Kees Cook <keescook@chromium.org> --- net/netfilter/ipset/ip_set_core.c | 10 +++++-- net/netlink/af_netlink.c | 49 +++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index 16ae92054baa..43576f68f53d 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -1709,13 +1709,14 @@ call_ad(struct net *net, struct sock *ctnl, struct sk_buff *skb, struct nlmsghdr *rep, *nlh = nlmsg_hdr(skb); struct sk_buff *skb2; struct nlmsgerr *errmsg; - size_t payload = min(SIZE_MAX, - sizeof(*errmsg) + nlmsg_len(nlh)); + size_t payload; int min_len = nlmsg_total_size(sizeof(struct nfgenmsg)); struct nlattr *cda[IPSET_ATTR_CMD_MAX + 1]; struct nlattr *cmdattr; u32 *errline; + if (check_add_overflow(sizeof(*errmsg), nlmsg_len(nlh), &payload)) + return -ENOMEM; skb2 = nlmsg_new(payload, GFP_KERNEL); if (!skb2) return -ENOMEM; @@ -1723,7 +1724,10 @@ call_ad(struct net *net, struct sock *ctnl, struct sk_buff *skb, nlh->nlmsg_seq, NLMSG_ERROR, payload, 0); errmsg = nlmsg_data(rep); errmsg->error = ret; - memcpy(&errmsg->msg, nlh, nlh->nlmsg_len); + unsafe_memcpy(&errmsg->msg, nlh, nlh->nlmsg_len, + /* "payload" was explicitly bounds-checked, based on + * the size of nlh->nlmsg_len. + */); cmdattr = (void *)&errmsg->msg + min_len; ret = nla_parse(cda, IPSET_ATTR_CMD_MAX, cmdattr, diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index 0cd91f813a3b..8779c273f34f 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c @@ -2407,7 +2407,7 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, struct nlmsghdr *rep; struct nlmsgerr *errmsg; size_t payload = sizeof(*errmsg); - size_t tlvlen = 0; + size_t alloc_size, tlvlen = 0; struct netlink_sock *nlk = nlk_sk(NETLINK_CB(in_skb).sk); unsigned int flags = 0; bool nlk_has_extack = nlk->flags & NETLINK_F_EXT_ACK; @@ -2419,32 +2419,44 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, if (nlk_has_extack && extack && extack->_msg) tlvlen += nla_total_size(strlen(extack->_msg) + 1); - if (err && !(nlk->flags & NETLINK_F_CAP_ACK)) - payload += nlmsg_len(nlh); + if (err && !(nlk->flags & NETLINK_F_CAP_ACK) && + check_add_overflow(payload, (size_t)nlmsg_len(nlh), &payload)) + goto failure; else flags |= NLM_F_CAPPED; - if (err && nlk_has_extack && extack && extack->bad_attr) - tlvlen += nla_total_size(sizeof(u32)); - if (nlk_has_extack && extack && extack->cookie_len) - tlvlen += nla_total_size(extack->cookie_len); - if (err && nlk_has_extack && extack && extack->policy) - tlvlen += netlink_policy_dump_attr_size_estimate(extack->policy); + if (err && nlk_has_extack && extack && extack->bad_attr && + check_add_overflow(tlvlen, (size_t)nla_total_size(sizeof(u32)), + &tlvlen)) + goto failure; + if (nlk_has_extack && extack && extack->cookie_len && + check_add_overflow(tlvlen, (size_t)nla_total_size(extack->cookie_len), + &tlvlen)) + goto failure; + if (err && nlk_has_extack && extack && extack->policy && + check_add_overflow(tlvlen, + (size_t)netlink_policy_dump_attr_size_estimate(extack->policy), + &tlvlen)) + goto failure; if (tlvlen) flags |= NLM_F_ACK_TLVS; - skb = nlmsg_new(payload + tlvlen, GFP_KERNEL); - if (!skb) { - NETLINK_CB(in_skb).sk->sk_err = ENOBUFS; - sk_error_report(NETLINK_CB(in_skb).sk); - return; - } + if (check_add_overflow(payload, tlvlen, &alloc_size)) + goto failure; + + skb = nlmsg_new(alloc_size, GFP_KERNEL); + if (!skb) + goto failure; rep = __nlmsg_put(skb, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, NLMSG_ERROR, payload, flags); errmsg = nlmsg_data(rep); errmsg->error = err; - memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh)); + unsafe_memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) + ? nlh->nlmsg_len : sizeof(*nlh), + /* "payload" was bounds checked against nlh->nlmsg_len, + * and overflow-checked as tlvlen was constructed. + */); if (nlk_has_extack && extack) { if (extack->_msg) { @@ -2469,6 +2481,11 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, nlmsg_end(skb, rep); nlmsg_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).portid); + return; +failure: + NETLINK_CB(in_skb).sk->sk_err = ENOBUFS; + sk_error_report(NETLINK_CB(in_skb).sk); + } EXPORT_SYMBOL(netlink_ack); -- 2.34.1 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 2/2] netlink: Bounds-check struct nlmsgerr creation 2022-09-01 3:06 ` [PATCH 2/2] netlink: Bounds-check struct nlmsgerr creation Kees Cook @ 2022-09-01 3:20 ` Jakub Kicinski 0 siblings, 0 replies; 8+ messages in thread From: Jakub Kicinski @ 2022-09-01 3:20 UTC (permalink / raw) To: Kees Cook Cc: Pablo Neira Ayuso, Jozsef Kadlecsik, Florian Westphal, David S. Miller, Eric Dumazet, Paolo Abeni, syzbot, netfilter-devel, coreteam, netdev, Yajun Deng, Oliver Hartkopp, Harshit Mogalapalli, linux-kernel, linux-hardening On Wed, 31 Aug 2022 20:06:10 -0700 Kees Cook wrote: > For 32-bit systems, it might be possible to wrap lnmsgerr content > lengths beyond SIZE_MAX. Explicitly test for all overflows, and mark the > memcpy() as being unable to internally diagnose overflows. > > This also excludes netlink from the coming runtime bounds check on > memcpy(), since it's an unusual case of open-coded sizing and > allocation. This one you gotta rebase we just rewrote the af_netlink part last week :) ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2022-09-01 20:54 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2022-09-01 3:06 [PATCH 0/2] netlink: Bounds-check struct nlmsgerr creation Kees Cook 2022-09-01 3:06 ` [PATCH 1/2] netlink: Bounds-check nlmsg_len() Kees Cook 2022-09-01 3:18 ` Jakub Kicinski 2022-09-01 6:27 ` Kees Cook 2022-09-01 19:49 ` Jakub Kicinski 2022-09-01 20:54 ` Eric Dumazet 2022-09-01 3:06 ` [PATCH 2/2] netlink: Bounds-check struct nlmsgerr creation Kees Cook 2022-09-01 3:20 ` Jakub Kicinski
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).