On Tue, May 03, 2022 at 06:44:10PM -0700, Kees Cook wrote: > In preparation for run-time memcpy() bounds checking, split the nlmsg > copying for error messages (which crosses a previous unspecified flexible > array boundary) in half. Avoids the future run-time warning: > > memcpy: detected field-spanning write (size 32) of single field "&errmsg->msg" (size 16) > > Creates an explicit flexible array at the end of nlmsghdr for the payload, > named "nlmsg_payload". There is no impact on UAPI; the sizeof(struct > nlmsghdr) does not change, but now the compiler can better reason about > where things are being copied. > > Fixed-by: Rasmus Villemoes <linux@xxxxxxxxxxxxxxxxxx> > Link: https://lore.kernel.org/lkml/d7251d92-150b-5346-6237-52afc154bb00@xxxxxxxxxxxxxxxxxx > Cc: "David S. Miller" <davem@xxxxxxxxxxxxx> > Cc: Jakub Kicinski <kuba@xxxxxxxxxx> > Cc: Rich Felker <dalias@xxxxxxxxxx> > Cc: Eric Dumazet <edumazet@xxxxxxxxxx> > Cc: netdev@xxxxxxxxxxxxxxx > Signed-off-by: Kees Cook <keescook@xxxxxxxxxxxx> > --- > include/uapi/linux/netlink.h | 1 + > net/netlink/af_netlink.c | 5 ++++- > 2 files changed, 5 insertions(+), 1 deletion(-) > > diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h > index 855dffb4c1c3..47f9342d51bc 100644 > --- a/include/uapi/linux/netlink.h > +++ b/include/uapi/linux/netlink.h > @@ -47,6 +47,7 @@ struct nlmsghdr { > __u16 nlmsg_flags; /* Additional flags */ > __u32 nlmsg_seq; /* Sequence number */ > __u32 nlmsg_pid; /* Sending process port ID */ > + __u8 nlmsg_payload[];/* Contents of message */ > }; > > /* Flags values */ > diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c > index 1b5a9c2e1c29..09346aee1022 100644 > --- a/net/netlink/af_netlink.c > +++ b/net/netlink/af_netlink.c > @@ -2445,7 +2445,10 @@ void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err, > NLMSG_ERROR, payload, flags); > errmsg = nlmsg_data(rep); > errmsg->error = err; > - memcpy(&errmsg->msg, nlh, payload > sizeof(*errmsg) ? nlh->nlmsg_len : sizeof(*nlh)); > + errmsg->msg = *nlh; > + if (payload > sizeof(*errmsg)) > + memcpy(errmsg->msg.nlmsg_payload, nlh->nlmsg_payload, > + nlh->nlmsg_len - sizeof(*nlh)); They have nlmsg_len()[1] for the length of the payload without the header: /** * nlmsg_len - length of message payload * @nlh: netlink message header */ static inline int nlmsg_len(const struct nlmsghdr *nlh) { return nlh->nlmsg_len - NLMSG_HDRLEN; } (would that function use some sanitization, though? what if nlmsg_len is somehow manipulated to be less than NLMSG_HDRLEN?...) Also, it seems there is at least one more instance of this same issue: diff --git a/net/netfilter/ipset/ip_set_core.c b/net/netfilter/ipset/ip_set_core.c index 16ae92054baa..d06184b94af5 100644 --- a/net/netfilter/ipset/ip_set_core.c +++ b/net/netfilter/ipset/ip_set_core.c @@ -1723,7 +1723,8 @@ 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); + errmsg->msg = *nlh; + memcpy(errmsg->msg.nlmsg_payload, nlh->nlmsg_payload, nlmsg_len(nlh)); cmdattr = (void *)&errmsg->msg + min_len; ret = nla_parse(cda, IPSET_ATTR_CMD_MAX, cmdattr, -- Gustavo [1] https://elixir.bootlin.com/linux/v5.18-rc5/source/include/net/netlink.h#L577