nfsd_nl_listener_set_doit() holds nfsd_mutex across the whole listener teardown/rebuild. NFSD_A_SOCK_TRANSPORT_NAME is only checked for presence, not content, so an arbitrary name reaches svc_xprt_create_from_sa(), where a name matching no registered class triggers request_module("svc%s", name) -- a TASK_KILLABLE usermode-helper upcall run under nfsd_mutex. Vet the name against the classes NFSD can instantiate (tcp, udp, rdma) in nfsd_nl_validate_listeners(), which runs before nfsd_mutex is taken. This narrows the upcall rather than removing it. "rdma" is accepted unconditionally, so on a kernel where svcrdma is not built it still reaches request_module("svcrdma") under nfsd_mutex -- as it must for the modular case, where autoloading is legitimate. Assisted-by: LLM Link: https://syzkaller.appspot.com/bug?extid=c7eae0eb80858a2dba0f Signed-off-by: Jeff Layton --- fs/nfsd/nfsctl.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 4e5e083d8477..e5844d8454b8 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1975,14 +1975,31 @@ int nfsd_nl_version_get_doit(struct sk_buff *skb, struct genl_info *info) return err; } +/* + * Transport classes NFSD knows how to instantiate. Vetting the name here + * keeps a bogus string from reaching svc_xprt_create_from_sa(), where an + * unknown name triggers a request_module("svc%s", name) upcall under + * nfsd_mutex. + */ +static bool nfsd_nl_transport_supported(const char *name) +{ + static const char * const supported[] = { "tcp", "udp", "rdma" }; + int i; + + for (i = 0; i < ARRAY_SIZE(supported); i++) + if (!strcmp(name, supported[i])) + return true; + return false; +} + /** * 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 + * a supported 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. @@ -2006,6 +2023,9 @@ static int nfsd_nl_validate_listeners(struct genl_info *info) if (!tb[NFSD_A_SOCK_ADDR] || !tb[NFSD_A_SOCK_TRANSPORT_NAME]) return -EINVAL; + if (!nfsd_nl_transport_supported(nla_data(tb[NFSD_A_SOCK_TRANSPORT_NAME]))) + return -EPROTONOSUPPORT; + sa = nla_data(tb[NFSD_A_SOCK_ADDR]); if (nla_len(tb[NFSD_A_SOCK_ADDR]) < sizeof(sa->sa_family)) return -EINVAL; -- 2.55.0