From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AB459442B1B; Tue, 11 Aug 2026 12:03:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449794; cv=none; b=NoMtoE+sw13FCNFcKRp0o9dV8W8UH3BBgqz9+yF7kWHfk0TRpB+RwhkfMl1KmsaHTJ8+nEh5rqv3+bhy5rMjP9Avt48lk/364lVBX3tH4eicBBVJI6Q2EejaKK4OPNK+GNzsXcFFYYMTt6du6CXCGe3YOlf+LrJOhB+hCVEiZCc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449794; c=relaxed/simple; bh=5rSg4dAcvVQv/gwh6ajdjqTKnjmHWmRK73iIdvgeZj0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nfVp7XmIrQzNwJsmKz4mH1QRJ0eFhW4QkhTfpJ1FX8BS5xrkP9T3wEPknaGBL7RI597merr4HGtQovXQ4pVqKbjJcUlj1hP94cXcLqhRFIAsCZt1NdmFhsmHlhLf5Ji9GJkVbyGoLQ9w4UcJm0kH2YsNkEfZxKAdrqfHFq8gZOE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=a3FtI9uS; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="a3FtI9uS" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E2CE11F00A3A; Tue, 11 Aug 2026 12:03:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449793; bh=q9kotpnhc3C3OSb7dmtZK9XgIYyfRPrPIg0cQGtxKB0=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=a3FtI9uSq4+QlpfOi/sG8KFmljaR7k5HyLNSe1hBtUZfqKKr5+Wcg0/dZ3u7+Gdh+ My3I8vxK9pQuhONkAJBG8t0O8w5kQYiaAmFr15bOb03KF6yVioYDqZNPYgfb1tDj4Y RUzZCkhFGtjd2cvaAb91t+uWGL5LLsUQ2ii7jhkLUvp1IFSMPM67CzbaX+sh+j/yij +FtES12gwc1+tpSP2K/yF30l28YVH5b3UhMWsP+mV3OSidInZhsLfT30HN4p9gRP/T vWkWjmwNyAo0Cf2PwQMfledQORqLcdJ0GG7Ln9pPJf/dL+GDvtni7GSfhthZYJg3Fw qTEnmdm63K37g== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:00 -0400 Subject: [PATCH v2 1/8] NFSD: validate transport name in listener_set before serv creation Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-1-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2981; i=jlayton@kernel.org; h=from:subject:message-id; bh=5rSg4dAcvVQv/gwh6ajdjqTKnjmHWmRK73iIdvgeZj0=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew98cqbUEml+nrQcyt4CLQwFwc/dSd408L7xi hsc1X+aIcCJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfAAKCRAADmhBGVaC FQHgEACEMbOWPsNYfHKRaxjwnybKG3uRSazAlvcOsRI05qbUxbhvcV5uSLxKNyzHEwP1dSho3ld r5aOJkjj+J0er3z6SqIkA5/jDKvUVmFeqqvUlXA1dNHD5HrsHK8BoDFbaHg5N839JHgxd36xH4K 4ZmQWvrBbJPAVKztEZxx5MShzbQmmP/snuQWrlouyqsw6IL3J6YWTsMwegU1lZEhAx90CVLtgAc MFJxfs9kO5C6aiB/vuFMTWU6b+tsqFX3+ym/kvnsJTarFJm4XWfZNaPIMDrcJILoYjgrpqMdy3g INvKeGoTjeklp5lfkqnvDLPDlIGAREzH2IseaNxr3TTemqWUfq8pgPkbaTlGttCicYJMOBr+vPg 5ZLutSlw0gJHFk082YURk7Gq+9MTOU5dEl9oM0hbjmkU5knjTn9DHGbGy+xsE7wenQiLy7/U3G7 FgBqukeadKf8/dLf4R9zkpyF4vHEC1F6hN4R3T2f130qisM+nBX31ogGrYK2xLb0mTBo+/qAiIu Dl6RcPp+c6M24yK+/MkMlBGhtxP0nrr5vC6J+vB/m+YWFCs1erzt2Pv9bMt5XwGpETRTlD85PuE QHL6HFWW+LDJIGJInN9jguCqXCXEGKEuw5dqeFjjbGPkq+mCJwqaSNV6Paso/TTnBvmKHDfhp1q mA93ipKDIim3+1A== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 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=3Dc7eae0eb80858a2dba0f 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, s= truct genl_info *info) return err; } =20 +/* + * 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[] =3D { "tcp", "udp", "rdma" }; + int i; + + for (i =3D 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 userla= nd * @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 a= nd - * 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 fami= ly. + * 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_inf= o *info) if (!tb[NFSD_A_SOCK_ADDR] || !tb[NFSD_A_SOCK_TRANSPORT_NAME]) return -EINVAL; =20 + if (!nfsd_nl_transport_supported(nla_data(tb[NFSD_A_SOCK_TRANSPORT_NAME]= ))) + return -EPROTONOSUPPORT; + sa =3D nla_data(tb[NFSD_A_SOCK_ADDR]); if (nla_len(tb[NFSD_A_SOCK_ADDR]) < sizeof(sa->sa_family)) return -EINVAL; --=20 2.55.0 From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5A06E443AA1; Tue, 11 Aug 2026 12:03:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449796; cv=none; b=CfePTdWvGnzlA1VkGsRkLMWBuyu5biuHFlo41PNXYWaja76wfswWWvzblZltV8C8CXmics1zSagyHzuIhsJlnSBFeYTfKlusSATdXY3gfc63znN1k8EVxMRM3wK+0YwlriHQ/wIhm3Sw/EhO9+3zglL+0aha9tgeGIhjt7ZKiok= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449796; c=relaxed/simple; bh=sRE5xJke2nxFvarK96HksthibPjar8XUFDzJSUVh8bo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=slyDIZPFj8PL1hZ0IHLOU36rh9c/O+wM3BC1BRzr0V6dQHPH/T1Gz2eL4lRKzmrUCtScfdpJ4nO0yupFnk6h8MmpFngK0cS2zlI3guV109uAMiQoTEcnuT14xx8g5HhH6xljHcTKc1i3oix70lKwGF9Id0Rn7Z9Il+e11jlSkc4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ii1uYS8i; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ii1uYS8i" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 965871F00A3D; Tue, 11 Aug 2026 12:03:13 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449795; bh=SDK8NtQYSDFTW8YWu3lKgc5WnA2MpTS+NwrEmTlcZac=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=ii1uYS8iZMMKnwKKAC7rOhMBID4NYZ8YnT+lXTegXS2jFrAdRGNfCc8FU9NRp1AQy 78O0zUjtGHEIOyRrBkpTfvv9ONTlaWnTsHY8g2W7djYQjixim3U6xfePGrfn5hDvWQ ozV36HjDWdZi3hPtpzO2PsBvoneCYJpKpBgQZeItSigFNdIimgDupIXTtzg/Rg9VAl LIRtyB++0hvb5TIubVTH7wElIJVb2nTCBE3YCcPzhNTC474G066WoIOTdtBovOZEOQ LvtVsYY+evibV+IPHsZuS9HLMFA3lLkC6ABSUGXVmhhXx6ScNWeC1go4ZewIiR2FCU QvlpuffUMjqug== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:01 -0400 Subject: [PATCH v2 2/8] NFSD: cap the number of listeners accepted in listener_set Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-2-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2696; i=jlayton@kernel.org; h=from:subject:message-id; bh=sRE5xJke2nxFvarK96HksthibPjar8XUFDzJSUVh8bo=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew98XTQDgLMe7bvAu0fdCVA3rcTHXuGoSiv1a 5BQZ2upMoSJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfAAKCRAADmhBGVaC FetJD/9o5UVHTfjPGqJBDukxVWWrBJCaqTcKGyWQvFOg9ymf/8y4yHCwG6zTwJ2qxBWNtSV5twH /Ber1Am0zqBijyq33LV510AysbKQk2I2XKDAR/eaXIYc86+Q/Vu05iZ2RseKqgAvSt+XoyAfTUg ka6gO28ZB84v84XjcILeOlDi5eG7REO3S8j8B73RbZjVbausaj+CDr0ESY8NEVrRsb/AWHR5Nt3 oXOAcEreBp/NtD0Ad6MH5CBKLt96IWaWYy2czMCH9BzKdqRU32oyWQfqdmcM7DjKUbC8tm5J1yQ X6Fc3x+T/EACvEYUFZQ7TJnjaS6TnV8cT7RC8mA++hJDYLWyk/y9I80UAsvXjbiQebk0W9eV2Q4 kFHyJQ2wuAGhZxA4ePaozPZitzfX4fBbZ29ZRVZdzNW9i3h+OXC9PDb7aSZaXmKpz6XLybTXMX0 RKLwwwVMjHCUXsIvI/8WR+leNmlUPg0bfC6ffz5+C36tVGRmqvc/b+JSaegKfedetP0dO7CnurT 1tMDwl97dD0FhwZA5lMHOWn2s1MKmXNh1c/2HQIZo4nd1+KLmbmBZljR3UoZlxaIODDTJC8qQCR scAsGgYCln2vFyVl9bLaLaNkUoxppRgtEAagA3NPtNWeA/xBUy46lclRxlvQZhXklwi/pvEuSKk cCMkZ/Rco4GGiiw== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 nfsd_nl_listener_set_doit() matches each requested listener against the existing set in a nested loop that is O(N * M) in the requested (N) and existing (M) counts, run under sv_lock with bottom halves disabled. A userland request with a very large listener list can therefore spin in atomic context for a long time. Reject requests carrying more than NFSD_NL_LISTENER_MAX (1024) entries in nfsd_nl_validate_listeners(), before any lock is taken. The limit is far above any realistic configuration. M is not capped here: write_ports() can add listeners too, via svc_addsock() and svc_xprt_create(). But each one costs a real socket, so M is bounded by resources, where N was bounded only by the message size. Capping N leaves ~1M iterations plus 1024 nla_parse_nested() calls under sv_lock as the worst case. Assisted-by: LLM Signed-off-by: Jeff Layton --- fs/nfsd/nfsctl.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index e5844d8454b8..66931caaaaed 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1992,21 +1992,22 @@ static bool nfsd_nl_transport_supported(const char = *name) return false; } =20 +/* Upper bound on the number of listeners a single request may carry. */ +#define NFSD_NL_LISTENER_MAX 1024 + /** * nfsd_nl_validate_listeners - sanity-check the listener list from userla= nd * @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 a= nd - * a supported transport name, and the address is long enough for its fami= ly. - * Doing this up front lets the callers below assume every entry is valid = and - * guarantees we make no changes when the request is malformed. + * Walk every NFSD_A_SERVER_SOCK_ADDR attribute and confirm that the list = is + * not oversized and that each entry is well-formed. * * 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; + unsigned int count =3D 0; int rem; =20 nlmsg_for_each_attr_type(attr, NFSD_A_SERVER_SOCK_ADDR, info->nlhdr, @@ -2015,6 +2016,11 @@ static int nfsd_nl_validate_listeners(struct genl_in= fo *info) struct sockaddr *sa; int err; =20 + if (++count > NFSD_NL_LISTENER_MAX) { + NL_SET_ERR_MSG(info->extack, "too many listeners"); + return -E2BIG; + } + err =3D nla_parse_nested(tb, NFSD_A_SOCK_MAX, attr, nfsd_sock_nl_policy, info->extack); if (err < 0) --=20 2.55.0 From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2669A44471C; Tue, 11 Aug 2026 12:03:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449798; cv=none; b=rHxhB3zmWfGSqsvvr/iGKE5yPaEMyAM0PcUxUPu7V+vsf0T31cyblau4BdQ4oxRA/eGQkt8zuc8PQHWcFFFuZDYvJF1uqVobYcIBshKwRyRGvl5N/2p/3/bMkwvIjEt+Qmm2QMtTw4tElB0CVuYa4Pj8kr1RvcmltNhkeA2LgPE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449798; c=relaxed/simple; bh=hbOURPfJqPVwoJhhbzaRk2KzuYTolYn6nyinjlg5viw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=KCEQxVEG8tyRc4VDhtIW0sMab5m6Y+1RxMJ1M+dlgNzrO446sGCv9nW4uP6t4WXGqhx9Cjs0X746QPfpkGaBDU91aoSROwOsMWGTDHh5VBMTjTu6V/aX5blpn1sElzbIKPJGAFS7pByK7/NS44DDXJtBzckVgTgfrVQSiEw6Qis= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Ce807X2N; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Ce807X2N" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 49F4D1F000E9; Tue, 11 Aug 2026 12:03:15 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449796; bh=giRg3v0pHMXaip1txilgypEjmxgscmaD8D7b9UsCyzE=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=Ce807X2N6UcWkl6Iwr+itUP1xlJNFcBnKl1EhVJ2S72cRTGRdMy/AwSKmyOgCWvKn wXytWNAk8ANXDF3V8TJwD8kikjQ0GDthmfT07g61fC8fqUyVsb7Pk7+jyuW22nEQc8 EQNVCk9ArYQabfcyLfhqSfOhjzUHm4pnmf4IrjsXufzVUSpucFDXKbyf2N7Sb0dH27 Q0ykuZmYNPTsfGyaKODtrEFz1AvnBFfCewrCtSnYd0OvF8mnBYuWjGJ7xCA47Ost0t ieC9KVuiQxMUEEyrmlGX1VNnYTSFGYcypJIa1Oe29g/5blQQ8bQIgDvH1bhlnH9scQ nRm+IESLH8Raw== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:02 -0400 Subject: [PATCH v2 3/8] SUNRPC: keep the first error in svc_register() Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-3-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2034; i=jlayton@kernel.org; h=from:subject:message-id; bh=hbOURPfJqPVwoJhhbzaRk2KzuYTolYn6nyinjlg5viw=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew98uSFjuFNXWwX2SklT0RZyXL0fESjGcS4+Y YKB51Iq7HuJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfAAKCRAADmhBGVaC FdhkD/9/fBOwC2Dyzill6zeGzDm3DfNDuxBEA7Evo8q5t+8cPsQ90ijcLr4BgoTLZiqSsZam63F biWAChKtIDbz6QphuT6Ux/KNH88ynTGnTIfrhMXk0ypQzdw8n4z3PY1uaXWDrN3Hpm5ZGod2Uhc 3rzv8Td9BYiLtSfCWkWd3YhNlEVrphngK+/ahK16OG0idJo4sOBXCsou11b2vBTzFnpF+LZywRV gA4ClIFRrX0lIX37GDziMd4eLput3BUIAA3IaJLEyx1vmBcWs4ldOwYchg7prj2bE2y+csVHcji 31yXlBsrnoIWvKuy0ti42T9bhA//pDf/ZNN4/gOMZehSmxWaHG9IWDpS5yRxzPms973N51x5PyT 4Mg3z8WuUzEQe2HF6l7iUUSCNBdG3uXRIwJNXe8GpAcoD3Rf18EGLQ6RZ13zC5kAx+8fZYoVLxF UmA4wRy2r16j3/UcwqaoRcNqY2UphpGf3sc3jo4t2VIuPAEXz+ZwAMwJ1/RsiUdVwcf9ClrHV20 5ACYmzlKtUk1TCQjkI2i+zmbGouJd9fgnkxYlMOUAYcedKm1hu8ZnhBLqgReJa6vCFVHzOrBOBQ VyFpegY1Ta867TJdKl4lamUHYXJtxHbBBxqoGgvOdHz7ZUzlb6ZQBDJigl3loRr4v5R7dCJxmiW HVMoU9XjbMh749w== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 svc_register() assigns each pg_rpcbind_set() result to the same "error" and returns the last one. The break only leaves the version loop, so any program after a failed one overwrites its error. Keep the first error instead of the last. This is a flag day for CONFIG_NFS_LOCALIO=3Dy, which converges on the CONFIG_NFS_LOCALIO=3Dn behaviour (the Kconfig default): - NFSv4 is unaffected: nfsd_version4 sets vs_rpcb_optnl, so svc_generic_rpcbind_set() returns 0 for it however __svc_register() went. - nfsd_version3 does not set it, and nfsd_net_init() enables every supported version, so a v3-enabled server with no reachable rpcbind now fails to bring up any listener. svc_bind() does not catch that earlier: rpcb_create_local() falls through to rpcb_create_local_net(), which passes RPC_CLNT_CREATE_NOPING and returns 0 with nothing listening. - A partial failure (nfsd registered, nfsacl not) tears the listener down but leaves the nfsd entry in rpcbind until the next svc_rpcb_setup() clears it. Fixes: 642ee6b209c2 ("SUNRPC: Allow further customisation of RPC program re= gistration") Assisted-by: LLM Signed-off-by: Jeff Layton --- net/sunrpc/svc.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 8297bad2b177..4f402bbf97ba 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -1208,13 +1208,16 @@ int svc_register(const struct svc_serv *serv, struc= t net *net, struct svc_program *progp =3D &serv->sv_programs[p]; =20 for (i =3D 0; i < progp->pg_nvers; i++) { + int ret; =20 - error =3D progp->pg_rpcbind_set(net, progp, i, + ret =3D progp->pg_rpcbind_set(net, progp, i, family, proto, port); - if (error < 0) { + if (ret < 0) { printk(KERN_WARNING "svc: failed to register " "%sv%u RPC service (errno %d).\n", - progp->pg_name, i, -error); + progp->pg_name, i, -ret); + if (!error) + error =3D ret; break; } } --=20 2.55.0 From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE659445AD8; Tue, 11 Aug 2026 12:03:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449800; cv=none; b=t+zMI8FFvOr/uXpGVgzewh4BA+PSFiyIm1RCnxtVUmO0NskHjkpkvwxlxv5DHqCKYnwCGrZLEsAEFu08dwGz0sGArW6brYB8lcO+1CvDV2a4HFuUIH3Sy8ns7ovAttEjc1/tlVePAmueAQI8IMb8dNCzGBobB++ah7GtL3re3zk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449800; c=relaxed/simple; bh=zDvMd0/cF3eAxO6o93Lh5KASDEwmgY2G8v3OOvtVWeQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qYZzpoRvtdoI10NB/KiUmCIcXoDTuQjkaaL6DisbszuLR7AXnxbbpNe2NveZ/QAguTS6GG3CsEXfMnT+svKBz0wTnapQHOgxI90k6hBAYBextA4dANOrLjxLKnn5XQtjd2iNLFVE9ECUh/1NPWhifmYh01WYdOPNIM163Li5QkM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ESmMghcd; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ESmMghcd" Received: by smtp.kernel.org (Postfix) with ESMTPSA id F24FD1F00A3E; Tue, 11 Aug 2026 12:03:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449798; bh=DOEfXMILkJ6R9NhbSPOTiA08U6fkuoNTet7R9sVrP2c=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=ESmMghcdKSshFjugD6qeLxdro008fq696MMG/eG8T67ZlT3Zp9SNek3drHb1ErtO8 iSUU6IOumrS4eOOAh5wm1/LPAFmM+Ah9RLlsw6W1z+p2Ek9p7ncpvnZMwrXC7fr5it AwoDH1bmKDPJW4ErAqvzED5XkaUf8Vi8VNLlJQkHTX1kYpYTAizwOVSMb84LBp48F7 wB5dlq2fIeffyCPWoTIqqJFcAJBGes6Gp7fbdYrwxOYxOC2TlWdbeX3y2J5bkavTMu fzpr3k2W/B4RPHlkQXXHGzE8xs5Bw+OwVDPQSJiaZAFkK6WvQ4THvJrkOP3KC7jVp9 2J0R0En2qYk2g== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:03 -0400 Subject: [PATCH v2 4/8] SUNRPC: undo partial rpcbind registrations when svc_register() fails Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-4-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=3049; i=jlayton@kernel.org; h=from:subject:message-id; bh=zDvMd0/cF3eAxO6o93Lh5KASDEwmgY2G8v3OOvtVWeQ=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew98bGlJv+eJ5TSRvY6rSytjS1VVTBsWGv9DK QVnvVGFOMiJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfAAKCRAADmhBGVaC FTzaEADHOhLoijQXb9De3bztd4I6DKODesrlicRtQx+mxUMdWUFhnxBnVGLf5eR7gBsqlbYE4kE ddKyRkyo0VRDjlZwjeB6Y75UPAKaM49Wo6NqClzKjqWhFUejT7dWl9C4MM//Kcl/ymjDWJXmwFU CNK+KbmySc+eru3lmcxegOJdfdbdGXTq8twOvLUS9JYJE2jecD+4p08J/FGQXoL4Rq+Bm8e6kV6 DvPygjJBuCFCKLuQin8j7t38clv7qKL1VSygJZD/eqyrgkkr6IpcO4NNT4FDk2RUXUljJPXl2vM lMyTl0DxKvWDFzbUHMdG2453sktTsyZu6SQWTQQxIm/Jqi21eIeNFEYvcabfamSa95CwcJ1UcDv SEw18MK2e8PEbzHE67ik318v0YukLlQKJqyfVkeb17fyXzS6UQjxJh5jIL359KLJSREarGtZYfZ XlemDoz5dy2eNIlCo1g8FNXHELLhiimqScVgL0W68jcqk6dx4oTqt3sgtJ/f3hjR7EDdch9kcp2 hobNT3aB61LnliUfKmFU+a4C7zwH4r2qmMc292DHMyTR0/QDUZCwQ2Qwk6VtUSZqE/UkmtF6JRZ K/MDFT5C5pce/Fc8q2xWY6q0Eg6WNwvT7th2/4e43rQjxZbJTL6XZuE9nw3yWgMHAw/GbUEybpc bXGfCivG46OOZgA== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 svc_register() registers each [program, version] in turn. On failure the caller tears the listener down -- svc_setup_socket() frees the svc_sock and svc_create_socket() releases the socket -- but the entries that were already set stay in rpcbind, now pointing at a closed port. XPT_RPCB_UNREG is set later, in svc_udp_init()/svc_tcp_init(), so svc_delete_xprt() never runs for this transport and nothing clears them. Unwind on failure, and stop the walk there rather than registering the programs after it. rpcbind matches RPCBPROC_UNSET on [program, version, netid] and ignores the address, so the unwind clears the netid, not just the port -- the same granularity svc_delete_xprt() already unregisters at. Assisted-by: LLM Signed-off-by: Jeff Layton --- net/sunrpc/svc.c | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 4f402bbf97ba..9ffa87007004 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -1183,6 +1183,29 @@ int svc_generic_rpcbind_set(struct net *net, } EXPORT_SYMBOL_GPL(svc_generic_rpcbind_set); =20 +/* + * Undo the [program, version] registrations that this svc_register() call + * already made, stopping at version @nvers of program @nprog. + * + * Note that rpcbind matches RPCBPROC_UNSET on [program, version, netid] a= nd + * ignores the address, so this clears the netid rather than the one port. + * That is the granularity svc_delete_xprt() unregisters at as well. + */ +static void svc_unwind_register(const struct svc_serv *serv, struct net *n= et, + const int family, const unsigned short proto, + unsigned int nprog, unsigned int nvers) +{ + unsigned int p, i; + + for (p =3D 0; p <=3D nprog; p++) { + struct svc_program *progp =3D &serv->sv_programs[p]; + unsigned int last =3D p < nprog ? progp->pg_nvers : nvers; + + for (i =3D 0; i < last; i++) + progp->pg_rpcbind_set(net, progp, i, family, proto, 0); + } +} + /** * svc_register - register an RPC service with the local portmapper * @serv: svc_serv struct for the service to register @@ -1191,7 +1214,8 @@ EXPORT_SYMBOL_GPL(svc_generic_rpcbind_set); * @proto: transport protocol number to advertise * @port: port to advertise * - * Service is registered for any address in the passed-in protocol family + * Service is registered for any address in the passed-in protocol family. + * A @port of 0 unregisters instead, and then every program is attempted. */ int svc_register(const struct svc_serv *serv, struct net *net, const int family, const unsigned short proto, @@ -1216,6 +1240,11 @@ int svc_register(const struct svc_serv *serv, struct= net *net, printk(KERN_WARNING "svc: failed to register " "%sv%u RPC service (errno %d).\n", progp->pg_name, i, -ret); + if (port) { + svc_unwind_register(serv, net, family, + proto, p, i); + return ret; + } if (!error) error =3D ret; break; --=20 2.55.0 From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 693CF44605E; Tue, 11 Aug 2026 12:03:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449801; cv=none; b=DIdon5bmI91KVzCxCsG1mUzrhX+nrWPi0gCE50k6eNe2l7yYUAlSWi/iWFNKjUUsyb6ijt/ZoynfYbsw/uCwyQzHFx1TkjSwapqqeYND9gJXKqpdwslCi9SVlkt+QuOInPzekhtpiipKI28DPNXCELHpNCeEUsrRYXN0EU2jZX4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449801; c=relaxed/simple; bh=5UtxjO3qrVonu/LnLd63d6RqCZxjq8jKUKG6jtN+3XA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hjLr6weZZVrhS+r5ORx0SialS88QqREH13ZqonwWgI/wvU7ZctHPIybnkYMU40K6PwVuyIkvN1DZPYXyL+SdQwsV/Y5zdd2R0VT0ps9qhzzmTwojHwTO8FvHwaDJadtoXZoi6r51hOsVUvmbQs7XNIhBSNNZigoEawUQs7r19mI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=J3kMuaBA; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="J3kMuaBA" Received: by smtp.kernel.org (Postfix) with ESMTPSA id A5EDB1F00A3A; Tue, 11 Aug 2026 12:03:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449800; bh=F+zP/L1S8rxKrDSj7nPm4YNqgaXbgBiW/0SHmk+Jzq4=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=J3kMuaBAq0LPWrnHwNLYhsuFjZCaU1XjQwiLZgZHrcy5pa1Y3HScheyMntejG2r0W wbnQEW0Bmkf+TqJai/aNg2QeuH2jW83yLJQdrNYiTj1SmD+Mv+op0yGINyV3Lc2uhd 8D0efkcHBsBo8Wk62G/MjN1w/TxpvRulqh44mXYZRLfsaDVLudXoxmM4EfwW33OcqA sQJ+3qyFU1sLFShWVqqd/o7ZHSRoc8g9es9ZyqKJznwNeRfEoXLLyG6KlQtzNDaala LewT6kpylbHwTgvlBhC6Tjda/ohVQ3W8bXxyqxVCL0M6Z6YBSM7y8SPVL2jRCFqcW9 81dQHXEL9EH1w== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:04 -0400 Subject: [PATCH v2 5/8] SUNRPC: bound the local rpcbind client timeout to 1s Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-5-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=2805; i=jlayton@kernel.org; h=from:subject:message-id; bh=5UtxjO3qrVonu/LnLd63d6RqCZxjq8jKUKG6jtN+3XA=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew99c/9d+RiWRAtYyDK75EqVPB4JoAe0EgcSV cnOswMcavCJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfQAKCRAADmhBGVaC FZWyD/9uoDZ6IHpu8rhivRGKdONfzeQ+him19N50xM6Gb18F3wLkElcuRCHucJ9JKpF0uUrT1QJ WLzf3Zha0El82+EcsqeauADI5qjrKNMbt5WSebX3iNtllxKfmcJ44HSOJNXZlJOWph119N/RWZ+ 620St/02UqVp841PaOCAi1EX2OGJcwm/A6pStVUI/XkchiHLDDpwDyeO/P7cPXV9asCRU91CPdp MnWpJgNzC6vO7dAYeb0h32Y68YXR8j0JpxiTdd+O7qNzX/tzsiV2R0D+/FearbLK0SHIUrt2Kep ER/5h46pmy2hcpE1GO+huBmBFbVThFyiqrTsmlwh0bPM4ZzSteyPNwhitRkM4ujehE/KcY5rVy7 UkkybHBAvCFwG/Ea5Mpxl9hbZVjSfb1QX5Vxw5C0O0YXtW3sofGxhnoXQSX38ZjEzFcdj2vRnmT 3eJsc/lvB3T9HclWr2rKhr+EBS633nDtod3DeFtxas9D3bghH94mhC/CVRuH5mW24Pa7nWdfSwZ xVTGQ2jxenHNmPCBztAu5pizsqVJjpgTvOWStdUS0/PGWPpNKYg8b+CdLP1TTOURtlL8DUQGOa+ gH3ypacQGqi/JHTD9a55RT4DGWJlps7l5j7Vw0horuX9kcndVjr/zYrkPgXEwQREz410GgcacCR l7bB/XvbNHN6DYg== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 The kernel's local rpcbind client runs on the transport defaults: a 10s major timeout for AF_LOCAL, 60s for the loopback TCP fallback (xprt_calc_majortimeo() returns to_initval when to_increment is 0). Those calls are synchronous and run under nfsd_mutex, several per operation: rpcb_create_local() attempts up to three client creations, and svc_register() issues one call per program and version. A local rpcbind that accepts the connection but never replies stalls each of them, and the accumulated hold is enough to trip the hung-task watchdog on other NFSD netlink ops (the holder waits killably and evades it): INFO: task hung in nfsd_nl_cache_flush_doit The local rpcbind lives on loopback or an AF_LOCAL socket and answers in microseconds, so bound its client to one attempt, 1s. This shortens the stall rather than removing it, and it is not free. Registration stays synchronous and stays fatal: rpcb_create_local() failure aborts nfsd_create_serv() via svc_bind(), and svc_register() failure makes svc_setup_socket() fail, so a rpcbind that is merely slow to be scheduled can now fail server startup where it previously succeeded. Making the registration asynchronous is the real fix. Assisted-by: LLM Link: https://syzkaller.appspot.com/bug?extid=3Dc7eae0eb80858a2dba0f Signed-off-by: Jeff Layton --- net/sunrpc/rpcb_clnt.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/net/sunrpc/rpcb_clnt.c b/net/sunrpc/rpcb_clnt.c index 6aa372188c86..0aa376b82a52 100644 --- a/net/sunrpc/rpcb_clnt.c +++ b/net/sunrpc/rpcb_clnt.c @@ -221,6 +221,16 @@ static void rpcb_set_local(struct net *net, struct rpc= _clnt *clnt, # define SUN_LEN(ptr) (offsetof(struct sockaddr_un, sun_path) \ + 1 + strlen((ptr)->sun_path + 1)) =20 +/* + * The kernel's rpcbind client talks only to the local rpcbind, over loopb= ack + * or a local AF_LOCAL socket, where a healthy rpcbind answers in microsec= onds. + */ +static const struct rpc_timeout rpcb_local_timeout =3D { + .to_initval =3D 1 * HZ, + .to_maxval =3D 1 * HZ, + .to_retries =3D 0, +}; + /* * Returns zero on success, otherwise a negative errno value * is returned. @@ -238,6 +248,7 @@ static int rpcb_create_af_local(struct net *net, .version =3D RPCBVERS_2, .authflavor =3D RPC_AUTH_NULL, .cred =3D current_cred(), + .timeout =3D &rpcb_local_timeout, /* * We turn off the idle timeout to prevent the kernel * from automatically disconnecting the socket. @@ -312,6 +323,7 @@ static int rpcb_create_local_net(struct net *net) .version =3D RPCBVERS_2, .authflavor =3D RPC_AUTH_UNIX, .cred =3D current_cred(), + .timeout =3D &rpcb_local_timeout, .flags =3D RPC_CLNT_CREATE_NOPING, }; struct rpc_clnt *clnt, *clnt4; --=20 2.55.0 From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2FD3B446842; Tue, 11 Aug 2026 12:03:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449803; cv=none; b=Wvf8l1HpY+ntV0FXqlQ48UwvtqNjQuhEMRHl2es8r2tH8WSCckOmbcD/0moxWGYCLNDlYLttU9gyifanzDYtCWmQU3Gfw/J4giID96GU7i2YqdmVJBc5FXruo+DfR8I9P1PtKupe/xEZQxnbvb7/yWLDhJIatsM1n/jqymZfnvY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449803; c=relaxed/simple; bh=0VKi9tlUU92VzYlet7gMRmJ6FUukTGB2mM1mmIAcrXA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=GzsSUUOvpQ5K6yGrMtggci+qepUoazm89l/AyvXIpm4OQedyXRCjcAVkjnzp7Wbxr6IwQ2T3a1pBUlJy/CSrMe6lck1mYDJyfjuz1Gu79hrctd2+NY/AaYmY2UUeknakHn7x2aEjZKaIOdaK+q0fFhTST+8y+zbgpk4CvOfb58o= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=euugj658; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="euugj658" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 587F81F00A3D; Tue, 11 Aug 2026 12:03:20 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449801; bh=uYor/DEoMZCw61P2Q5//f+reILAzSGdrCeBsSPTIuO4=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=euugj6582Re9JBo55QnBcMMpzpKzbLGZSksX/pxHx5qjBqGtoix6FvXkexOxRLMHH goxZ3y7LrsLBX+M7Wy5P2QkqvGMrB1JE9HDiCrHDZRoexjVjDDnvoXJzVrRsHEmVJ6 T5uKu2XDDTpsycCAzNI71NjwunURSF1tk2oEKrFw+oABPRTadE/kxBurXZriRTyxjz HnbwHv7wF0s6WXLYnrZ1eoXx7hsJuc07jXqHhAadhDTCdUxAAePTOcblemGO7G12pt dPwza6PbgMWmq9K8cBuH23q7DzhgBPr5oQPyNuFs6iBlQn2zOvM7FGJLBOFBymhTNH TtkSq8BTqn+hg== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:05 -0400 Subject: [PATCH v2 6/8] NFSD: report listener creation failures through extack Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-6-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=1503; i=jlayton@kernel.org; h=from:subject:message-id; bh=0VKi9tlUU92VzYlet7gMRmJ6FUukTGB2mM1mmIAcrXA=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew99WrOCex2NssJpIwrCzNAn/LrvaOe4UXuH/ bpR1cCFhJuJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfQAKCRAADmhBGVaC FcuyD/9XjfxrYQN64BLlGnveFdPEg7rhQJV1cAADTlsGuL/k+jQwllFjq+UJEnL+Id9v5v4svuP f3KkMzYxcMUt3GDJwmUpsTrXKU9wB2EPsDGOU5QQTPnBxapaIGDZgdv3JXnks4Y4ENvaqbSuVZo Eu7B+nyeEe8giColjVTOuh4e4q+oiKd+APx/bSX+E/jbaBYDRG+Flky+PH8AegRcYhfSEVDMEeO iQSs/R2NC1e8xZFr28vMwU9/RiQokJHlSKK+9pBwkILMC8EqVLKmCONQD1EsIRFIcCjmdRYvleQ Z4vZ8GR3YU0sHOlC6QjANuk3XOIVcxkN6R9RlX40b/hHnzM7bP6xUnNyE3qxolqhKDTxXbWqFDc 3V6ZXTg54HFhLLNp+KAK7Kn3irA5n0EEJ4Q9bzInj2YwtvsKpDq2+fxo7hwJKeUeMGhV4/5rOWH fi0ZQa3dwlZWvqonZufF5zlonh+NwwgnoFBKjUQr4AgfnTHoVx8haFYPqwBTcpcvfR1IKE/rv2a qZEUUem9miYu9d934yNwIUqw9VKoOFJnRVKnAKC3SNM/2TRLcv5CDxK8fojnWBANACIuwZ3vcit pUithyBWp+vIrjw2p+GyTmnot0Lk6Gj0m9zh+4h99zWdBTmkW1kg+l+/Hww1/+ozIHWsqXbpq/I tOOiywytK4rGFOA== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 nfsd_nl_listener_set_doit() hands back the raw errno from svc_xprt_create_from_sa() and sets no extack, so a failed LISTENER_SET tells userland only "Connection refused". The usual cause is svc_setup_socket() -> svc_register() failing because the local rpcbind is not reachable, which is not guessable from the errno alone -- and since "SUNRPC: keep the first error in svc_register()" that failure is fatal on CONFIG_NFS_LOCALIO=3Dy too. Name the transport and the error. The message tracks err, which keeps the last failure, so a multi-listener request reports the entry whose errno is returned. The rejections in nfsd_nl_validate_listeners() other than -E2BIG are still bare; those are left alone here. Assisted-by: LLM Signed-off-by: Jeff Layton --- fs/nfsd/nfsctl.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 66931caaaaed..a6ef85e6b419 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2184,8 +2184,12 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, s= truct genl_info *info) ret =3D svc_xprt_create_from_sa(serv, xcl_name, net, sa, 0, current_cred()); /* always save the latest error */ - if (ret < 0) + if (ret < 0) { + NL_SET_ERR_MSG_FMT(info->extack, + "cannot create %s listener: %d", + xcl_name, ret); err =3D ret; + } } =20 if (!serv->sv_nrthreads && list_empty(&nn->nfsd_serv->sv_permsocks)) --=20 2.55.0 From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D73B1446C1E; Tue, 11 Aug 2026 12:03:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449805; cv=none; b=Xw2TpqxsBWPJze+2xWmko3mhZKPyq3pxbs7c+9c543zKMTeAXjoTyUaCtai7oR27qesOdcUhQUsvYog7LGPH16i81FYAQt4/2DAuljIP1qP44YtJ5lJCRp7i9gPvMa3nqPU6KLoHYlATsOqbJX2dMcc+xyHDEsxK05AEVf3K7mE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449805; c=relaxed/simple; bh=A7fBnGniU0UsFSMF1FDZXA2ljp97Zj/39X0PUFllHDM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UOwC2brb5XL5bvBJWcXYGboCGMl8rmMtz2hIXYemuVwBCqYw7l3AioxHGhQ0Q8vb3JhjpIpWgryzr73ZrZZVGuFu0+ZqCRgLlXGGjCYY5cN+bbolSkoGkdkYRMYXm77NqC/88zEhQESsF9r14nDdO44xEcoBiVQPaFFABlJqPiY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=epiKkAHB; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="epiKkAHB" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 0B2E41F00A3F; Tue, 11 Aug 2026 12:03:21 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449803; bh=XYHQVIZ+dSGy9kiK7EmuYJzEhSYbpVFgfLR8OZJOA54=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=epiKkAHBLn/3AcFF90FjzJrl/Zvxn0bzlVN9eFItthEddP5TqVVKDHT396qQ1uhym cedp6arC+0LJqSrlN9zmfSCA8v156lDIAjTijexZ1oxvq+yfnInWccSEpANyi3IMb8 klNaQghw4fx8nsN0nT5CAV/RZ4+BqoHcXH/QGEBE73cJ33X/b84OBn0M/d8fTvTiEf h/YwEPzVobj6NEK4jTq0FlMlgRXJVBBV6gczWSqITDh9aPzq41VsUx84tPN/CuAnXY QNUkQYzMX9OJloYOUtkHikxJWFP9WPFfvQtq5YAQ0AdwLljNgljAb4KtOqi8ewYEk/ P7MC0rNC5Z/pA== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:06 -0400 Subject: [PATCH v2 7/8] selftests/nfsd: exercise listener_set request validation Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-7-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=18535; i=jlayton@kernel.org; h=from:subject:message-id; bh=A7fBnGniU0UsFSMF1FDZXA2ljp97Zj/39X0PUFllHDM=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew99QE3R/W8o9YFVisgkfqTf8aZmJikcHpFhf C3xf7AeuzSJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfQAKCRAADmhBGVaC FUkzEACh/SLm8h28EumD9z/i5lhXApa/zP8n2FaxUVnva3zbwTQ9IAILbVq5Uymy0H1IcpcPAbo 8+6hTx9Og6f8Awf9tgRXy7hsDOHXNKvu6GaahoGX7tbptYMBrbIW9p2x5B0rs+ta5QYYaZT1rYp PKlF7ZGmXr6isf/FcdZkYpnxmaslERMLzeALJzjnS2EWEyzDx7APnT43JabHRbw9KYSNC1YlAc3 0gUmNCQA3a2yiJGA0XmDnqEv4dx6xv6p6TW5SKEroKcRIxvSY660tX7J5m/PBV/7kKA6SfKTmcN nOVz4O73p1FElA+eDh7yzAwjLiae9ii+l7zyCiFhEflnOTpeXurO5LS864BWbuNJi0z/ZD1USUK ZuZb3PV8vy24aEYjyDjLn01rDhlbYuAx9ZojbtKS0827sGo9V0l9lwMPa6GjDCzueao4H41qTLz Sas6c/E26NZjKTh0Bscgq5f0+0Uz1j7LDYTEpy/JdraFX1IFhxwkWmdq8JNMCzZ7smdX8ezjWeT YdB5AfQqn5QbBqP2fY2nGlSIIat8jYkJ+hDCwWqI67cQyLDO42Xn/VnWRhsblFAHWP/MDXmGGK3 Ms+6jcMdJ06Ah0F8ivGWSWLcu2gERVcHa5VswyHh2CTw1b2mJKzWvPTDlMrjh7Y+gMM7H+9fxtQ Gllw9Aj7JDCf1DA== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 Regression tests for the NFSD_CMD_LISTENER_SET checks that nfsd_nl_validate_listeners() runs before nfsd_mutex is taken: bad or absent transport name, missing address, truncated or unsupported sockaddr, bad address family, a malformed entry behind a well-formed one, and more than NFSD_NL_LISTENER_MAX entries. Plus a LISTENER_GET against an empty netns. None of these reach nfsd_create_serv(), so nothing here creates a serv or registers with rpcbind. Tests that do need one come next, with a stub. Uses kselftest_harness.h so each test runs in its own net+mount namespace. /run is masked there: unix_find_bsd() resolves by inode and takes no struct net, so a "/var/run/rpcbind.sock" connect from this netns would otherwise reach the rpcbind on the host -- and svc_rpcb_setup() opens by calling svc_unregister(), which would clear the host's nfsd registrations. The config fragment therefore has to cover the fixture as well as nfsd: NAMESPACES and NET_NS for the unshare(), SHMEM and TMPFS for the mask, and UNIX for the AF_LOCAL rpcbind client. Without them every test skips. Assisted-by: LLM Signed-off-by: Jeff Layton --- tools/testing/selftests/Makefile | 1 + tools/testing/selftests/nfsd/.gitignore | 1 + tools/testing/selftests/nfsd/Makefile | 6 + tools/testing/selftests/nfsd/config | 8 + .../testing/selftests/nfsd/nfsd_netlink_listener.c | 488 +++++++++++++++++= ++++ tools/testing/selftests/nfsd/settings | 1 + 6 files changed, 505 insertions(+) diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Mak= efile index 8d4db2241cc2..5d615301d368 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -85,6 +85,7 @@ TARGETS +=3D net/packetdrill TARGETS +=3D net/ppp TARGETS +=3D net/rds TARGETS +=3D net/tcp_ao +TARGETS +=3D nfsd TARGETS +=3D nolibc TARGETS +=3D pci_endpoint TARGETS +=3D pcie_bwctrl diff --git a/tools/testing/selftests/nfsd/.gitignore b/tools/testing/selfte= sts/nfsd/.gitignore new file mode 100644 index 000000000000..19e6dec04d8e --- /dev/null +++ b/tools/testing/selftests/nfsd/.gitignore @@ -0,0 +1 @@ +nfsd_netlink_listener diff --git a/tools/testing/selftests/nfsd/Makefile b/tools/testing/selftest= s/nfsd/Makefile new file mode 100644 index 000000000000..15ac65549d25 --- /dev/null +++ b/tools/testing/selftests/nfsd/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS +=3D $(KHDR_INCLUDES) -Wall + +TEST_GEN_PROGS :=3D nfsd_netlink_listener + +include ../lib.mk diff --git a/tools/testing/selftests/nfsd/config b/tools/testing/selftests/= nfsd/config new file mode 100644 index 000000000000..ab84523fbedf --- /dev/null +++ b/tools/testing/selftests/nfsd/config @@ -0,0 +1,8 @@ +CONFIG_NAMESPACES=3Dy +CONFIG_NET_NS=3Dy +CONFIG_SHMEM=3Dy +CONFIG_TMPFS=3Dy +CONFIG_UNIX=3Dy +CONFIG_IPV6=3Dy +CONFIG_NFSD=3Dy +CONFIG_NFSD_V4=3Dy diff --git a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c b/tools/t= esting/selftests/nfsd/nfsd_netlink_listener.c new file mode 100644 index 000000000000..ae28c224255f --- /dev/null +++ b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c @@ -0,0 +1,488 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Regression tests for the NFSD generic-netlink listener interface + * (NFSD_CMD_LISTENER_SET / NFSD_CMD_LISTENER_GET). + * + * These cover the request validation that nfsd_nl_validate_listeners() do= es + * before nfsd_mutex is taken: bad or absent transport name, missing addre= ss, + * truncated or unsupported sockaddr, oversized list. None of them reach + * nfsd_create_serv(), so nothing here creates a serv or talks to rpcbind. + * + * Each test runs in its own private net + mount namespace (unshare in + * FIXTURE_SETUP). /run is masked there: a pathname AF_LOCAL connect is not + * scoped by the network namespace, since unix_find_bsd() resolves by inode + * and takes no struct net, so the kernel's rpcbind client would otherwise= be + * able to reach the rpcbind running on the host. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +/* NFSD generic-netlink constants (from linux/nfsd_netlink.h). */ +#define NFSD_FAMILY_NAME "nfsd" +#define NFSD_CMD_LISTENER_SET 6 +#define NFSD_CMD_LISTENER_GET 7 +#define NFSD_A_SERVER_SOCK_ADDR 1 /* per-listener nest */ +#define NFSD_A_SOCK_ADDR 1 /* inside the nest */ +#define NFSD_A_SOCK_TRANSPORT_NAME 2 /* inside the nest */ + +#define NLA_ALIGN4(len) (((len) + 3) & ~3) +#define TEST_PORT 20049 +#define MAX_LISTENERS 8 +#define RECV_TIMEO_SEC 30 + +static int nfsd_family; /* set per-test in FIXTURE_SETUP */ + +static void die(const char *msg) +{ + perror(msg); + exit(1); +} + +/* ------------------- minimal generic-netlink plumbing ------------------= - */ + +static int genl_open(void) +{ + struct sockaddr_nl sa =3D { .nl_family =3D AF_NETLINK }; + struct timeval tv =3D { .tv_sec =3D RECV_TIMEO_SEC }; + int fd =3D socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); + + if (fd < 0) + die("socket(NETLINK_GENERIC)"); + if (bind(fd, (void *)&sa, sizeof(sa)) < 0) + die("bind(netlink)"); + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + return fd; +} + +/* Append an attribute at @off; return the new (aligned) offset. */ +static int put_attr(char *buf, int off, uint16_t type, + const void *data, int len) +{ + struct nlattr *na =3D (void *)(buf + off); + + na->nla_type =3D type; + na->nla_len =3D NLA_HDRLEN + len; + if (len) + memcpy(buf + off + NLA_HDRLEN, data, len); + return off + NLA_ALIGN4(NLA_HDRLEN + len); +} + +/* Build a genl message header into @buf; return the offset past it. */ +static int genl_hdr(char *buf, uint16_t type, uint16_t flags, uint8_t cmd) +{ + struct nlmsghdr *nlh =3D (void *)buf; + struct genlmsghdr *gnl =3D (void *)(buf + NLMSG_HDRLEN); + + memset(buf, 0, NLMSG_HDRLEN + GENL_HDRLEN); + nlh->nlmsg_type =3D type; + nlh->nlmsg_flags =3D flags; + nlh->nlmsg_seq =3D 1; + gnl->cmd =3D cmd; + gnl->version =3D 1; + return NLMSG_HDRLEN + GENL_HDRLEN; +} + +/* Send an nfsd command with an ACK; return the ACK errno (<=3D 0). */ +static int genl_request(uint8_t cmd, const char *attrs, int attrs_len) +{ + char buf[1 << 20], rbuf[4096]; + struct nlmsghdr *nlh =3D (void *)buf; + int fd =3D genl_open(); + int off, n, ret; + + off =3D genl_hdr(buf, nfsd_family, NLM_F_REQUEST | NLM_F_ACK, cmd); + if (attrs_len) { + memcpy(buf + off, attrs, attrs_len); + off +=3D attrs_len; + } + nlh->nlmsg_len =3D off; + + if (send(fd, buf, off, 0) < 0) + die("send(genl)"); + + n =3D recv(fd, rbuf, sizeof(rbuf), 0); + if (n < 0) + ret =3D (errno =3D=3D EAGAIN || errno =3D=3D EWOULDBLOCK) ? -ETIMEDOUT := -errno; + else if (((struct nlmsghdr *)rbuf)->nlmsg_type =3D=3D NLMSG_ERROR) + ret =3D ((struct nlmsgerr *)NLMSG_DATA(rbuf))->error; + else + ret =3D 0; + close(fd); + return ret; +} + +/* Send a command and return the full reply message; -errno on failure. */ +static int genl_request_reply(uint8_t cmd, char *rbuf, size_t rlen) +{ + char buf[256]; + struct nlmsghdr *nlh =3D (void *)buf; + int fd =3D genl_open(); + int off, n, ret; + + off =3D genl_hdr(buf, nfsd_family, NLM_F_REQUEST, cmd); + nlh->nlmsg_len =3D off; + + if (send(fd, buf, off, 0) < 0) + die("send(genl reply)"); + + n =3D recv(fd, rbuf, rlen, 0); + if (n < 0) + ret =3D (errno =3D=3D EAGAIN || errno =3D=3D EWOULDBLOCK) ? -ETIMEDOUT := -errno; + else if (((struct nlmsghdr *)rbuf)->nlmsg_type =3D=3D NLMSG_ERROR) + ret =3D ((struct nlmsgerr *)NLMSG_DATA(rbuf))->error; + else + ret =3D n; + close(fd); + return ret; +} + +/* Resolve the "nfsd" genl family id; -1 if not registered. */ +static int genl_resolve_nfsd(void) +{ + char buf[1024], rbuf[4096]; + struct nlmsghdr *nlh =3D (void *)buf; + struct nlmsghdr *rh =3D (void *)rbuf; + struct nlattr *na; + int fd, off, left, id =3D -1; + + fd =3D genl_open(); + off =3D genl_hdr(buf, GENL_ID_CTRL, NLM_F_REQUEST, CTRL_CMD_GETFAMILY); + off =3D put_attr(buf, off, CTRL_ATTR_FAMILY_NAME, + NFSD_FAMILY_NAME, sizeof(NFSD_FAMILY_NAME)); + nlh->nlmsg_len =3D off; + + if (send(fd, buf, off, 0) < 0) + die("send(GETFAMILY)"); + if (recv(fd, rbuf, sizeof(rbuf), 0) < 0) + die("recv(GETFAMILY)"); + close(fd); + + if (rh->nlmsg_type =3D=3D NLMSG_ERROR) + return -1; + + na =3D (void *)((char *)NLMSG_DATA(rh) + GENL_HDRLEN); + left =3D rh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + while (left >=3D (int)NLA_HDRLEN) { + if (na->nla_type =3D=3D CTRL_ATTR_FAMILY_ID) { + id =3D *(uint16_t *)((char *)na + NLA_HDRLEN); + break; + } + left -=3D NLA_ALIGN4(na->nla_len); + na =3D (void *)((char *)na + NLA_ALIGN4(na->nla_len)); + } + return id; +} + +/* ------------------- listener request builders ------------------- */ + +/* Fine-grained control for negative tests: any field can be omitted/malfo= rmed. */ +struct raw_listener { + const char *xprt; /* NULL -> omit NFSD_A_SOCK_TRANSPORT_NAME */ + int emit_addr; /* 0 -> omit NFSD_A_SOCK_ADDR */ + const void *addr; + int addr_len; /* bytes to emit for NFSD_A_SOCK_ADDR */ +}; + +static int put_raw_listener(char *buf, int off, const struct raw_listener = *r) +{ + struct nlattr *nest =3D (void *)(buf + off); + int inner =3D off + NLA_HDRLEN; + + if (r->emit_addr) + inner =3D put_attr(buf, inner, NFSD_A_SOCK_ADDR, r->addr, r->addr_len); + if (r->xprt) + inner =3D put_attr(buf, inner, NFSD_A_SOCK_TRANSPORT_NAME, + r->xprt, strlen(r->xprt) + 1); + nest->nla_type =3D NFSD_A_SERVER_SOCK_ADDR | NLA_F_NESTED; + nest->nla_len =3D inner - off; + return off + NLA_ALIGN4(nest->nla_len); +} + +/* Well-formed loopback listener for @family (AF_INET or AF_INET6). */ +static int put_listener_af(char *buf, int off, const char *xprt, int famil= y, + uint16_t port) +{ + struct sockaddr_storage ss =3D {0}; + struct raw_listener r =3D { .xprt =3D xprt, .emit_addr =3D 1, .addr =3D &= ss }; + + if (family =3D=3D AF_INET6) { + struct sockaddr_in6 *s6 =3D (void *)&ss; + + s6->sin6_family =3D AF_INET6; + s6->sin6_port =3D htons(port); + s6->sin6_addr =3D in6addr_loopback; + r.addr_len =3D sizeof(*s6); + } else { + struct sockaddr_in *s4 =3D (void *)&ss; + + s4->sin_family =3D AF_INET; + s4->sin_port =3D htons(port); + s4->sin_addr.s_addr =3D htonl(INADDR_LOOPBACK); + r.addr_len =3D sizeof(*s4); + } + return put_raw_listener(buf, off, &r); +} + +static int put_listener(char *buf, int off, const char *xprt, uint16_t por= t) +{ + return put_listener_af(buf, off, xprt, AF_INET, port); +} + +/* ------------------- LISTENER_GET parsing ------------------- */ + +struct listener_ent { + char xprt[16]; + int family; + uint16_t port; + struct in_addr a4; + struct in6_addr a6; +}; + +static int parse_listener_get(const char *rbuf, int len, + struct listener_ent *out, int max) +{ + const struct nlmsghdr *nlh =3D (const void *)rbuf; + const struct nlattr *na; + int left, count =3D 0; + + (void)len; + na =3D (const void *)(rbuf + NLMSG_HDRLEN + GENL_HDRLEN); + left =3D nlh->nlmsg_len - NLMSG_HDRLEN - GENL_HDRLEN; + + while (left >=3D (int)NLA_HDRLEN) { + int alen =3D na->nla_len; + + if ((na->nla_type & NLA_TYPE_MASK) =3D=3D NFSD_A_SERVER_SOCK_ADDR && + count < max) { + const struct nlattr *in =3D (const void *)((char *)na + NLA_HDRLEN); + int ileft =3D alen - NLA_HDRLEN; + struct listener_ent *e =3D &out[count]; + + memset(e, 0, sizeof(*e)); + while (ileft >=3D (int)NLA_HDRLEN) { + const void *d =3D (const char *)in + NLA_HDRLEN; + int t =3D in->nla_type & NLA_TYPE_MASK; + + if (t =3D=3D NFSD_A_SOCK_TRANSPORT_NAME) { + strncpy(e->xprt, d, sizeof(e->xprt) - 1); + } else if (t =3D=3D NFSD_A_SOCK_ADDR) { + const struct sockaddr_storage *ss =3D d; + + e->family =3D ss->ss_family; + if (ss->ss_family =3D=3D AF_INET) { + const struct sockaddr_in *s =3D d; + + e->a4 =3D s->sin_addr; + e->port =3D ntohs(s->sin_port); + } else if (ss->ss_family =3D=3D AF_INET6) { + const struct sockaddr_in6 *s =3D d; + + e->a6 =3D s->sin6_addr; + e->port =3D ntohs(s->sin6_port); + } + } + ileft -=3D NLA_ALIGN4(in->nla_len); + in =3D (const void *)((char *)in + NLA_ALIGN4(in->nla_len)); + } + count++; + } + left -=3D NLA_ALIGN4(alen); + na =3D (const void *)((char *)na + NLA_ALIGN4(alen)); + } + return count; +} + +/* ------------------- convenience wrappers ------------------- */ + +static int listener_set(const char *attrs, int len) +{ + return genl_request(NFSD_CMD_LISTENER_SET, attrs, len); +} + +/* Fetch the current listeners; returns count (>=3D0) or -errno. */ +static int listener_get(struct listener_ent *out, int max) +{ + char rbuf[8192]; + int n =3D genl_request_reply(NFSD_CMD_LISTENER_GET, rbuf, sizeof(rbuf)); + + if (n < 0) + return n; + return parse_listener_get(rbuf, n, out, max); +} + +/* --------------------------- fixture --------------------------- */ + +FIXTURE(nfsd_listener) { + int placeholder; +}; + +FIXTURE_SETUP(nfsd_listener) +{ + struct ifreq ifr =3D {0}; + struct stat st; + int s; + + if (geteuid() !=3D 0) + SKIP(return, "must be run as root"); + if (unshare(CLONE_NEWNET | CLONE_NEWNS) < 0) + SKIP(return, "unshare(NEWNET|NEWNS): %s", strerror(errno)); + if (mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) + SKIP(return, "mount(/ private): %s", strerror(errno)); + + /* + * Keep the kernel's rpcbind client inside this namespace. The + * abstract socket it tries first is per-netns, but the + * "/var/run/rpcbind.sock" fallback is not, so hide the path. + */ + if (mount("tmpfs", "/run", "tmpfs", 0, NULL) < 0) + SKIP(return, "mount(tmpfs on /run): %s", strerror(errno)); + if (lstat("/var/run", &st) =3D=3D 0 && S_ISDIR(st.st_mode) && + mount("tmpfs", "/var/run", "tmpfs", 0, NULL) < 0) + SKIP(return, "mount(tmpfs on /var/run): %s", strerror(errno)); + + /* Bring loopback up so listener binds (127.0.0.1 / ::1) work. */ + s =3D socket(AF_INET, SOCK_DGRAM, 0); + ASSERT_GE(s, 0); + strcpy(ifr.ifr_name, "lo"); + ASSERT_EQ(0, ioctl(s, SIOCGIFFLAGS, &ifr)); + ifr.ifr_flags |=3D IFF_UP | IFF_RUNNING; + ASSERT_EQ(0, ioctl(s, SIOCSIFFLAGS, &ifr)); + close(s); + + nfsd_family =3D genl_resolve_nfsd(); + if (nfsd_family < 0) + SKIP(return, "nfsd genl family not found (modprobe nfsd?)"); +} + +FIXTURE_TEARDOWN(nfsd_listener) +{ +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D validat= ion / negative =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D */ + +TEST_F(nfsd_listener, val_too_many) +{ + static char attrs[1 << 20]; + int i, off =3D 0; + + for (i =3D 0; i < 1025; i++) /* > NFSD_NL_LISTENER_MAX (1024) */ + off =3D put_listener(attrs, off, "udp", TEST_PORT); + EXPECT_EQ(-E2BIG, listener_set(attrs, off)); +} + +TEST_F(nfsd_listener, val_missing_addr) +{ + char attrs[64]; + struct raw_listener r =3D { .xprt =3D "tcp", .emit_addr =3D 0 }; + int off =3D put_raw_listener(attrs, 0, &r); + + EXPECT_EQ(-EINVAL, listener_set(attrs, off)); +} + +TEST_F(nfsd_listener, val_missing_transport) +{ + struct sockaddr_in s4 =3D { .sin_family =3D AF_INET, .sin_port =3D htons(= TEST_PORT) }; + struct raw_listener r =3D { .xprt =3D NULL, .emit_addr =3D 1, + .addr =3D &s4, .addr_len =3D sizeof(s4) }; + char attrs[64]; + int off =3D put_raw_listener(attrs, 0, &r); + + EXPECT_EQ(-EINVAL, listener_set(attrs, off)); +} + +/* + * A name matching no transport class must be refused before nfsd_mutex is + * taken, so it never reaches svc_xprt_create_from_sa() and its + * request_module("svc%s", name) upcall. + */ +TEST_F(nfsd_listener, val_bad_transport) +{ + char attrs[64]; + int off =3D put_listener(attrs, 0, "bogus_xprt", TEST_PORT); + + EXPECT_EQ(-EPROTONOSUPPORT, listener_set(attrs, off)); +} + +TEST_F(nfsd_listener, val_addr_too_short) +{ + unsigned char tiny =3D 0; + struct raw_listener r =3D { .xprt =3D "tcp", .emit_addr =3D 1, + .addr =3D &tiny, .addr_len =3D 1 }; + char attrs[64]; + int off =3D put_raw_listener(attrs, 0, &r); + + EXPECT_EQ(-EINVAL, listener_set(attrs, off)); +} + +TEST_F(nfsd_listener, val_inet_short) +{ + struct sockaddr_in s4 =3D { .sin_family =3D AF_INET, .sin_port =3D htons(= TEST_PORT) }; + struct raw_listener r =3D { .xprt =3D "tcp", .emit_addr =3D 1, .addr =3D = &s4, + .addr_len =3D sizeof(sa_family_t) + 2 }; + char attrs[64]; + int off =3D put_raw_listener(attrs, 0, &r); + + EXPECT_EQ(-EINVAL, listener_set(attrs, off)); +} + +TEST_F(nfsd_listener, val_inet6_short) +{ + struct sockaddr_in6 s6 =3D { .sin6_family =3D AF_INET6, .sin6_port =3D ht= ons(TEST_PORT) }; + struct raw_listener r =3D { .xprt =3D "tcp", .emit_addr =3D 1, .addr =3D = &s6, + .addr_len =3D sizeof(struct sockaddr_in) }; + char attrs[64]; + int off =3D put_raw_listener(attrs, 0, &r); + + EXPECT_EQ(-EINVAL, listener_set(attrs, off)); +} + +TEST_F(nfsd_listener, val_bad_family) +{ + struct sockaddr_storage ss =3D { .ss_family =3D AF_UNIX }; + struct raw_listener r =3D { .xprt =3D "tcp", .emit_addr =3D 1, .addr =3D = &ss, + .addr_len =3D sizeof(struct sockaddr_in) }; + char attrs[64]; + int off =3D put_raw_listener(attrs, 0, &r); + + EXPECT_EQ(-EAFNOSUPPORT, listener_set(attrs, off)); +} + +TEST_F(nfsd_listener, val_second_entry_bad) +{ + struct sockaddr_storage ss =3D { .ss_family =3D AF_UNIX }; + struct raw_listener bad =3D { .xprt =3D "tcp", .emit_addr =3D 1, .addr = =3D &ss, + .addr_len =3D sizeof(struct sockaddr_in) }; + char attrs[128]; + int off =3D put_listener(attrs, 0, "tcp", TEST_PORT); + + off =3D put_raw_listener(attrs, off, &bad); + /* The whole request is rejected during validation; nothing applied. */ + EXPECT_EQ(-EAFNOSUPPORT, listener_set(attrs, off)); +} + +/* LISTENER_GET with no serv in this netns returns an empty list. */ +TEST_F(nfsd_listener, func_get_empty) +{ + struct listener_ent got[MAX_LISTENERS]; + + EXPECT_EQ(0, listener_get(got, MAX_LISTENERS)); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/nfsd/settings b/tools/testing/selftest= s/nfsd/settings new file mode 100644 index 000000000000..6091b45d226b --- /dev/null +++ b/tools/testing/selftests/nfsd/settings @@ -0,0 +1 @@ +timeout=3D120 --=20 2.55.0 From nobody Tue Sep 29 06:59:03 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5B735443A96; Tue, 11 Aug 2026 12:03:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449807; cv=none; b=tjASioFYLUqLxElTCH8AHUJq3inF0Jt1UnfqikIppqbJ6O3Q9vyKFp0NcZPXp/79/l4cE0M4JAJmHcMKGYw8Iq4n2r1Rcteo/Ee7zp1atbckeQmAtJcCPI3O2EYtE7lA1tzl70NiP1pwqnjnH3Zz/f/sbpMVdw4/OXsFgPh/hWg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786449807; c=relaxed/simple; bh=xvBya8FG3skq1IEmsPu7E0m53t45XPtowboiGRyEis8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=W2hKG3+Iw4bVBGXxiEWkrYOR26zJVPVA9VLyi/304CxNn4P8WuSeJSh+V/1lqN0+bAM9vH3LqKnATrYMNw9e7abvYJjqBbLmLwQi89YLqz4aRxOFy1KwIdDdC1J5zeElC54CXTBat22Gy3k2CFHpg1G3n9wf6XBagjJZ5IIjPyo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Gi0XcxAG; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Gi0XcxAG" Received: by smtp.kernel.org (Postfix) with ESMTPSA id C3D571F000E9; Tue, 11 Aug 2026 12:03:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1786449805; bh=DeWZZXxEYFj0P/FV/30bfEsa0ScDekugz8g2m6TThEo=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=Gi0XcxAGg+OvtJwZNSiK//Sn7EILpffmtwkDQK4eySh28t6uN6v3Sf5nTjtvgnz8W 8y4bH/fWDSmg6oiNvrqCjDRJyTjoZ2sEcV4d0A949D/ugDl03qd8dzXGBjooZsh9JL TERumcSFHOH6mW81HsXVwEIq2odyZQCjaAfcchvavdhdQwHFyuV3+Q2Y+we0BuGG1R CgJuS8oh+SwfbdgklbkfnWN/D7BSQH9ny5xKpxG6RyHQRtZb9tBSAJd9dAFZt/mt7w GwG7ORWyeWqK6VS/fyHonbydFC64WdJ6ECoeXS33fXPVfurNojqub+eCGrKs/Bu899 QL/xOZ/jzcskA== From: Jeff Layton Date: Tue, 11 Aug 2026 08:03:07 -0400 Subject: [PATCH v2 8/8] selftests/nfsd: add a per-netns rpcbind stub and the listener round-trips Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260811-nfsd-nl-hang-v2-8-c0c92b3953c3@kernel.org> References: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> In-Reply-To: <20260811-nfsd-nl-hang-v2-0-c0c92b3953c3@kernel.org> To: Chuck Lever , NeilBrown , Olga Kornievskaia , Dai Ngo , Tom Talpey , Trond Myklebust , Anna Schumaker , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , "J. Bruce Fields" , Shuah Khan Cc: linux-nfs@vger.kernel.org, linux-kernel@vger.kernel.org, netdev@vger.kernel.org, Trond Myklebust , linux-kselftest@vger.kernel.org, Jeff Layton X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=openpgp-sha256; l=24810; i=jlayton@kernel.org; h=from:subject:message-id; bh=xvBya8FG3skq1IEmsPu7E0m53t45XPtowboiGRyEis8=; b=owEBbQKS/ZANAwAKAQAOaEEZVoIVAcsmYgBqew99coLpgP1PdHx+TNnv/Aah36BwhPKUhxvLL uMju4C7n/SJAjMEAAEKAB0WIQRLwNeyRHGyoYTq9dMADmhBGVaCFQUCansPfQAKCRAADmhBGVaC FdfpD/47n/wRCKyo5cFAhLz6ret7xNkk8oJT5HpPUgPgM8IAfYHzCVJxsVLa5xAGdrBrAajIiAb k2USzy1rs7Hvt3x7pfFphKDXCok9KZq08YSIBrrf9tykaGax/B+H/1+YNSy00s1dlY47hIDdxkV AZv79KrmTwp2+GbdvtUD+Ygdh4ykytdfo9japZBBUlrDw/MetxKojJWQFUiXsF9lX9oaynfaGT8 n9FEiuM07XGUA+Vg8g3yZpEtpkkDj4SZIf/SHyyvP8ECwyKlmQaw944q5OudJXhzMfSa+Q+OrUu nGxzW8iHweSMobEL1LnVLNLpk9fObz7BBA9oXJCscajgXl5FybHAm2+sA+9vz/5L00GcUMTOR06 WBlidTqXz1FIYZjlPcAIaA815DC4O/TrcYIeNdiAiO5v/S8L6JjqCozd75Pd9CXEjUKju85z4IH D14s5ROm8+t2suK+tXJS27+Ywo5GolInPbCPQYAqOZ3KwQUbRDGUe2BMYqzcn5uVDWCMEcVzoLg 7NyfV4/RqL5IAw7IDYlbJO9KQQsB+NYJlilWDs2oVdH9omaHyS0yAb1hPy1uep55mzVonRaJcVS azM6j5WtvzrWXJZ0oBKzDnvrTLQzH2HNQpM1/Ka4q1rtFGzKVBnnjeXG2N3Jp3lrhammC+KyDCH O3lvhwa4TPmME+A== X-Developer-Key: i=jlayton@kernel.org; a=openpgp; fpr=4BC0D7B24471B2A184EAF5D3000E684119568215 Creating a listener registers with rpcbind: svc_xprt_create_from_sa() passes flags of 0, so pmap_register is true in svc_setup_socket(), and nfsd_version3 is enabled by default and does not set vs_rpcb_optnl, so a registration failure aborts listener creation. A fresh netns has no rpcbind, and reaching the host's is not an option -- svc_rpcb_setup() opens by calling svc_unregister(), which would clear the host's nfsd entries. Serve it from within the namespace instead. The abstract AF_LOCAL name the kernel tries first is per-netns (unix_find_abstract() takes a struct net), so bind "\0/run/rpcbind.sock" and fork a minimal responder: - arguments are never decoded; the NULL procedure gets an empty success and SET/UNSET get TRUE - RPCBVERS_4 is answered as well as RPCBVERS_2, because __svc_rpcb_register6() turns a v4 refusal into -EAFNOSUPPORT and that would fail every IPv6 listener - connections accepted and calls answered are counted in a page shared with the test - PR_SET_PDEATHSIG plus an explicit kill in FIXTURE_TEARDOWN, so no stub outlives its test With that in place, add the tests that need a serv: create/add/remove and LISTENER_GET round-trips (tcp, udp, multi, idempotent re-set, subset removal, empty-list serv destroy, IPv6), the empty-list request, and the -EBUSY refusal once THREADS_SET has started threads. Several of these exist to catch a revert rather than to describe the interface, since none of them is visible in the errno alone: - val_reject_keeps_listeners. An unknown transport name ends in -EPROTONOSUPPORT either way, because svc_xprt_create_from_sa() returns that too. What differs is that without the up-front check nfsd_nl_listener_set_doit() has already destroyed the listeners that did not match by the time the name fails. - val_bad_transport, extended to require that the stub saw no traffic. Reaching svc_xprt_create_from_sa() means nfsd_create_serv() ran, and svc_bind() pings rpcbind at client creation and then sweeps stale entries with svc_unregister(), so silence is what shows the request was refused up front. - val_second_entry_bad, extended with a LISTENER_GET. -EAFNOSUPPORT is also what svc_xprt_create_from_sa() returns for the bad entry, and the doit keeps the listeners it did create, so the well-formed tcp entry ahead of it would otherwise still be up. - sem_register_refused, which restarts the stub in a mode that answers RPCBPROC_SET with FALSE. rpcb_register_call() turns that into -EACCES, which must reach userland and leave no listener behind. On CONFIG_NFS_LOCALIO=3Dy it does not, unless svc_register() keeps the first error: nfslocalio is last in nfsd_programs and its NULL and vs_hidden versions both report success, overwriting the failure. - func_empty_destroys uses the connection count. LISTENER_GET replies empty for a destroyed serv and for a live one with no permsocks alike, but only nfsd_destroy_serv() reaches svc_xprt_destroy_all(..., unregister=3Dtrue) = -> svc_rpcb_cleanup() -> rpcb_put_local(), which drops the last user and shuts the local client down. The next serv has to reconnect. - sem_busy_on_change and sem_busy_on_remove read the listeners back, since -EBUSY says nothing about what the doit did before it returned. find_listener() matches the address as well as the transport, family and port; every listener here is created on loopback, so a reply naming 0.0.0.0 has to fail. Assisted-by: LLM Signed-off-by: Jeff Layton --- .../testing/selftests/nfsd/nfsd_netlink_listener.c | 554 +++++++++++++++++= +++- 1 file changed, 548 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c b/tools/t= esting/selftests/nfsd/nfsd_netlink_listener.c index ae28c224255f..9972b0db45ff 100644 --- a/tools/testing/selftests/nfsd/nfsd_netlink_listener.c +++ b/tools/testing/selftests/nfsd/nfsd_netlink_listener.c @@ -3,30 +3,41 @@ * Regression tests for the NFSD generic-netlink listener interface * (NFSD_CMD_LISTENER_SET / NFSD_CMD_LISTENER_GET). * - * These cover the request validation that nfsd_nl_validate_listeners() do= es - * before nfsd_mutex is taken: bad or absent transport name, missing addre= ss, - * truncated or unsupported sockaddr, oversized list. None of them reach - * nfsd_create_serv(), so nothing here creates a serv or talks to rpcbind. + * Three groups: + * validation - malformed/abusive LISTENER_SET requests are rejected by + * nfsd_nl_validate_listeners(), before nfsd_mutex is take= n. + * functional - create/add/remove listeners and verify LISTENER_GET + * reflects the set (round-trip of transport + addr:port). + * semantics - once threads are running (THREADS_SET) a listener change + * is refused with -EBUSY. * * Each test runs in its own private net + mount namespace (unshare in * FIXTURE_SETUP). /run is masked there: a pathname AF_LOCAL connect is not * scoped by the network namespace, since unix_find_bsd() resolves by inode * and takes no struct net, so the kernel's rpcbind client would otherwise= be - * able to reach the rpcbind running on the host. + * able to reach the rpcbind running on the host. Anything that creates a + * serv is served by the per-netns rpcbind stub below instead. */ #define _GNU_SOURCE #include +#include #include +#include +#include #include #include #include #include #include +#include #include +#include #include #include #include #include +#include +#include #include #include #include @@ -36,8 +47,10 @@ =20 /* NFSD generic-netlink constants (from linux/nfsd_netlink.h). */ #define NFSD_FAMILY_NAME "nfsd" +#define NFSD_CMD_THREADS_SET 2 #define NFSD_CMD_LISTENER_SET 6 #define NFSD_CMD_LISTENER_GET 7 +#define NFSD_A_SERVER_THREADS 1 #define NFSD_A_SERVER_SOCK_ADDR 1 /* per-listener nest */ #define NFSD_A_SOCK_ADDR 1 /* inside the nest */ #define NFSD_A_SOCK_TRANSPORT_NAME 2 /* inside the nest */ @@ -327,10 +340,293 @@ static int listener_get(struct listener_ent *out, in= t max) return parse_listener_get(rbuf, n, out, max); } =20 +/* + * Every listener these tests create comes from put_listener_af(), so the + * address is always loopback. Match on it too: without that, a reply that + * gave the right transport and port on the wrong address (0.0.0.0, say) + * would pass. + */ +static struct listener_ent *find_listener(struct listener_ent *e, int n, + const char *xprt, int family, + uint16_t port) +{ + int i; + + for (i =3D 0; i < n; i++) { + if (e[i].family !=3D family || e[i].port !=3D port || + strcmp(e[i].xprt, xprt)) + continue; + if (family =3D=3D AF_INET6) { + if (memcmp(&e[i].a6, &in6addr_loopback, sizeof(e[i].a6))) + continue; + } else if (e[i].a4.s_addr !=3D htonl(INADDR_LOOPBACK)) { + continue; + } + return &e[i]; + } + return NULL; +} + +/* Start (@n > 0) or stop (@n =3D=3D 0) nfsd threads in this netns. */ +static int threads_set(int n) +{ + char attrs[64]; + uint32_t v =3D n; + int off =3D put_attr(attrs, 0, NFSD_A_SERVER_THREADS, &v, sizeof(v)); + + return genl_request(NFSD_CMD_THREADS_SET, attrs, off); +} + +/* ------------------- per-netns local rpcbind stub ------------------- */ + +/* + * Creating a listener registers with rpcbind: svc_xprt_create_from_sa() + * passes flags of 0, so pmap_register is true in svc_setup_socket(), and + * nfsd_version3 is registerable by default and does not set vs_rpcb_optnl, + * so a registration failure aborts listener creation. The abstract AF_LOC= AL + * name the kernel tries first is per-netns (unix_find_abstract() takes a + * struct net), so answer it here and stay out of the host's rpcbind. + * + * Arguments are never decoded. The NULL procedure gets an empty success a= nd + * SET/UNSET get TRUE, for both RPCBVERS_2 and RPCBVERS_4. v4 has to be + * answered because __svc_rpcb_register6() turns a v4 refusal into + * -EAFNOSUPPORT, which would fail every IPv6 listener. + * + * In RPCB_STUB_REFUSE mode SET is answered FALSE instead, which + * rpcb_register_call() reports as -EACCES. UNSET is left alone: only + * svc_unregister() issues it, and it discards the result. + * + * The stub also keeps counters in a page shared with the test, so a test = can + * assert that the kernel never talked to rpcbind at all, or that it dropp= ed + * the local rpcbind client and had to reconnect. + */ +#define RPCB_PROGRAM 100000 +#define RPCB_PROC_NULL 0 +#define RPCB_PROC_SET 1 +#define RPCB_PROC_UNSET 2 +#define RPCB_ABSTRACT_NAME "/run/rpcbind.sock" +#define RPCB_STUB_MAXCONN 4 + +enum { RPCB_STUB_ACCEPT, RPCB_STUB_REFUSE }; + +struct rpcb_stub_stats { + unsigned int conns; /* connections accepted */ + unsigned int calls; /* calls answered */ +}; + +static volatile struct rpcb_stub_stats *rpcb_stats; /* MAP_SHARED */ + +static int rpcb_stats_alloc(void) +{ + void *p =3D mmap(NULL, sizeof(*rpcb_stats), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + + if (p =3D=3D MAP_FAILED) + return -1; + rpcb_stats =3D p; + return 0; +} + +/* + * The stub bumps these before it replies and the kernel waits for that re= ply, + * so whatever a netlink request provoked is visible once it returns. + */ +static int rpcb_calls(void) +{ + return rpcb_stats ? (int)rpcb_stats->calls : 0; +} + +static int rpcb_conns(void) +{ + return rpcb_stats ? (int)rpcb_stats->conns : 0; +} + +static int rpcb_stub_listen(void) +{ + struct sockaddr_un sun =3D { .sun_family =3D AF_UNIX }; + size_t nlen =3D strlen(RPCB_ABSTRACT_NAME); + socklen_t alen; + int fd; + + /* Abstract names are length-delimited, so the length must match. */ + memcpy(sun.sun_path + 1, RPCB_ABSTRACT_NAME, nlen); + alen =3D offsetof(struct sockaddr_un, sun_path) + 1 + nlen; + + fd =3D socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -1; + if (bind(fd, (struct sockaddr *)&sun, alen) < 0 || + listen(fd, RPCB_STUB_MAXCONN) < 0) { + close(fd); + return -1; + } + return fd; +} + +static int rpcb_stub_read(int fd, void *buf, size_t len) +{ + size_t done =3D 0; + + while (done < len) { + ssize_t n =3D read(fd, (char *)buf + done, len - done); + + if (n <=3D 0) + return -1; + done +=3D n; + } + return 0; +} + +/* Handle one record-marked RPC call. Returns -1 when the peer is done. */ +static int rpcb_stub_call(int fd, int mode) +{ + uint32_t mark, call[6], rep[7]; + unsigned int len, nrep =3D 6; + size_t replen; + + if (rpcb_stub_read(fd, &mark, sizeof(mark))) + return -1; + len =3D ntohl(mark) & 0x7fffffff; + if (len < sizeof(call) || len > 4096) + return -1; + if (rpcb_stub_read(fd, call, sizeof(call))) + return -1; + + /* xid, msg_type, rpcvers, prog, vers, proc; the rest is discarded */ + for (len -=3D sizeof(call); len; ) { + char sink[256]; + unsigned int n =3D len > sizeof(sink) ? sizeof(sink) : len; + + if (rpcb_stub_read(fd, sink, n)) + return -1; + len -=3D n; + } + + if (rpcb_stats) + rpcb_stats->calls++; + + rep[0] =3D call[0]; /* xid */ + rep[1] =3D htonl(1); /* REPLY */ + rep[2] =3D htonl(0); /* MSG_ACCEPTED */ + rep[3] =3D htonl(0); /* verifier flavor AUTH_NULL */ + rep[4] =3D htonl(0); /* verifier length */ + rep[5] =3D htonl(0); /* SUCCESS */ + + if (ntohl(call[3]) !=3D RPCB_PROGRAM) { + rep[5] =3D htonl(1); /* PROG_UNAVAIL */ + } else { + switch (ntohl(call[5])) { + case RPCB_PROC_NULL: + break; + case RPCB_PROC_SET: + rep[6] =3D htonl(mode =3D=3D RPCB_STUB_REFUSE ? 0 : 1); + nrep =3D 7; + break; + case RPCB_PROC_UNSET: + rep[6] =3D htonl(1); /* TRUE */ + nrep =3D 7; + break; + default: + rep[5] =3D htonl(3); /* PROC_UNAVAIL */ + } + } + + replen =3D nrep * sizeof(rep[0]); + mark =3D htonl(0x80000000 | replen); + if (write(fd, &mark, sizeof(mark)) !=3D (ssize_t)sizeof(mark) || + write(fd, rep, replen) !=3D (ssize_t)replen) + return -1; + return 0; +} + +static void rpcb_stub_serve(int lfd, int mode) +{ + struct pollfd pfd[1 + RPCB_STUB_MAXCONN]; + nfds_t n =3D 1, i; + + pfd[0].fd =3D lfd; + + for (;;) { + /* stop polling the listener when full, or poll() spins */ + pfd[0].events =3D n < 1 + RPCB_STUB_MAXCONN ? POLLIN : 0; + + if (poll(pfd, n, -1) < 0) + return; + + if (pfd[0].revents & POLLIN) { + int c =3D accept(lfd, NULL, NULL); + + if (c >=3D 0) { + pfd[n].fd =3D c; + pfd[n].events =3D POLLIN; + /* + * poll() ran with the old n, so it did not + * write this revents. The loop below reads it. + */ + pfd[n].revents =3D 0; + n++; + if (rpcb_stats) + rpcb_stats->conns++; + } + } + + for (i =3D 1; i < n; i++) { + if (!(pfd[i].revents & (POLLIN | POLLHUP | POLLERR))) + continue; + if (rpcb_stub_call(pfd[i].fd, mode)) { + close(pfd[i].fd); + pfd[i] =3D pfd[--n]; + } + } + } +} + +/* Returns the stub's pid, or -1. The socket is listening before we fork. = */ +static pid_t rpcb_stub_start(int mode) +{ + int lfd =3D rpcb_stub_listen(); + pid_t pid; + + if (lfd < 0) + return -1; + + pid =3D fork(); + if (pid < 0) { + close(lfd); + return -1; + } + if (pid =3D=3D 0) { + signal(SIGPIPE, SIG_IGN); + prctl(PR_SET_PDEATHSIG, SIGKILL); + if (getppid() =3D=3D 1) /* raced with parent exit */ + _exit(0); + rpcb_stub_serve(lfd, mode); + _exit(0); + } + + close(lfd); + return pid; +} + +/* + * Swap the stub for one in @mode. Safe before the first request: no serv + * exists yet, so the kernel has not connected and the abstract name is fr= ee + * again once the old stub has been reaped. + */ +static int rpcb_stub_restart(pid_t *pid, int mode) +{ + if (*pid > 0) { + kill(*pid, SIGKILL); + waitpid(*pid, NULL, 0); + } + *pid =3D rpcb_stub_start(mode); + return *pid > 0 ? 0 : -1; +} + /* --------------------------- fixture --------------------------- */ =20 FIXTURE(nfsd_listener) { - int placeholder; + pid_t rpcbd; }; =20 FIXTURE_SETUP(nfsd_listener) @@ -369,14 +665,35 @@ FIXTURE_SETUP(nfsd_listener) nfsd_family =3D genl_resolve_nfsd(); if (nfsd_family < 0) SKIP(return, "nfsd genl family not found (modprobe nfsd?)"); + + if (rpcb_stats_alloc() < 0) + SKIP(return, "mmap(rpcbind stub counters): %s", strerror(errno)); + + self->rpcbd =3D rpcb_stub_start(RPCB_STUB_ACCEPT); + if (self->rpcbd < 0) + SKIP(return, "cannot start the rpcbind stub: %s", + strerror(errno)); } =20 FIXTURE_TEARDOWN(nfsd_listener) { + if (self->rpcbd > 0) { + kill(self->rpcbd, SIGKILL); + waitpid(self->rpcbd, NULL, 0); + } + if (rpcb_stats) { + munmap((void *)rpcb_stats, sizeof(*rpcb_stats)); + rpcb_stats =3D NULL; + } } =20 /* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D validat= ion / negative =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D */ =20 +TEST_F(nfsd_listener, val_empty_list_ok) +{ + EXPECT_EQ(0, listener_set(NULL, 0)); +} + TEST_F(nfsd_listener, val_too_many) { static char attrs[1 << 20]; @@ -411,13 +728,21 @@ TEST_F(nfsd_listener, val_missing_transport) * A name matching no transport class must be refused before nfsd_mutex is * taken, so it never reaches svc_xprt_create_from_sa() and its * request_module("svc%s", name) upcall. + * + * The errno cannot show that -- svc_xprt_create_from_sa() returns + * -EPROTONOSUPPORT for an unknown name too. The rpcbind traffic can: + * getting that far means nfsd_create_serv() ran, and svc_bind() pings + * rpcbind at client creation and then sweeps stale entries with + * svc_unregister(). A silent stub is the proof nothing was created. */ TEST_F(nfsd_listener, val_bad_transport) { char attrs[64]; int off =3D put_listener(attrs, 0, "bogus_xprt", TEST_PORT); =20 + ASSERT_EQ(0, rpcb_calls()); EXPECT_EQ(-EPROTONOSUPPORT, listener_set(attrs, off)); + EXPECT_EQ(0, rpcb_calls()); } =20 TEST_F(nfsd_listener, val_addr_too_short) @@ -469,14 +794,49 @@ TEST_F(nfsd_listener, val_second_entry_bad) struct sockaddr_storage ss =3D { .ss_family =3D AF_UNIX }; struct raw_listener bad =3D { .xprt =3D "tcp", .emit_addr =3D 1, .addr = =3D &ss, .addr_len =3D sizeof(struct sockaddr_in) }; + struct listener_ent got[MAX_LISTENERS]; char attrs[128]; int off =3D put_listener(attrs, 0, "tcp", TEST_PORT); =20 off =3D put_raw_listener(attrs, off, &bad); /* The whole request is rejected during validation; nothing applied. */ EXPECT_EQ(-EAFNOSUPPORT, listener_set(attrs, off)); + /* + * Again the errno alone does not say so: svc_xprt_create_from_sa() + * also returns -EAFNOSUPPORT, and the doit keeps the listeners it did + * manage to create, so the well-formed tcp entry ahead of the bad one + * would still be up. + */ + EXPECT_EQ(0, listener_get(got, MAX_LISTENERS)); } =20 +/* + * A rejected request must leave the listeners that are already up alone. + * The errno alone does not show that: svc_xprt_create_from_sa() returns + * -EPROTONOSUPPORT for an unknown name too. What differs is how far the + * request gets -- without the check in nfsd_nl_validate_listeners(), + * nfsd_nl_listener_set_doit() has already moved the unmatched tcp listener + * off sv_permsocks and run svc_xprt_destroy_all() on it by the time the + * name fails. + */ +TEST_F(nfsd_listener, val_reject_keeps_listeners) +{ + struct listener_ent got[MAX_LISTENERS]; + char good[64], bad[64]; + int og =3D put_listener(good, 0, "tcp", TEST_PORT); + int ob =3D put_listener(bad, 0, "bogus_xprt", TEST_PORT); + + ASSERT_EQ(0, listener_set(good, og)); + ASSERT_EQ(1, listener_get(got, MAX_LISTENERS)); + + EXPECT_EQ(-EPROTONOSUPPORT, listener_set(bad, ob)); + + ASSERT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT)); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D functio= nal / round-trip =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D */ + /* LISTENER_GET with no serv in this netns returns an empty list. */ TEST_F(nfsd_listener, func_get_empty) { @@ -485,4 +845,186 @@ TEST_F(nfsd_listener, func_get_empty) EXPECT_EQ(0, listener_get(got, MAX_LISTENERS)); } =20 +TEST_F(nfsd_listener, func_create_tcp) +{ + struct listener_ent got[MAX_LISTENERS]; + char attrs[64]; + int off =3D put_listener(attrs, 0, "tcp", TEST_PORT); + + ASSERT_EQ(0, listener_set(attrs, off)); + ASSERT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT)); +} + +TEST_F(nfsd_listener, func_create_udp) +{ + struct listener_ent got[MAX_LISTENERS]; + char attrs[64]; + int off =3D put_listener(attrs, 0, "udp", TEST_PORT); + + ASSERT_EQ(0, listener_set(attrs, off)); + ASSERT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "udp", AF_INET, TEST_PORT)); +} + +TEST_F(nfsd_listener, func_create_multi) +{ + struct listener_ent got[MAX_LISTENERS]; + char attrs[128]; + int off =3D put_listener(attrs, 0, "tcp", TEST_PORT); + + off =3D put_listener(attrs, off, "udp", TEST_PORT); + ASSERT_EQ(0, listener_set(attrs, off)); + ASSERT_EQ(2, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 2, "tcp", AF_INET, TEST_PORT)); + EXPECT_NE(NULL, find_listener(got, 2, "udp", AF_INET, TEST_PORT)); +} + +TEST_F(nfsd_listener, func_idempotent) +{ + struct listener_ent got[MAX_LISTENERS]; + char attrs[64]; + int off =3D put_listener(attrs, 0, "tcp", TEST_PORT); + + ASSERT_EQ(0, listener_set(attrs, off)); + EXPECT_EQ(0, listener_set(attrs, off)); /* re-set same list */ + ASSERT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT)); +} + +TEST_F(nfsd_listener, func_add) +{ + struct listener_ent got[MAX_LISTENERS]; + char one[64], two[128]; + int o1 =3D put_listener(one, 0, "tcp", TEST_PORT); + int o2 =3D put_listener(two, 0, "tcp", TEST_PORT); + + o2 =3D put_listener(two, o2, "udp", TEST_PORT); + ASSERT_EQ(0, listener_set(one, o1)); + ASSERT_EQ(0, listener_set(two, o2)); /* add udp, keep tcp */ + ASSERT_EQ(2, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 2, "tcp", AF_INET, TEST_PORT)); + EXPECT_NE(NULL, find_listener(got, 2, "udp", AF_INET, TEST_PORT)); +} + +TEST_F(nfsd_listener, func_remove_subset) +{ + struct listener_ent got[MAX_LISTENERS]; + char both[128], one[64]; + int ob =3D put_listener(both, 0, "tcp", TEST_PORT); + int oo =3D put_listener(one, 0, "tcp", TEST_PORT); + + ob =3D put_listener(both, ob, "udp", TEST_PORT); + ASSERT_EQ(0, listener_set(both, ob)); + ASSERT_EQ(0, listener_set(one, oo)); /* drop udp */ + ASSERT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT)); +} + +/* + * LISTENER_GET cannot tell a destroyed serv from a live one with no + * permsocks: nfsd_nl_listener_get_doit() replies empty either way. The + * rpcbind client can. nfsd_destroy_serv() is the only path that reaches + * svc_xprt_destroy_all(..., unregister=3Dtrue) -> svc_rpcb_cleanup() -> + * rpcb_put_local(), which drops the last user and shuts the local client + * down; the next serv then has to connect again. Leaving the serv in place + * would keep the first connection and the stub would see just the one. + */ +TEST_F(nfsd_listener, func_empty_destroys) +{ + struct listener_ent got[MAX_LISTENERS]; + char attrs[64]; + int off =3D put_listener(attrs, 0, "tcp", TEST_PORT); + int conns; + + ASSERT_EQ(0, listener_set(attrs, off)); + conns =3D rpcb_conns(); + ASSERT_GT(conns, 0); + + EXPECT_EQ(0, listener_set(NULL, 0)); /* empty -> destroy serv */ + EXPECT_EQ(0, listener_get(got, MAX_LISTENERS)); + + ASSERT_EQ(0, listener_set(attrs, off)); + EXPECT_GT(rpcb_conns(), conns); +} + +TEST_F(nfsd_listener, func_ipv6) +{ + struct listener_ent got[MAX_LISTENERS]; + char attrs[64]; + int off, s; + + s =3D socket(AF_INET6, SOCK_STREAM, 0); + if (s < 0) + SKIP(return, "IPv6 unavailable: %s", strerror(errno)); + close(s); + + off =3D put_listener_af(attrs, 0, "tcp", AF_INET6, TEST_PORT); + ASSERT_EQ(0, listener_set(attrs, off)); + ASSERT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET6, TEST_PORT)); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D rpcbind= registration =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D */ + +/* + * A rpcbind that refuses the registration must fail listener creation, + * whatever CONFIG_NFS_LOCALIO is set to. + * + * The error has to survive svc_register()'s walk over sv_programs to get + * here. With CONFIG_NFS_LOCALIO=3Dy the trailing nfslocalio program has o= nly + * a NULL and a vs_hidden version, and svc_generic_rpcbind_set() reports 0 + * for both, so an svc_register() that keeps the last result rather than t= he + * first hands back success and the listener comes up regardless. + */ +TEST_F(nfsd_listener, sem_register_refused) +{ + struct listener_ent got[MAX_LISTENERS]; + char attrs[64]; + int off =3D put_listener(attrs, 0, "tcp", TEST_PORT); + + ASSERT_EQ(0, rpcb_stub_restart(&self->rpcbd, RPCB_STUB_REFUSE)); + + EXPECT_EQ(-EACCES, listener_set(attrs, off)); + EXPECT_EQ(0, listener_get(got, MAX_LISTENERS)); +} + +/* =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D threads= / -EBUSY semantics =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D */ + +TEST_F(nfsd_listener, sem_busy_on_change) +{ + struct listener_ent got[MAX_LISTENERS]; + char one[64], two[128]; + int o1 =3D put_listener(one, 0, "tcp", TEST_PORT); + int o2 =3D put_listener(two, 0, "tcp", TEST_PORT); + + o2 =3D put_listener(two, o2, "udp", TEST_PORT); + ASSERT_EQ(0, listener_set(one, o1)); + ASSERT_EQ(0, threads_set(1)); /* threads now running */ + EXPECT_EQ(-EBUSY, listener_set(two, o2)); /* add refused */ + + /* refused means refused: the udp listener must not have been added */ + EXPECT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT)); + + threads_set(0); /* stop before netns exit */ +} + +TEST_F(nfsd_listener, sem_busy_on_remove) +{ + struct listener_ent got[MAX_LISTENERS]; + char one[64]; + int o1 =3D put_listener(one, 0, "tcp", TEST_PORT); + + ASSERT_EQ(0, listener_set(one, o1)); + ASSERT_EQ(0, threads_set(1)); + EXPECT_EQ(-EBUSY, listener_set(NULL, 0)); /* remove refused */ + + /* the doit moves the permsocks to a temp list before it can fail */ + EXPECT_EQ(1, listener_get(got, MAX_LISTENERS)); + EXPECT_NE(NULL, find_listener(got, 1, "tcp", AF_INET, TEST_PORT)); + + threads_set(0); +} + TEST_HARNESS_MAIN --=20 2.55.0