FAILED: patch "[PATCH] nfsd: validate sockaddr length per family in listener_set" failed to apply to 6.12-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.12-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.12.y
git checkout FETCH_HEAD
git cherry-pick -x bdcc85c2b05a9378d8bd2d65f9fc41440a3cf464
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090327-bully-unsightly-dc1a@gregkh' --subject-prefix 'PATCH 6.12.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From bdcc85c2b05a9378d8bd2d65f9fc41440a3cf464 Mon Sep 17 00:00:00 2001
From: Jeff Layton <[email protected]>
Date: Mon, 15 Jun 2026 14:31:25 -0400
Subject: [PATCH] nfsd: validate sockaddr length per family in listener_set
nfsd_sock_nl_policy declares NFSD_A_SOCK_ADDR as a bare NLA_BINARY attribute with no minimum length. A CAP_NET_ADMIN caller can send a 16-byte NFSD_A_SOCK_ADDR with sa_family=AF_INET6, causing a 12-byte OOB read across three consumers (rpc_cmp_addr_port, svc_find_listener, kernel_bind).
nfsd_nl_listener_set_doit() also parsed and validated each listener entry inline in two separate loops, interleaved with mutating the running listener configuration. The validation was duplicated, used an open-coded "nla_len < sizeof(struct sockaddr)" check that was too short for AF_INET6, and handled a malformed entry inconsistently depending on which loop noticed it.
Add an nfsd_nl_validate_listeners() helper that walks the entire list once and confirms each entry parses, carries both an address and a transport name, and is long enough for its address family (sizeof(struct sockaddr_in) for AF_INET, sizeof(struct sockaddr_in6) for AF_INET6, -EAFNOSUPPORT otherwise). Call it before taking nfsd_mutex or creating the serv, so a malformed request fails cleanly with no side effects.
Since every entry is known valid by the time the two existing loops run, drop the redundant presence and per-family length checks from both, leaving only the nla_parse_nested() call needed to extract the data.
fs/nfsd/nfsctl.c +65 -12
@@ -1971,6 +1971,60 @@ int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info)return err; } +/** +* nfsd_nl_validate_listeners - sanity-check the listener list from userland +* @info: netlink metadata and command arguments +* +* Walk every NFSD_A_SERVER_SOCK_ADDR attribute and confirm that each entry +* is well-formed: it parses against the policy, carries both an address and +* a transport name, and the address is long enough for its family. Doing +* this up front lets the callers below assume every entry is valid and +* guarantees we make no changes when the request is malformed. +* +* Return: 0 if every entry is valid, or a negative errno otherwise. +*/ +static int nfsd_nl_validate_listeners (struct genl_info *info) +{ +const struct nlattr *attr; +int rem; + +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_SOCK_ADDR , info->nlhdr, +GENL_HDRLEN , rem) { +struct nlattr *tb[NFSD_A_SOCK_MAX +1 ]; +struct sockaddr *sa; +int err; + + err =nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, + nfsd_sock_nl_policy, info->extack); +if (err <0 ) +return err; + +if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) +return -EINVAL ; + + sa =nla_data (tb[NFSD_A_SOCK_ADDR ]); +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (sa->sa_family)) +return -EINVAL ; + +switch (sa->sa_family) { +case AF_INET : +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) < +sizeof (struct sockaddr_in )) +return -EINVAL ; +break ; +case AF_INET6 : +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) < +sizeof (struct sockaddr_in6 )) +return -EINVAL ; +break ; +default : +return -EAFNOSUPPORT ; + } + } + +return 0 ; +} +/** * nfsd_nl_listener_set_doit - set the nfs running sockets * @skb: reply buffer
@@ -1989,6 +2043,15 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)bool delete = false;int err, rem; +/* +* Validate the entire listener list before making any changes, so a +* malformed request fails cleanly without creating a serv or touching +* the existing listeners. +*/ + err =nfsd_nl_validate_listeners (info); +if (err) +return err; +mutex_lock (&nfsd_mutex); err =nfsd_create_serv (net);
@@ -2015,16 +2078,11 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)const char *xcl_name;struct sockaddr *sa; +/* validated up front in nfsd_nl_validate_listeners() */ if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ; -if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) -continue ; - -if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (*sa)) -continue ; - xcl_name =nla_data (tb[NFSD_A_SOCK_TRANSPORT_NAME ]); sa =nla_data (tb[NFSD_A_SOCK_ADDR ]);
@@ -2076,16 +2134,11 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)struct sockaddr *sa;int ret; +/* validated up front in nfsd_nl_validate_listeners() */ if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ; -if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) -continue ; - -if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (*sa)) -continue ; - xcl_name =nla_data (tb[NFSD_A_SOCK_TRANSPORT_NAME ]); sa =nla_data (tb[NFSD_A_SOCK_ADDR ]);
Sasha Levin
maintainerPATCH 1/2 netlink: introduce type-checking attribute iteration for nlmsg
From: Carolina Jubran <[email protected]>
[ Upstream commit 42401c42389622424f2973ec57414f033ae6be8f ]
Add the nlmsg_for_each_attr_type() macro to simplify iteration over attributes of a specific type in a Netlink message.
Convert existing users in vxlan and nfsd to use the new macro.
Stable-dep-of: bdcc85c2b05a ("nfsd: validate sockaddr length per family in listener_set")
--- drivers/net/vxlan/vxlan_vnifilter.c | 13 ++++------- fs/nfsd/nfsctl.c | 36 +++++++++++------------------ include/net/netlink.h | 14 +++++++++++ 3 files changed, 32 insertions(+), 31 deletions(-)
drivers/net/vxlan/vxlan_vnifilter.c +4 -9
@@ -980,15 +980,10 @@ static int vxlan_vnifilter_process(struct sk_buff *skb, struct nlmsghdr *nlh,if (!(vxlan->cfg.flags &VXLAN_F_VNIFILTER ))return -EOPNOTSUPP ; -nlmsg_for_each_attr (attr, nlh,sizeof (*tmsg), rem) { -switch (nla_type (attr)) { -case VXLAN_VNIFILTER_ENTRY : - err =vxlan_process_vni_filter (vxlan, attr, - nlh->nlmsg_type, extack); -break ; -default : -continue ; - } +nlmsg_for_each_attr_type (attr,VXLAN_VNIFILTER_ENTRY , nlh, +sizeof (*tmsg), rem) { + err =vxlan_process_vni_filter (vxlan, attr, nlh->nlmsg_type, + extack); vnis++;if (err)break ;
fs/nfsd/nfsctl.c +14 -22
@@ -1679,10 +1679,9 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info)return -EINVAL ;/* count number of SERVER_THREADS values */ -nlmsg_for_each_attr (attr, info->nlhdr,GENL_HDRLEN , rem) { -if (nla_type (attr) ==NFSD_A_SERVER_THREADS ) - nrpools++; - } +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_THREADS , info->nlhdr, +GENL_HDRLEN , rem) + nrpools++;mutex_lock (&nfsd_mutex);
@@ -1693,12 +1692,11 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info) } i =0 ; -nlmsg_for_each_attr (attr, info->nlhdr,GENL_HDRLEN , rem) { -if (nla_type (attr) ==NFSD_A_SERVER_THREADS ) { - nthreads[i++] =nla_get_u32 (attr); -if (i >= nrpools) -break ; - } +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_THREADS , info->nlhdr, +GENL_HDRLEN , rem) { + nthreads[i++] =nla_get_u32 (attr); +if (i >= nrpools) +break ; } if (info->attrs[NFSD_A_SERVER_GRACETIME] ||
@@ -1839,14 +1837,12 @@ int nfsd_nl_version_set_doit(struct sk_buff *skb, struct genl_info *info)for (i =0 ; i <=NFSD_SUPPORTED_MINOR_VERSION ; i++)nfsd_minorversion (nn, i,NFSD_CLEAR ); -nlmsg_for_each_attr (attr, info->nlhdr,GENL_HDRLEN , rem) { +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_PROTO_VERSION , info->nlhdr, +GENL_HDRLEN , rem) {struct nlattr *tb[NFSD_A_VERSION_MAX +1 ];u32 major, minor =0 ;bool enabled; -if (nla_type (attr) !=NFSD_A_SERVER_PROTO_VERSION ) -continue ; -if (nla_parse_nested (tb,NFSD_A_VERSION_MAX , attr, nfsd_version_nl_policy, info->extack) <0 )continue ;
@@ -1997,14 +1993,12 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)* Walk the list of server_socks from userland and move any that match * back to sv_permsocks */ -nlmsg_for_each_attr (attr, info->nlhdr,GENL_HDRLEN , rem) { +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_SOCK_ADDR , info->nlhdr, +GENL_HDRLEN , rem) {struct nlattr *tb[NFSD_A_SOCK_MAX +1 ];const char *xcl_name;struct sockaddr *sa; -if (nla_type (attr) !=NFSD_A_SERVER_SOCK_ADDR ) -continue ; -if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ;
@@ -2059,15 +2053,13 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)svc_xprt_destroy_all (serv, net);/* walk list of addrs again, open any that still don't exist */ -nlmsg_for_each_attr (attr, info->nlhdr,GENL_HDRLEN , rem) { +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_SOCK_ADDR , info->nlhdr, +GENL_HDRLEN , rem) {struct nlattr *tb[NFSD_A_SOCK_MAX +1 ];const char *xcl_name;struct sockaddr *sa;int ret; -if (nla_type (attr) !=NFSD_A_SERVER_SOCK_ADDR ) -continue ; -if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ;
include/net/netlink.h +14 -0
@@ -68,6 +68,8 @@* nlmsg_for_each_msg() loop over all messages * nlmsg_validate() validate netlink message incl. attrs * nlmsg_for_each_attr() loop over all attributes +* nlmsg_for_each_attr_type() loop over all attributes with the +* given type * * Misc: * nlmsg_report() report back to application?
@@ -940,6 +942,18 @@ static inline u32 nlmsg_seq(const struct nlmsghdr *nlh)nla_for_each_attr (pos,nlmsg_attrdata (nlh, hdrlen), \nlmsg_attrlen (nlh, hdrlen), rem) +/** +* nlmsg_for_each_attr_type - iterate over a stream of attributes +* @pos: loop counter, set to the current attribute +* @type: required attribute type for @pos +* @nlh: netlink message header +* @hdrlen: length of the family specific header +* @rem: initialized to len, holds bytes currently remaining in stream + */ +#define nlmsg_for_each_attr_type (pos, type, nlh, hdrlen, rem) \ + nlmsg_for_each_attr(pos, nlh, hdrlen, rem) \ + if (nla_type(pos) == type) +/** * nlmsg_put - Add a new netlink message to an skb * @skb: socket buffer to store message in
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 nfsd: validate sockaddr length per family in listener_set
From: Jeff Layton <[email protected]>
[ Upstream commit bdcc85c2b05a9378d8bd2d65f9fc41440a3cf464 ]
nfsd_sock_nl_policy declares NFSD_A_SOCK_ADDR as a bare NLA_BINARY attribute with no minimum length. A CAP_NET_ADMIN caller can send a 16-byte NFSD_A_SOCK_ADDR with sa_family=AF_INET6, causing a 12-byte OOB read across three consumers (rpc_cmp_addr_port, svc_find_listener, kernel_bind).
nfsd_nl_listener_set_doit() also parsed and validated each listener entry inline in two separate loops, interleaved with mutating the running listener configuration. The validation was duplicated, used an open-coded "nla_len < sizeof(struct sockaddr)" check that was too short for AF_INET6, and handled a malformed entry inconsistently depending on which loop noticed it.
Add an nfsd_nl_validate_listeners() helper that walks the entire list once and confirms each entry parses, carries both an address and a transport name, and is long enough for its address family (sizeof(struct sockaddr_in) for AF_INET, sizeof(struct sockaddr_in6) for AF_INET6, -EAFNOSUPPORT otherwise). Call it before taking nfsd_mutex or creating the serv, so a malformed request fails cleanly with no side effects.
Since every entry is known valid by the time the two existing loops run, drop the redundant presence and per-family length checks from both, leaving only the nla_parse_nested() call needed to extract the data.
--- fs/nfsd/nfsctl.c | 77 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 12 deletions(-)
fs/nfsd/nfsctl.c +65 -12
@@ -1955,6 +1955,60 @@ int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info)return err; } +/** +* nfsd_nl_validate_listeners - sanity-check the listener list from userland +* @info: netlink metadata and command arguments +* +* Walk every NFSD_A_SERVER_SOCK_ADDR attribute and confirm that each entry +* is well-formed: it parses against the policy, carries both an address and +* a transport name, and the address is long enough for its family. Doing +* this up front lets the callers below assume every entry is valid and +* guarantees we make no changes when the request is malformed. +* +* Return: 0 if every entry is valid, or a negative errno otherwise. +*/ +static int nfsd_nl_validate_listeners (struct genl_info *info) +{ +const struct nlattr *attr; +int rem; + +nlmsg_for_each_attr_type (attr,NFSD_A_SERVER_SOCK_ADDR , info->nlhdr, +GENL_HDRLEN , rem) { +struct nlattr *tb[NFSD_A_SOCK_MAX +1 ]; +struct sockaddr *sa; +int err; + + err =nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, + nfsd_sock_nl_policy, info->extack); +if (err <0 ) +return err; + +if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) +return -EINVAL ; + + sa =nla_data (tb[NFSD_A_SOCK_ADDR ]); +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (sa->sa_family)) +return -EINVAL ; + +switch (sa->sa_family) { +case AF_INET : +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) < +sizeof (struct sockaddr_in )) +return -EINVAL ; +break ; +case AF_INET6 : +if (nla_len (tb[NFSD_A_SOCK_ADDR ]) < +sizeof (struct sockaddr_in6 )) +return -EINVAL ; +break ; +default : +return -EAFNOSUPPORT ; + } + } + +return 0 ; +} +/** * nfsd_nl_listener_set_doit - set the nfs running sockets * @skb: reply buffer
@@ -1973,6 +2027,15 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)bool delete = false;int err, rem; +/* +* Validate the entire listener list before making any changes, so a +* malformed request fails cleanly without creating a serv or touching +* the existing listeners. +*/ + err =nfsd_nl_validate_listeners (info); +if (err) +return err; +mutex_lock (&nfsd_mutex); err =nfsd_create_serv (net);
@@ -1999,16 +2062,11 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)const char *xcl_name;struct sockaddr *sa; +/* validated up front in nfsd_nl_validate_listeners() */ if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ; -if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) -continue ; - -if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (*sa)) -continue ; - xcl_name =nla_data (tb[NFSD_A_SOCK_TRANSPORT_NAME ]); sa =nla_data (tb[NFSD_A_SOCK_ADDR ]);
@@ -2060,16 +2118,11 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info)struct sockaddr *sa;int ret; +/* validated up front in nfsd_nl_validate_listeners() */ if (nla_parse_nested (tb,NFSD_A_SOCK_MAX , attr, nfsd_sock_nl_policy, info->extack) <0 )continue ; -if (!tb[NFSD_A_SOCK_ADDR ] || !tb[NFSD_A_SOCK_TRANSPORT_NAME ]) -continue ; - -if (nla_len (tb[NFSD_A_SOCK_ADDR ]) <sizeof (*sa)) -continue ; - xcl_name =nla_data (tb[NFSD_A_SOCK_TRANSPORT_NAME ]); sa =nla_data (tb[NFSD_A_SOCK_ADDR ]);
Signature · 1 line
2.53.0