From 3a5be9e05b392431cd4f105417bd5d9e7f58b47a Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 24 Mar 2026 09:04:47 -0400 Subject: [PATCH 001/106] sunrpc: skip svc_xprt_enqueue when no work is pending svc_reserve() and svc_xprt_release_slot() call svc_xprt_enqueue() after modifying xpt_reserved or xpt_nr_rqsts. The purpose is to re-dispatch the transport when write-space or a slot becomes available. However, when neither XPT_DATA nor XPT_DEFERRED is set, no thread can make progress on the transport and the enqueue accomplishes nothing. Trace data from a 256KB NFSv3 WRITE workload over RDMA shows 11.2 svc_xprt_enqueue() calls per RPC. Of these, 6.9 per RPC lack XPT_DATA and exit svc_xprt_ready() immediately after executing the smp_rmb(), READ_ONCE(), and tracepoint. svc_reserve() and svc_xprt_release_slot() account for roughly five of these per RPC. A new helper, svc_xprt_resource_released(), checks XPT_DATA | XPT_DEFERRED before calling svc_xprt_enqueue(). The existing smp_wmb() barriers are upgraded to smp_mb() to ensure the flags check observes a concurrent producer's set_bit(XPT_DATA). Each producer (svc_rdma_wc_receive, etc.) both sets XPT_DATA and calls svc_xprt_enqueue(), so even if the check reads a stale value, the producer's own enqueue provides a fallback path. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- net/sunrpc/svc_xprt.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/net/sunrpc/svc_xprt.c b/net/sunrpc/svc_xprt.c index b16e710926c1..b8f3ac29e718 100644 --- a/net/sunrpc/svc_xprt.c +++ b/net/sunrpc/svc_xprt.c @@ -425,13 +425,28 @@ static bool svc_xprt_reserve_slot(struct svc_rqst *rqstp, struct svc_xprt *xprt) return true; } +/* + * After a caller releases write-space or a request slot, + * re-enqueue the transport only when there is pending + * work that a thread could act on. The smp_mb() pairs + * with the smp_rmb() in svc_xprt_ready() and orders the + * preceding counter update before the flags read so a + * concurrent set_bit(XPT_DATA) is visible here. + */ +static void svc_xprt_resource_released(struct svc_xprt *xprt) +{ + smp_mb(); + if (READ_ONCE(xprt->xpt_flags) & + (BIT(XPT_DATA) | BIT(XPT_DEFERRED))) + svc_xprt_enqueue(xprt); +} + static void svc_xprt_release_slot(struct svc_rqst *rqstp) { struct svc_xprt *xprt = rqstp->rq_xprt; if (test_and_clear_bit(RQ_DATA, &rqstp->rq_flags)) { atomic_dec(&xprt->xpt_nr_rqsts); - smp_wmb(); /* See smp_rmb() in svc_xprt_ready() */ - svc_xprt_enqueue(xprt); + svc_xprt_resource_released(xprt); } } @@ -525,10 +540,10 @@ void svc_reserve(struct svc_rqst *rqstp, int space) space += rqstp->rq_res.head[0].iov_len; if (xprt && space < rqstp->rq_reserved) { - atomic_sub((rqstp->rq_reserved - space), &xprt->xpt_reserved); + atomic_sub((rqstp->rq_reserved - space), + &xprt->xpt_reserved); rqstp->rq_reserved = space; - smp_wmb(); /* See smp_rmb() in svc_xprt_ready() */ - svc_xprt_enqueue(xprt); + svc_xprt_resource_released(xprt); } } EXPORT_SYMBOL_GPL(svc_reserve); From 0a5d7be861782f760431363b172ef2575645c5e6 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 24 Mar 2026 09:04:48 -0400 Subject: [PATCH 002/106] sunrpc: skip svc_xprt_enqueue in svc_xprt_received when idle svc_xprt_received() unconditionally calls svc_xprt_enqueue() after clearing XPT_BUSY. When no work flags are pending, the enqueue traverses svc_xprt_ready() -- executing an smp_rmb(), READ_ONCE(), and tracepoint -- before returning false. Trace data from a 256KB NFSv3 workload over RDMA shows 85% of svc_xprt_received() invocations reach svc_xprt_enqueue() with no pending work flags. In the WRITE phase, 167,335 of 196,420 calls find no work; in the READ phase, 97,165 of 98,276. Each unnecessary call executes a memory barrier, a flags read, and (when tracing is active) fires the svc_xprt_enqueue tracepoint. Add a flags pre-check between clear_bit(XPT_BUSY) and svc_xprt_enqueue(). Both the clear and the subsequent READ_ONCE operate on the same xpt_flags word, so cache-line serialization of the atomic bitops ensures the read observes any flag set by a concurrent producer before the line was acquired for the clear. If a producer's set_bit occurs after the clear_bit, that producer's own svc_xprt_enqueue() call observes !XPT_BUSY and dispatches the transport. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- net/sunrpc/svc_xprt.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/net/sunrpc/svc_xprt.c b/net/sunrpc/svc_xprt.c index b8f3ac29e718..377f5d7490aa 100644 --- a/net/sunrpc/svc_xprt.c +++ b/net/sunrpc/svc_xprt.c @@ -234,7 +234,19 @@ void svc_xprt_received(struct svc_xprt *xprt) svc_xprt_get(xprt); smp_mb__before_atomic(); clear_bit(XPT_BUSY, &xprt->xpt_flags); - svc_xprt_enqueue(xprt); + + /* + * Skip the enqueue when no actionable flags are set. + * Each producer both sets its flag (XPT_DATA, XPT_CLOSE, + * etc.) and calls svc_xprt_enqueue(); if a set_bit races + * with this check, the producer's own enqueue observes + * !XPT_BUSY and dispatches the transport. + */ + if (READ_ONCE(xprt->xpt_flags) & + (BIT(XPT_CONN) | BIT(XPT_CLOSE) | BIT(XPT_HANDSHAKE) | + BIT(XPT_DATA) | BIT(XPT_DEFERRED))) + svc_xprt_enqueue(xprt); + svc_xprt_put(xprt); } EXPORT_SYMBOL_GPL(svc_xprt_received); From e7f558158edda53b89b456cc5795807459914f2e Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 24 Mar 2026 09:04:49 -0400 Subject: [PATCH 003/106] sunrpc: skip svc_xprt_enqueue when transport is busy svc_xprt_resource_released() calls svc_xprt_enqueue() whenever XPT_DATA or XPT_DEFERRED is set. During RPC processing, svc_reserve_auth() reduces the reservation counter and triggers this path while the current thread still holds XPT_BUSY. The enqueue enters svc_xprt_ready(), executes an smp_rmb(), READ_ONCE(), and tracepoint, then returns false on seeing XPT_BUSY. Trace data from a 256KB NFSv3 WRITE workload over TCP shows this pattern generates roughly 195,000 wasted enqueue calls -- approximately one per RPC -- each paying the full svc_xprt_ready() cost for no benefit. Add a BUSY check alongside the existing DATA|DEFERRED check in svc_xprt_resource_released(). When the transport is BUSY, the holder will call svc_xprt_received() upon completion, which already checks for pending work flags and re-enqueues. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- net/sunrpc/svc_xprt.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/sunrpc/svc_xprt.c b/net/sunrpc/svc_xprt.c index 377f5d7490aa..63d1002e63e7 100644 --- a/net/sunrpc/svc_xprt.c +++ b/net/sunrpc/svc_xprt.c @@ -440,16 +440,23 @@ static bool svc_xprt_reserve_slot(struct svc_rqst *rqstp, struct svc_xprt *xprt) /* * After a caller releases write-space or a request slot, * re-enqueue the transport only when there is pending - * work that a thread could act on. The smp_mb() pairs + * work that a thread could act on. The smp_mb() pairs * with the smp_rmb() in svc_xprt_ready() and orders the * preceding counter update before the flags read so a * concurrent set_bit(XPT_DATA) is visible here. + * + * When the transport is BUSY, the thread holding it will + * call svc_xprt_received() upon completion, which checks + * for pending work and re-enqueues as needed. */ static void svc_xprt_resource_released(struct svc_xprt *xprt) { + unsigned long xpt_flags; + smp_mb(); - if (READ_ONCE(xprt->xpt_flags) & - (BIT(XPT_DATA) | BIT(XPT_DEFERRED))) + xpt_flags = READ_ONCE(xprt->xpt_flags); + if (xpt_flags & (BIT(XPT_DATA) | BIT(XPT_DEFERRED)) && + !(xpt_flags & BIT(XPT_BUSY))) svc_xprt_enqueue(xprt); } From 625981c8f3da0cc2d236d7b46c39dd75554b8276 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 24 Mar 2026 11:18:12 -0400 Subject: [PATCH 004/106] NFSD: Fix delegation reference leak in nfsd4_revoke_states When revoking delegation state, nfsd4_revoke_states() takes an extra reference on the stid before calling unhash_delegation_locked(). If unhash_delegation_locked() returns false (the delegation was already unhashed by a concurrent path), dp is set to NULL and revoke_delegation() is skipped, but the extra reference is never released. Each occurrence permanently pins the stid in memory. The leaked reference also prevents nfs4_put_stid() from decrementing cl_admin_revoked, leaving the counter permanently inflated. Drop the extra reference in the failure path. Fixes: 8dd91e8d31fe ("nfsd: fix race between laundromat and free_stateid") Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 6837b63d9864..3c2eb03f78c6 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1376,7 +1376,8 @@ static void destroy_delegation(struct nfs4_delegation *dp) * stateid or it's called from a laundromat thread (nfsd4_landromat()) that * determined that this specific state has expired and needs to be revoked * (both mark state with the appropriate stid sc_status mode). It is also - * assumed that a reference was taken on the @dp state. + * assumed that a reference was taken on the @dp state. This function + * consumes that reference. * * If this function finds that the @dp state is SC_STATUS_FREED it means * that a FREE_STATEID operation for this stateid has been processed and @@ -1839,6 +1840,10 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) mutex_unlock(&stp->st_mutex); break; case SC_TYPE_DELEG: + /* Extra reference guards against concurrent + * FREE_STATEID; revoke_delegation() consumes + * it, otherwise release it directly. + */ refcount_inc(&stid->sc_count); dp = delegstateid(stid); spin_lock(&nn->deleg_lock); @@ -1848,6 +1853,8 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) spin_unlock(&nn->deleg_lock); if (dp) revoke_delegation(dp); + else + nfs4_put_stid(stid); break; case SC_TYPE_LAYOUT: ls = layoutstateid(stid); From 1ed3df33bdbda5fd639571afe9c7cd282ff82cd9 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:22 -0400 Subject: [PATCH 005/106] nfsd: move struct nfsd_genl_rqstp to nfsctl.c It's not used outside of that file. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfsctl.c | 15 +++++++++++++++ fs/nfsd/nfsd.h | 15 --------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 04e3954d54bd..a457ed836972 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -1412,6 +1412,21 @@ static int create_proc_exports_entry(void) unsigned int nfsd_net_id; +struct nfsd_genl_rqstp { + struct sockaddr rq_daddr; + struct sockaddr rq_saddr; + unsigned long rq_flags; + ktime_t rq_stime; + __be32 rq_xid; + u32 rq_vers; + u32 rq_prog; + u32 rq_proc; + + /* NFSv4 compound */ + u32 rq_opcnt; + u32 rq_opnum[16]; +}; + static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb, struct netlink_callback *cb, struct nfsd_genl_rqstp *genl_rqstp) diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 7c009f07c90b..260bf8badb03 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -60,21 +60,6 @@ struct readdir_cd { /* Maximum number of operations per session compound */ #define NFSD_MAX_OPS_PER_COMPOUND 200 -struct nfsd_genl_rqstp { - struct sockaddr rq_daddr; - struct sockaddr rq_saddr; - unsigned long rq_flags; - ktime_t rq_stime; - __be32 rq_xid; - u32 rq_vers; - u32 rq_prog; - u32 rq_proc; - - /* NFSv4 compound */ - u32 rq_opcnt; - u32 rq_opnum[16]; -}; - extern struct svc_program nfsd_programs[]; extern const struct svc_version nfsd_version2, nfsd_version3, nfsd_version4; extern struct mutex nfsd_mutex; From 55a000fa1d0ebc20b8690ca0d869522712f82c19 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:23 -0400 Subject: [PATCH 006/106] sunrpc: rename sunrpc_cache_pipe_upcall() to sunrpc_cache_upcall() Since it will soon also send an upcall via netlink, if configured. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/export.c | 4 ++-- include/linux/sunrpc/cache.h | 2 +- net/sunrpc/cache.c | 6 +++--- net/sunrpc/svcauth_unix.c | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index 35fef3197a66..adc2266ea9f2 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -64,7 +64,7 @@ static void expkey_put(struct kref *ref) static int expkey_upcall(struct cache_detail *cd, struct cache_head *h) { - return sunrpc_cache_pipe_upcall(cd, h); + return sunrpc_cache_upcall(cd, h); } static void expkey_request(struct cache_detail *cd, @@ -388,7 +388,7 @@ static void svc_export_put(struct kref *ref) static int svc_export_upcall(struct cache_detail *cd, struct cache_head *h) { - return sunrpc_cache_pipe_upcall(cd, h); + return sunrpc_cache_upcall(cd, h); } static void svc_export_request(struct cache_detail *cd, diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h index b1e595c2615b..981af830a003 100644 --- a/include/linux/sunrpc/cache.h +++ b/include/linux/sunrpc/cache.h @@ -189,7 +189,7 @@ sunrpc_cache_update(struct cache_detail *detail, struct cache_head *new, struct cache_head *old, int hash); extern int -sunrpc_cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h); +sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h); extern int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail, struct cache_head *h); diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index 27dd6b58b8ff..aab84706f78a 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -1241,13 +1241,13 @@ static int cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h) return ret; } -int sunrpc_cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h) +int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h) { if (test_and_set_bit(CACHE_PENDING, &h->flags)) return 0; return cache_pipe_upcall(detail, h); } -EXPORT_SYMBOL_GPL(sunrpc_cache_pipe_upcall); +EXPORT_SYMBOL_GPL(sunrpc_cache_upcall); int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail, struct cache_head *h) @@ -1257,7 +1257,7 @@ int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail, trace_cache_entry_no_listener(detail, h); return -EINVAL; } - return sunrpc_cache_pipe_upcall(detail, h); + return sunrpc_cache_upcall(detail, h); } EXPORT_SYMBOL_GPL(sunrpc_cache_pipe_upcall_timeout); diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index 3be69c145d2a..9d5e07b900e1 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -152,7 +152,7 @@ static struct cache_head *ip_map_alloc(void) static int ip_map_upcall(struct cache_detail *cd, struct cache_head *h) { - return sunrpc_cache_pipe_upcall(cd, h); + return sunrpc_cache_upcall(cd, h); } static void ip_map_request(struct cache_detail *cd, From b2c217ee4f779334cff979a3c11a4ec59162f78b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:24 -0400 Subject: [PATCH 007/106] sunrpc: rename sunrpc_cache_pipe_upcall_timeout() This function doesn't have anything to do with a timeout. The only difference is that it warns if there are no listeners. Rename it to sunrpc_cache_upcall_warn(). Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfs/dns_resolve.c | 2 +- fs/nfsd/nfs4idmap.c | 4 ++-- include/linux/sunrpc/cache.h | 2 +- net/sunrpc/auth_gss/svcauth_gss.c | 2 +- net/sunrpc/cache.c | 6 +++--- net/sunrpc/svcauth_unix.c | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/nfs/dns_resolve.c b/fs/nfs/dns_resolve.c index 2ed2126201f4..acd3511c04a6 100644 --- a/fs/nfs/dns_resolve.c +++ b/fs/nfs/dns_resolve.c @@ -156,7 +156,7 @@ static int nfs_dns_upcall(struct cache_detail *cd, if (!nfs_cache_upcall(cd, key->hostname)) return 0; clear_bit(CACHE_PENDING, &ch->flags); - return sunrpc_cache_pipe_upcall_timeout(cd, ch); + return sunrpc_cache_upcall_warn(cd, ch); } static int nfs_dns_match(struct cache_head *ca, diff --git a/fs/nfsd/nfs4idmap.c b/fs/nfsd/nfs4idmap.c index ba06d3d3e6dd..71ba61b5d0a3 100644 --- a/fs/nfsd/nfs4idmap.c +++ b/fs/nfsd/nfs4idmap.c @@ -126,7 +126,7 @@ idtoname_hash(struct ent *ent) static int idtoname_upcall(struct cache_detail *cd, struct cache_head *h) { - return sunrpc_cache_pipe_upcall_timeout(cd, h); + return sunrpc_cache_upcall_warn(cd, h); } static void @@ -306,7 +306,7 @@ nametoid_hash(struct ent *ent) static int nametoid_upcall(struct cache_detail *cd, struct cache_head *h) { - return sunrpc_cache_pipe_upcall_timeout(cd, h); + return sunrpc_cache_upcall_warn(cd, h); } static void diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h index 981af830a003..80a3f17731d8 100644 --- a/include/linux/sunrpc/cache.h +++ b/include/linux/sunrpc/cache.h @@ -191,7 +191,7 @@ sunrpc_cache_update(struct cache_detail *detail, extern int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h); extern int -sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail, +sunrpc_cache_upcall_warn(struct cache_detail *detail, struct cache_head *h); diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index 161d02cc1c2c..d14209031e18 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -206,7 +206,7 @@ static struct cache_head *rsi_alloc(void) static int rsi_upcall(struct cache_detail *cd, struct cache_head *h) { - return sunrpc_cache_pipe_upcall_timeout(cd, h); + return sunrpc_cache_upcall_warn(cd, h); } static void rsi_request(struct cache_detail *cd, diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index aab84706f78a..eb866d6cd593 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -1249,8 +1249,8 @@ int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h) } EXPORT_SYMBOL_GPL(sunrpc_cache_upcall); -int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail, - struct cache_head *h) +int sunrpc_cache_upcall_warn(struct cache_detail *detail, + struct cache_head *h) { if (!cache_listeners_exist(detail)) { warn_no_listener(detail); @@ -1259,7 +1259,7 @@ int sunrpc_cache_pipe_upcall_timeout(struct cache_detail *detail, } return sunrpc_cache_upcall(detail, h); } -EXPORT_SYMBOL_GPL(sunrpc_cache_pipe_upcall_timeout); +EXPORT_SYMBOL_GPL(sunrpc_cache_upcall_warn); /* * parse a message from user-space and pass it diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index 9d5e07b900e1..87732c4cb838 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -467,7 +467,7 @@ static struct cache_head *unix_gid_alloc(void) static int unix_gid_upcall(struct cache_detail *cd, struct cache_head *h) { - return sunrpc_cache_pipe_upcall_timeout(cd, h); + return sunrpc_cache_upcall_warn(cd, h); } static void unix_gid_request(struct cache_detail *cd, From 8bfc7e13ee7c94edb5f9938985e58f5c14bac91c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:25 -0400 Subject: [PATCH 008/106] sunrpc: rename cache_pipe_upcall() to cache_do_upcall() Rename cache_pipe_upcall() to cache_do_upcall() in anticipation of the addition of a netlink-based upcall mechanism. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- net/sunrpc/cache.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index eb866d6cd593..04d30a1892d2 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -1195,12 +1195,12 @@ static bool cache_listeners_exist(struct cache_detail *detail) } /* - * register an upcall request to user-space and queue it up for read() by the - * upcall daemon. + * register an upcall request to user-space and queue it up to be fetched by + * the upcall daemon. * * Each request is at most one page long. */ -static int cache_pipe_upcall(struct cache_detail *detail, struct cache_head *h) +static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h) { char *buf; struct cache_request *crq; @@ -1245,7 +1245,7 @@ int sunrpc_cache_upcall(struct cache_detail *detail, struct cache_head *h) { if (test_and_set_bit(CACHE_PENDING, &h->flags)) return 0; - return cache_pipe_upcall(detail, h); + return cache_do_upcall(detail, h); } EXPORT_SYMBOL_GPL(sunrpc_cache_upcall); From d52db76f2d3559292b7af7c43ad9a635e017cac8 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:26 -0400 Subject: [PATCH 009/106] sunrpc: add a cache_notify callback A later patch will be changing the kernel to send a netlink notification when there is a pending cache_request. Add a new cache_notify operation to struct cache_detail for this purpose. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- include/linux/sunrpc/cache.h | 3 +++ net/sunrpc/cache.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h index 80a3f17731d8..c358151c2395 100644 --- a/include/linux/sunrpc/cache.h +++ b/include/linux/sunrpc/cache.h @@ -80,6 +80,9 @@ struct cache_detail { int (*cache_upcall)(struct cache_detail *, struct cache_head *); + int (*cache_notify)(struct cache_detail *cd, + struct cache_head *h); + void (*cache_request)(struct cache_detail *cd, struct cache_head *ch, char **bpp, int *blen); diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index 04d30a1892d2..f54c6578dae9 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -1233,6 +1233,8 @@ static int cache_do_upcall(struct cache_detail *detail, struct cache_head *h) /* Lost a race, no longer PENDING, so don't enqueue */ ret = -EAGAIN; spin_unlock(&detail->queue_lock); + if (ret != -EAGAIN && detail->cache_notify) + detail->cache_notify(detail, h); wake_up(&detail->queue_wait); if (ret == -EAGAIN) { kfree(buf); From c13ecd47648cc98e9f324ce4bcaa3d0654c3c91c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:27 -0400 Subject: [PATCH 010/106] sunrpc: add helpers to count and snapshot pending cache requests Add sunrpc_cache_requests_count() and sunrpc_cache_requests_snapshot() to allow callers to count and snapshot the pending upcall request list without exposing struct cache_request outside of cache.c. Both functions skip entries that no longer have CACHE_PENDING set. The snapshot function takes a cache_get() reference on each item so the caller can safely use them after the queue_lock is released. These will be used by the nfsd generic netlink dumpit handler for svc_export upcall requests. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- include/linux/sunrpc/cache.h | 6 ++++ net/sunrpc/cache.c | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h index c358151c2395..f88dc6bb17c7 100644 --- a/include/linux/sunrpc/cache.h +++ b/include/linux/sunrpc/cache.h @@ -251,6 +251,12 @@ extern int sunrpc_cache_register_pipefs(struct dentry *parent, const char *, extern void sunrpc_cache_unregister_pipefs(struct cache_detail *); extern void sunrpc_cache_unhash(struct cache_detail *, struct cache_head *); +int sunrpc_cache_requests_count(struct cache_detail *cd); +int sunrpc_cache_requests_snapshot(struct cache_detail *cd, + struct cache_head **items, + u64 *seqnos, int max, + u64 min_seqno); + /* Must store cache_detail in seq_file->private if using next three functions */ extern void *cache_seq_start_rcu(struct seq_file *file, loff_t *pos); extern void *cache_seq_next_rcu(struct seq_file *file, void *p, loff_t *pos); diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index f54c6578dae9..302bd7ccb39b 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -1902,3 +1902,64 @@ void sunrpc_cache_unhash(struct cache_detail *cd, struct cache_head *h) spin_unlock(&cd->hash_lock); } EXPORT_SYMBOL_GPL(sunrpc_cache_unhash); + +/** + * sunrpc_cache_requests_count - count pending upcall requests + * @cd: cache_detail to query + * + * Returns the number of requests on the cache's request list that + * still have CACHE_PENDING set. + */ +int sunrpc_cache_requests_count(struct cache_detail *cd) +{ + struct cache_request *crq; + int cnt = 0; + + spin_lock(&cd->queue_lock); + list_for_each_entry(crq, &cd->requests, list) { + if (test_bit(CACHE_PENDING, &crq->item->flags)) + cnt++; + } + spin_unlock(&cd->queue_lock); + return cnt; +} +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_count); + +/** + * sunrpc_cache_requests_snapshot - snapshot pending upcall requests + * @cd: cache_detail to query + * @items: array to fill with cache_head pointers (caller-allocated) + * @seqnos: array to fill with sequence numbers (caller-allocated) + * @max: size of the arrays + * @min_seqno: only include entries with seqno > min_seqno (0 for all) + * + * Only entries with CACHE_PENDING set are included. Takes a reference + * on each cache_head via cache_get(). Caller must call cache_put() + * on each returned item when done. + * + * Returns the number of entries filled. + */ +int sunrpc_cache_requests_snapshot(struct cache_detail *cd, + struct cache_head **items, + u64 *seqnos, int max, + u64 min_seqno) +{ + struct cache_request *crq; + int i = 0; + + spin_lock(&cd->queue_lock); + list_for_each_entry(crq, &cd->requests, list) { + if (i >= max) + break; + if (!test_bit(CACHE_PENDING, &crq->item->flags)) + continue; + if (crq->seqno <= min_seqno) + continue; + items[i] = cache_get(crq->item); + seqnos[i] = crq->seqno; + i++; + } + spin_unlock(&cd->queue_lock); + return i; +} +EXPORT_SYMBOL_GPL(sunrpc_cache_requests_snapshot); From af81cb247ca94e4bcfea31ea862cf3aaf955a503 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:28 -0400 Subject: [PATCH 011/106] sunrpc: add a generic netlink family for cache upcalls The auth.unix.ip and auth.unix.gid caches live in the sunrpc module, so they cannot use the nfsd generic netlink family. Create a new "sunrpc" generic netlink family with its own "exportd" multicast group to support cache upcall notifications for sunrpc-resident caches. Define a YAML spec (sunrpc_cache.yaml) with a cache-type enum (ip_map, unix_gid), a cache-notify multicast event, and the corresponding uapi header. Implement sunrpc_cache_notify() in cache.c, which checks for listeners on the exportd multicast group, builds and sends a SUNRPC_CMD_CACHE_NOTIFY message with the cache-type attribute. Register/unregister the sunrpc_nl_family in init_sunrpc() and cleanup_sunrpc(). Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/sunrpc_cache.yaml | 40 +++++++++++++++++ include/linux/sunrpc/cache.h | 2 + include/uapi/linux/sunrpc_netlink.h | 35 +++++++++++++++ net/sunrpc/Makefile | 2 +- net/sunrpc/cache.c | 44 +++++++++++++++++++ net/sunrpc/netlink.c | 34 ++++++++++++++ net/sunrpc/netlink.h | 22 ++++++++++ net/sunrpc/sunrpc_syms.c | 10 +++++ 8 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 Documentation/netlink/specs/sunrpc_cache.yaml create mode 100644 include/uapi/linux/sunrpc_netlink.h create mode 100644 net/sunrpc/netlink.c create mode 100644 net/sunrpc/netlink.h diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml new file mode 100644 index 000000000000..f4aa699598bc --- /dev/null +++ b/Documentation/netlink/specs/sunrpc_cache.yaml @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +--- +name: sunrpc +protocol: genetlink +uapi-header: linux/sunrpc_netlink.h + +doc: SUNRPC cache upcall support over generic netlink. + +definitions: + - + type: flags + name: cache-type + entries: [ip_map, unix_gid] + +attribute-sets: + - + name: cache-notify + attributes: + - + name: cache-type + type: u32 + enum: cache-type + +operations: + list: + - + name: cache-notify + doc: Notification that there are cache requests that need servicing + attribute-set: cache-notify + mcgrp: exportd + event: + attributes: + - cache-type + +mcast-groups: + list: + - + name: none + - + name: exportd diff --git a/include/linux/sunrpc/cache.h b/include/linux/sunrpc/cache.h index f88dc6bb17c7..2735c332ddb7 100644 --- a/include/linux/sunrpc/cache.h +++ b/include/linux/sunrpc/cache.h @@ -256,6 +256,8 @@ int sunrpc_cache_requests_snapshot(struct cache_detail *cd, struct cache_head **items, u64 *seqnos, int max, u64 min_seqno); +int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h, + u32 cache_type); /* Must store cache_detail in seq_file->private if using next three functions */ extern void *cache_seq_start_rcu(struct seq_file *file, loff_t *pos); diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h new file mode 100644 index 000000000000..6135d9b3eef1 --- /dev/null +++ b/include/uapi/linux/sunrpc_netlink.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/sunrpc_cache.yaml */ +/* YNL-GEN uapi header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#ifndef _UAPI_LINUX_SUNRPC_NETLINK_H +#define _UAPI_LINUX_SUNRPC_NETLINK_H + +#define SUNRPC_FAMILY_NAME "sunrpc" +#define SUNRPC_FAMILY_VERSION 1 + +enum sunrpc_cache_type { + SUNRPC_CACHE_TYPE_IP_MAP = 1, + SUNRPC_CACHE_TYPE_UNIX_GID = 2, +}; + +enum { + SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE = 1, + + __SUNRPC_A_CACHE_NOTIFY_MAX, + SUNRPC_A_CACHE_NOTIFY_MAX = (__SUNRPC_A_CACHE_NOTIFY_MAX - 1) +}; + +enum { + SUNRPC_CMD_CACHE_NOTIFY = 1, + + __SUNRPC_CMD_MAX, + SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1) +}; + +#define SUNRPC_MCGRP_NONE "none" +#define SUNRPC_MCGRP_EXPORTD "exportd" + +#endif /* _UAPI_LINUX_SUNRPC_NETLINK_H */ diff --git a/net/sunrpc/Makefile b/net/sunrpc/Makefile index f89c10fe7e6a..96727df3aa85 100644 --- a/net/sunrpc/Makefile +++ b/net/sunrpc/Makefile @@ -14,7 +14,7 @@ sunrpc-y := clnt.o xprt.o socklib.o xprtsock.o sched.o \ addr.o rpcb_clnt.o timer.o xdr.o \ sunrpc_syms.o cache.o rpc_pipe.o sysfs.o \ svc_xprt.o \ - xprtmultipath.o + xprtmultipath.o netlink.o sunrpc-$(CONFIG_SUNRPC_DEBUG) += debugfs.o sunrpc-$(CONFIG_SUNRPC_BACKCHANNEL) += backchannel_rqst.o sunrpc-$(CONFIG_PROC_FS) += stats.o diff --git a/net/sunrpc/cache.c b/net/sunrpc/cache.c index 302bd7ccb39b..391037f15292 100644 --- a/net/sunrpc/cache.c +++ b/net/sunrpc/cache.c @@ -33,9 +33,11 @@ #include #include #include +#include #include #include "netns.h" +#include "netlink.h" #include "fail.h" #define RPCDBG_FACILITY RPCDBG_CACHE @@ -1963,3 +1965,45 @@ int sunrpc_cache_requests_snapshot(struct cache_detail *cd, return i; } EXPORT_SYMBOL_GPL(sunrpc_cache_requests_snapshot); + +/** + * sunrpc_cache_notify - send a netlink notification for a cache event + * @cd: cache_detail for the cache + * @h: cache_head entry (unused, reserved for future use) + * @cache_type: cache type identifier (e.g. SUNRPC_CACHE_TYPE_UNIX_GID) + * + * Sends a SUNRPC_CMD_CACHE_NOTIFY multicast message on the "exportd" + * group if any listeners are present. Returns 0 on success or a + * negative errno. + */ +int sunrpc_cache_notify(struct cache_detail *cd, struct cache_head *h, + u32 cache_type) +{ + struct genlmsghdr *hdr; + struct sk_buff *msg; + + if (!genl_has_listeners(&sunrpc_nl_family, cd->net, + SUNRPC_NLGRP_EXPORTD)) + return -ENOLINK; + + msg = genlmsg_new(nla_total_size(sizeof(u32)), GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &sunrpc_nl_family, 0, + SUNRPC_CMD_CACHE_NOTIFY); + if (!hdr) { + nlmsg_free(msg); + return -ENOMEM; + } + + if (nla_put_u32(msg, SUNRPC_A_CACHE_NOTIFY_CACHE_TYPE, cache_type)) { + nlmsg_free(msg); + return -ENOMEM; + } + + genlmsg_end(msg, hdr); + return genlmsg_multicast_netns(&sunrpc_nl_family, cd->net, msg, 0, + SUNRPC_NLGRP_EXPORTD, GFP_KERNEL); +} +EXPORT_SYMBOL_GPL(sunrpc_cache_notify); diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c new file mode 100644 index 000000000000..952de6de85e3 --- /dev/null +++ b/net/sunrpc/netlink.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/sunrpc_cache.yaml */ +/* YNL-GEN kernel source */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#include +#include +#include + +#include "netlink.h" + +#include + +/* Ops table for sunrpc */ +static const struct genl_split_ops sunrpc_nl_ops[] = { +}; + +static const struct genl_multicast_group sunrpc_nl_mcgrps[] = { + [SUNRPC_NLGRP_NONE] = { "none", }, + [SUNRPC_NLGRP_EXPORTD] = { "exportd", }, +}; + +struct genl_family sunrpc_nl_family __ro_after_init = { + .name = SUNRPC_FAMILY_NAME, + .version = SUNRPC_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = sunrpc_nl_ops, + .n_split_ops = ARRAY_SIZE(sunrpc_nl_ops), + .mcgrps = sunrpc_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(sunrpc_nl_mcgrps), +}; diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h new file mode 100644 index 000000000000..74cf5183d745 --- /dev/null +++ b/net/sunrpc/netlink.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/sunrpc_cache.yaml */ +/* YNL-GEN kernel header */ +/* To regenerate run: tools/net/ynl/ynl-regen.sh */ + +#ifndef _LINUX_SUNRPC_GEN_H +#define _LINUX_SUNRPC_GEN_H + +#include +#include + +#include + +enum { + SUNRPC_NLGRP_NONE, + SUNRPC_NLGRP_EXPORTD, +}; + +extern struct genl_family sunrpc_nl_family; + +#endif /* _LINUX_SUNRPC_GEN_H */ diff --git a/net/sunrpc/sunrpc_syms.c b/net/sunrpc/sunrpc_syms.c index bab6cab29405..ab88ce46afb5 100644 --- a/net/sunrpc/sunrpc_syms.c +++ b/net/sunrpc/sunrpc_syms.c @@ -23,9 +23,12 @@ #include #include +#include + #include "sunrpc.h" #include "sysfs.h" #include "netns.h" +#include "netlink.h" unsigned int sunrpc_net_id; EXPORT_SYMBOL_GPL(sunrpc_net_id); @@ -108,6 +111,10 @@ init_sunrpc(void) if (err) goto out5; + err = genl_register_family(&sunrpc_nl_family); + if (err) + goto out6; + sunrpc_debugfs_init(); #if IS_ENABLED(CONFIG_SUNRPC_DEBUG) rpc_register_sysctl(); @@ -116,6 +123,8 @@ init_sunrpc(void) init_socket_xprt(); /* clnt sock transport */ return 0; +out6: + rpc_sysfs_exit(); out5: unregister_rpc_pipefs(); out4: @@ -131,6 +140,7 @@ init_sunrpc(void) static void __exit cleanup_sunrpc(void) { + genl_unregister_family(&sunrpc_nl_family); rpc_sysfs_exit(); rpc_cleanup_clids(); xprt_cleanup_ids(); From 712bdbb2153bdb99d4a9d0cdcae24b484610147f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:29 -0400 Subject: [PATCH 012/106] sunrpc: add netlink upcall for the auth.unix.ip cache Add netlink-based cache upcall support for the ip_map (auth.unix.ip) cache, using the sunrpc generic netlink family. Add ip-map attribute-set (seqno, class, addr, domain, negative, expiry), ip-map-reqs wrapper, and ip-map-get-reqs / ip-map-set-reqs operations to the sunrpc_cache YAML spec and generated headers. Implement sunrpc_nl_ip_map_get_reqs_dumpit() which snapshots pending ip_map cache requests and sends each entry's seqno, class name, and IP address over netlink. Implement sunrpc_nl_ip_map_set_reqs_doit() which parses ip_map cache responses from userspace (class, addr, expiry, and domain name or negative flag) and updates the cache via __ip_map_lookup() / __ip_map_update(). Wire up ip_map_notify() callback in ip_map_cache_template so cache misses trigger SUNRPC_CMD_CACHE_NOTIFY multicast events with SUNRPC_CACHE_TYPE_IP_MAP. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/sunrpc_cache.yaml | 47 ++++ include/uapi/linux/sunrpc_netlink.h | 21 ++ net/sunrpc/netlink.c | 34 +++ net/sunrpc/netlink.h | 8 + net/sunrpc/svcauth_unix.c | 246 ++++++++++++++++++ 5 files changed, 356 insertions(+) diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml index f4aa699598bc..8bcd43f65f32 100644 --- a/Documentation/netlink/specs/sunrpc_cache.yaml +++ b/Documentation/netlink/specs/sunrpc_cache.yaml @@ -20,6 +20,35 @@ attribute-sets: name: cache-type type: u32 enum: cache-type + - + name: ip-map + attributes: + - + name: seqno + type: u64 + - + name: class + type: string + - + name: addr + type: string + - + name: domain + type: string + - + name: negative + type: flag + - + name: expiry + type: u64 + - + name: ip-map-reqs + attributes: + - + name: requests + type: nest + nested-attributes: ip-map + multi-attr: true operations: list: @@ -31,6 +60,24 @@ operations: event: attributes: - cache-type + - + name: ip-map-get-reqs + doc: Dump all pending ip_map requests + attribute-set: ip-map-reqs + flags: [admin-perm] + dump: + request: + attributes: + - requests + - + name: ip-map-set-reqs + doc: Respond to one or more ip_map requests + attribute-set: ip-map-reqs + flags: [admin-perm] + do: + request: + attributes: + - requests mcast-groups: list: diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h index 6135d9b3eef1..b44befb5a34b 100644 --- a/include/uapi/linux/sunrpc_netlink.h +++ b/include/uapi/linux/sunrpc_netlink.h @@ -22,8 +22,29 @@ enum { SUNRPC_A_CACHE_NOTIFY_MAX = (__SUNRPC_A_CACHE_NOTIFY_MAX - 1) }; +enum { + SUNRPC_A_IP_MAP_SEQNO = 1, + SUNRPC_A_IP_MAP_CLASS, + SUNRPC_A_IP_MAP_ADDR, + SUNRPC_A_IP_MAP_DOMAIN, + SUNRPC_A_IP_MAP_NEGATIVE, + SUNRPC_A_IP_MAP_EXPIRY, + + __SUNRPC_A_IP_MAP_MAX, + SUNRPC_A_IP_MAP_MAX = (__SUNRPC_A_IP_MAP_MAX - 1) +}; + +enum { + SUNRPC_A_IP_MAP_REQS_REQUESTS = 1, + + __SUNRPC_A_IP_MAP_REQS_MAX, + SUNRPC_A_IP_MAP_REQS_MAX = (__SUNRPC_A_IP_MAP_REQS_MAX - 1) +}; + enum { SUNRPC_CMD_CACHE_NOTIFY = 1, + SUNRPC_CMD_IP_MAP_GET_REQS, + SUNRPC_CMD_IP_MAP_SET_REQS, __SUNRPC_CMD_MAX, SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1) diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c index 952de6de85e3..f57eb17fc27d 100644 --- a/net/sunrpc/netlink.c +++ b/net/sunrpc/netlink.c @@ -12,8 +12,42 @@ #include +/* Common nested types */ +const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1] = { + [SUNRPC_A_IP_MAP_SEQNO] = { .type = NLA_U64, }, + [SUNRPC_A_IP_MAP_CLASS] = { .type = NLA_NUL_STRING, }, + [SUNRPC_A_IP_MAP_ADDR] = { .type = NLA_NUL_STRING, }, + [SUNRPC_A_IP_MAP_DOMAIN] = { .type = NLA_NUL_STRING, }, + [SUNRPC_A_IP_MAP_NEGATIVE] = { .type = NLA_FLAG, }, + [SUNRPC_A_IP_MAP_EXPIRY] = { .type = NLA_U64, }, +}; + +/* SUNRPC_CMD_IP_MAP_GET_REQS - dump */ +static const struct nla_policy sunrpc_ip_map_get_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = { + [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), +}; + +/* SUNRPC_CMD_IP_MAP_SET_REQS - do */ +static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = { + [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), +}; + /* Ops table for sunrpc */ static const struct genl_split_ops sunrpc_nl_ops[] = { + { + .cmd = SUNRPC_CMD_IP_MAP_GET_REQS, + .dumpit = sunrpc_nl_ip_map_get_reqs_dumpit, + .policy = sunrpc_ip_map_get_reqs_nl_policy, + .maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = SUNRPC_CMD_IP_MAP_SET_REQS, + .doit = sunrpc_nl_ip_map_set_reqs_doit, + .policy = sunrpc_ip_map_set_reqs_nl_policy, + .maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group sunrpc_nl_mcgrps[] = { diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h index 74cf5183d745..68b773960b39 100644 --- a/net/sunrpc/netlink.h +++ b/net/sunrpc/netlink.h @@ -12,6 +12,14 @@ #include +/* Common nested types */ +extern const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1]; + +int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info); + enum { SUNRPC_NLGRP_NONE, SUNRPC_NLGRP_EXPORTD, diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index 87732c4cb838..b09b911c532a 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -17,11 +17,14 @@ #include #include #include +#include +#include #include #define RPCDBG_FACILITY RPCDBG_AUTH #include "netns.h" +#include "netlink.h" /* * AUTHUNIX and AUTHNULL credentials are both handled here. @@ -1017,12 +1020,255 @@ struct auth_ops svcauth_unix = { .set_client = svcauth_unix_set_client, }; +static int ip_map_notify(struct cache_detail *cd, struct cache_head *h) +{ + return sunrpc_cache_notify(cd, h, SUNRPC_CACHE_TYPE_IP_MAP); +} + +/** + * sunrpc_nl_ip_map_get_reqs_dumpit - dump pending ip_map requests + * @skb: reply buffer + * @cb: netlink metadata and command arguments + * + * Walk the ip_map cache's pending request list and create a netlink + * message with a nested entry for each cache_request, containing the + * seqno, class and addr. + * + * Uses cb->args[0] as a seqno cursor for dump continuation across + * multiple netlink messages. + * + * Returns the size of the reply or a negative errno. + */ +int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct sunrpc_net *sn; + struct cache_detail *cd; + struct cache_head **items; + u64 *seqnos; + int cnt, i, emitted; + void *hdr; + int ret; + + sn = net_generic(sock_net(skb->sk), sunrpc_net_id); + + cd = sn->ip_map_cache; + if (!cd) + return -ENODEV; + + cnt = sunrpc_cache_requests_count(cd); + if (!cnt) + return 0; + + items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); + if (!items || !seqnos) { + ret = -ENOMEM; + goto out_alloc; + } + + cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt, + cb->args[0]); + if (!cnt) { + ret = 0; + goto out_alloc; + } + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &sunrpc_nl_family, + NLM_F_MULTI, SUNRPC_CMD_IP_MAP_GET_REQS); + if (!hdr) { + ret = -ENOBUFS; + goto out_put; + } + + emitted = 0; + for (i = 0; i < cnt; i++) { + struct ip_map *im; + struct nlattr *nest; + char text_addr[40]; + + im = container_of(items[i], struct ip_map, h); + + if (ipv6_addr_v4mapped(&im->m_addr)) + snprintf(text_addr, 20, "%pI4", + &im->m_addr.s6_addr32[3]); + else + snprintf(text_addr, 40, "%pI6", &im->m_addr); + + nest = nla_nest_start(skb, SUNRPC_A_IP_MAP_REQS_REQUESTS); + if (!nest) + break; + + if (nla_put_u64_64bit(skb, SUNRPC_A_IP_MAP_SEQNO, + seqnos[i], 0) || + nla_put_string(skb, SUNRPC_A_IP_MAP_CLASS, + im->m_class) || + nla_put_string(skb, SUNRPC_A_IP_MAP_ADDR, text_addr)) { + nla_nest_cancel(skb, nest); + break; + } + + nla_nest_end(skb, nest); + cb->args[0] = seqnos[i]; + emitted++; + } + + if (!emitted) { + genlmsg_cancel(skb, hdr); + ret = -EMSGSIZE; + goto out_put; + } + + genlmsg_end(skb, hdr); + ret = skb->len; +out_put: + for (i = 0; i < cnt; i++) + cache_put(items[i], cd); +out_alloc: + kfree(seqnos); + kfree(items); + return ret; +} + +/** + * sunrpc_nl_parse_one_ip_map - parse one ip_map entry from netlink + * @cd: cache_detail for the ip_map cache + * @attr: nested attribute containing ip_map fields + * + * Parses one ip_map entry from a netlink message and updates the + * cache. Mirrors the logic in ip_map_parse(). + * + * Returns 0 on success or a negative errno. + */ +static int sunrpc_nl_parse_one_ip_map(struct cache_detail *cd, + struct nlattr *attr) +{ + struct nlattr *tb[SUNRPC_A_IP_MAP_EXPIRY + 1]; + union { + struct sockaddr sa; + struct sockaddr_in s4; + struct sockaddr_in6 s6; + } address; + struct sockaddr_in6 sin6; + struct ip_map *ipmp; + struct auth_domain *dom = NULL; + struct unix_domain *udom = NULL; + struct timespec64 boot; + time64_t expiry; + char class[8]; + int err; + int len; + + err = nla_parse_nested(tb, SUNRPC_A_IP_MAP_EXPIRY, attr, + sunrpc_ip_map_nl_policy, NULL); + if (err) + return err; + + /* class (required) */ + if (!tb[SUNRPC_A_IP_MAP_CLASS]) + return -EINVAL; + len = nla_len(tb[SUNRPC_A_IP_MAP_CLASS]); + if (len <= 0 || len > sizeof(class)) + return -EINVAL; + nla_strscpy(class, tb[SUNRPC_A_IP_MAP_CLASS], sizeof(class)); + + /* addr (required) */ + if (!tb[SUNRPC_A_IP_MAP_ADDR]) + return -EINVAL; + if (rpc_pton(cd->net, nla_data(tb[SUNRPC_A_IP_MAP_ADDR]), + nla_len(tb[SUNRPC_A_IP_MAP_ADDR]) - 1, + &address.sa, sizeof(address)) == 0) + return -EINVAL; + + switch (address.sa.sa_family) { + case AF_INET: + sin6.sin6_family = AF_INET6; + ipv6_addr_set_v4mapped(address.s4.sin_addr.s_addr, + &sin6.sin6_addr); + break; +#if IS_ENABLED(CONFIG_IPV6) + case AF_INET6: + memcpy(&sin6, &address.s6, sizeof(sin6)); + break; +#endif + default: + return -EINVAL; + } + + /* expiry (required, wallclock seconds) */ + if (!tb[SUNRPC_A_IP_MAP_EXPIRY]) + return -EINVAL; + getboottime64(&boot); + expiry = nla_get_u64(tb[SUNRPC_A_IP_MAP_EXPIRY]) - boot.tv_sec; + + /* domain name or negative */ + if (tb[SUNRPC_A_IP_MAP_NEGATIVE]) { + udom = NULL; + } else if (tb[SUNRPC_A_IP_MAP_DOMAIN]) { + dom = unix_domain_find(nla_data(tb[SUNRPC_A_IP_MAP_DOMAIN])); + if (!dom) + return -ENOENT; + udom = container_of(dom, struct unix_domain, h); + } else { + return -EINVAL; + } + + ipmp = __ip_map_lookup(cd, class, &sin6.sin6_addr); + if (ipmp) + err = __ip_map_update(cd, ipmp, udom, expiry); + else + err = -ENOMEM; + + if (dom) + auth_domain_put(dom); + + cache_flush(); + return err; +} + +/** + * sunrpc_nl_ip_map_set_reqs_doit - respond to ip_map requests + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Parse one or more ip_map cache responses from userspace and + * update the ip_map cache accordingly. + * + * Returns 0 on success or a negative errno. + */ +int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info) +{ + struct sunrpc_net *sn; + struct cache_detail *cd; + const struct nlattr *attr; + int rem, ret = 0; + + sn = net_generic(genl_info_net(info), sunrpc_net_id); + + cd = sn->ip_map_cache; + if (!cd) + return -ENODEV; + + nlmsg_for_each_attr_type(attr, SUNRPC_A_IP_MAP_REQS_REQUESTS, + info->nlhdr, GENL_HDRLEN, rem) { + ret = sunrpc_nl_parse_one_ip_map(cd, + (struct nlattr *)attr); + if (ret) + break; + } + + return ret; +} + static const struct cache_detail ip_map_cache_template = { .owner = THIS_MODULE, .hash_size = IP_HASHMAX, .name = "auth.unix.ip", .cache_put = ip_map_put, .cache_upcall = ip_map_upcall, + .cache_notify = ip_map_notify, .cache_request = ip_map_request, .cache_parse = ip_map_parse, .cache_show = ip_map_show, From 0850e8603cd7a3a17e2888391937154a54b93e02 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:30 -0400 Subject: [PATCH 013/106] sunrpc: add netlink upcall for the auth.unix.gid cache Add netlink-based cache upcall support for the unix_gid (auth.unix.gid) cache, using the sunrpc generic netlink family. Add unix-gid attribute-set (seqno, uid, gids multi-attr, negative, expiry), unix-gid-reqs wrapper, and unix-gid-get-reqs / unix-gid-set-reqs operations to the sunrpc_cache YAML spec and generated headers. Implement sunrpc_nl_unix_gid_get_reqs_dumpit() which snapshots pending unix_gid cache requests and sends each entry's seqno and uid over netlink. Implement sunrpc_nl_unix_gid_set_reqs_doit() which parses unix_gid cache responses from userspace (uid, expiry, gids as u32 multi-attr or negative flag) and updates the cache via unix_gid_lookup() / sunrpc_cache_update(). Wire up unix_gid_notify() callback in unix_gid_cache_template so cache misses trigger SUNRPC_CMD_CACHE_NOTIFY multicast events with SUNRPC_CACHE_TYPE_UNIX_GID. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/sunrpc_cache.yaml | 45 ++++ include/uapi/linux/sunrpc_netlink.h | 20 ++ net/sunrpc/netlink.c | 32 +++ net/sunrpc/netlink.h | 5 + net/sunrpc/svcauth_unix.c | 234 ++++++++++++++++++ 5 files changed, 336 insertions(+) diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml index 8bcd43f65f32..ed0ddb61ebcf 100644 --- a/Documentation/netlink/specs/sunrpc_cache.yaml +++ b/Documentation/netlink/specs/sunrpc_cache.yaml @@ -49,6 +49,33 @@ attribute-sets: type: nest nested-attributes: ip-map multi-attr: true + - + name: unix-gid + attributes: + - + name: seqno + type: u64 + - + name: uid + type: u32 + - + name: gids + type: u32 + multi-attr: true + - + name: negative + type: flag + - + name: expiry + type: u64 + - + name: unix-gid-reqs + attributes: + - + name: requests + type: nest + nested-attributes: unix-gid + multi-attr: true operations: list: @@ -78,6 +105,24 @@ operations: request: attributes: - requests + - + name: unix-gid-get-reqs + doc: Dump all pending unix_gid requests + attribute-set: unix-gid-reqs + flags: [admin-perm] + dump: + request: + attributes: + - requests + - + name: unix-gid-set-reqs + doc: Respond to one or more unix_gid requests + attribute-set: unix-gid-reqs + flags: [admin-perm] + do: + request: + attributes: + - requests mcast-groups: list: diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h index b44befb5a34b..d71c623e92ab 100644 --- a/include/uapi/linux/sunrpc_netlink.h +++ b/include/uapi/linux/sunrpc_netlink.h @@ -41,10 +41,30 @@ enum { SUNRPC_A_IP_MAP_REQS_MAX = (__SUNRPC_A_IP_MAP_REQS_MAX - 1) }; +enum { + SUNRPC_A_UNIX_GID_SEQNO = 1, + SUNRPC_A_UNIX_GID_UID, + SUNRPC_A_UNIX_GID_GIDS, + SUNRPC_A_UNIX_GID_NEGATIVE, + SUNRPC_A_UNIX_GID_EXPIRY, + + __SUNRPC_A_UNIX_GID_MAX, + SUNRPC_A_UNIX_GID_MAX = (__SUNRPC_A_UNIX_GID_MAX - 1) +}; + +enum { + SUNRPC_A_UNIX_GID_REQS_REQUESTS = 1, + + __SUNRPC_A_UNIX_GID_REQS_MAX, + SUNRPC_A_UNIX_GID_REQS_MAX = (__SUNRPC_A_UNIX_GID_REQS_MAX - 1) +}; + enum { SUNRPC_CMD_CACHE_NOTIFY = 1, SUNRPC_CMD_IP_MAP_GET_REQS, SUNRPC_CMD_IP_MAP_SET_REQS, + SUNRPC_CMD_UNIX_GID_GET_REQS, + SUNRPC_CMD_UNIX_GID_SET_REQS, __SUNRPC_CMD_MAX, SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1) diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c index f57eb17fc27d..41843f007c37 100644 --- a/net/sunrpc/netlink.c +++ b/net/sunrpc/netlink.c @@ -32,6 +32,24 @@ static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_ [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), }; +const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] = { + [SUNRPC_A_UNIX_GID_SEQNO] = { .type = NLA_U64, }, + [SUNRPC_A_UNIX_GID_UID] = { .type = NLA_U32, }, + [SUNRPC_A_UNIX_GID_GIDS] = { .type = NLA_U32, }, + [SUNRPC_A_UNIX_GID_NEGATIVE] = { .type = NLA_FLAG, }, + [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, }, +}; + +/* SUNRPC_CMD_UNIX_GID_GET_REQS - dump */ +static const struct nla_policy sunrpc_unix_gid_get_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = { + [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), +}; + +/* SUNRPC_CMD_UNIX_GID_SET_REQS - do */ +static const struct nla_policy sunrpc_unix_gid_set_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = { + [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), +}; + /* Ops table for sunrpc */ static const struct genl_split_ops sunrpc_nl_ops[] = { { @@ -48,6 +66,20 @@ static const struct genl_split_ops sunrpc_nl_ops[] = { .maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = SUNRPC_CMD_UNIX_GID_GET_REQS, + .dumpit = sunrpc_nl_unix_gid_get_reqs_dumpit, + .policy = sunrpc_unix_gid_get_reqs_nl_policy, + .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = SUNRPC_CMD_UNIX_GID_SET_REQS, + .doit = sunrpc_nl_unix_gid_set_reqs_doit, + .policy = sunrpc_unix_gid_set_reqs_nl_policy, + .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group sunrpc_nl_mcgrps[] = { diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h index 68b773960b39..16b87519a409 100644 --- a/net/sunrpc/netlink.h +++ b/net/sunrpc/netlink.h @@ -14,11 +14,16 @@ /* Common nested types */ extern const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1]; +extern const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1]; int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); +int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info); enum { SUNRPC_NLGRP_NONE, diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index b09b911c532a..7703523d4246 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -585,12 +585,246 @@ static int unix_gid_show(struct seq_file *m, return 0; } +static int unix_gid_notify(struct cache_detail *cd, struct cache_head *h) +{ + return sunrpc_cache_notify(cd, h, SUNRPC_CACHE_TYPE_UNIX_GID); +} + +/** + * sunrpc_nl_unix_gid_get_reqs_dumpit - dump pending unix_gid requests + * @skb: reply buffer + * @cb: netlink metadata and command arguments + * + * Walk the unix_gid cache's pending request list and create a netlink + * message with a nested entry for each cache_request, containing the + * seqno and uid. + * + * Uses cb->args[0] as a seqno cursor for dump continuation across + * multiple netlink messages. + * + * Returns the size of the reply or a negative errno. + */ +int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct sunrpc_net *sn; + struct cache_detail *cd; + struct cache_head **items; + u64 *seqnos; + int cnt, i, emitted; + void *hdr; + int ret; + + sn = net_generic(sock_net(skb->sk), sunrpc_net_id); + + cd = sn->unix_gid_cache; + if (!cd) + return -ENODEV; + + cnt = sunrpc_cache_requests_count(cd); + if (!cnt) + return 0; + + items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); + if (!items || !seqnos) { + ret = -ENOMEM; + goto out_alloc; + } + + cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt, + cb->args[0]); + if (!cnt) { + ret = 0; + goto out_alloc; + } + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &sunrpc_nl_family, + NLM_F_MULTI, SUNRPC_CMD_UNIX_GID_GET_REQS); + if (!hdr) { + ret = -ENOBUFS; + goto out_put; + } + + emitted = 0; + for (i = 0; i < cnt; i++) { + struct unix_gid *ug; + struct nlattr *nest; + + ug = container_of(items[i], struct unix_gid, h); + + nest = nla_nest_start(skb, + SUNRPC_A_UNIX_GID_REQS_REQUESTS); + if (!nest) + break; + + if (nla_put_u64_64bit(skb, SUNRPC_A_UNIX_GID_SEQNO, + seqnos[i], 0) || + nla_put_u32(skb, SUNRPC_A_UNIX_GID_UID, + from_kuid(&init_user_ns, ug->uid))) { + nla_nest_cancel(skb, nest); + break; + } + + nla_nest_end(skb, nest); + cb->args[0] = seqnos[i]; + emitted++; + } + + if (!emitted) { + genlmsg_cancel(skb, hdr); + ret = -EMSGSIZE; + goto out_put; + } + + genlmsg_end(skb, hdr); + ret = skb->len; +out_put: + for (i = 0; i < cnt; i++) + cache_put(items[i], cd); +out_alloc: + kfree(seqnos); + kfree(items); + return ret; +} + +/** + * sunrpc_nl_parse_one_unix_gid - parse one unix_gid entry from netlink + * @cd: cache_detail for the unix_gid cache + * @attr: nested attribute containing unix_gid fields + * + * Parses one unix_gid entry from a netlink message and updates the + * cache. Mirrors the logic in unix_gid_parse(). + * + * Returns 0 on success or a negative errno. + */ +static int sunrpc_nl_parse_one_unix_gid(struct cache_detail *cd, + struct nlattr *attr) +{ + struct nlattr *tb[SUNRPC_A_UNIX_GID_EXPIRY + 1]; + struct unix_gid ug, *ugp; + struct timespec64 boot; + struct nlattr *gid_attr; + int err, rem, gids = 0; + kuid_t uid; + + err = nla_parse_nested(tb, SUNRPC_A_UNIX_GID_EXPIRY, attr, + sunrpc_unix_gid_nl_policy, NULL); + if (err) + return err; + + /* uid (required) */ + if (!tb[SUNRPC_A_UNIX_GID_UID]) + return -EINVAL; + uid = make_kuid(current_user_ns(), + nla_get_u32(tb[SUNRPC_A_UNIX_GID_UID])); + ug.uid = uid; + + /* expiry (required, wallclock seconds) */ + if (!tb[SUNRPC_A_UNIX_GID_EXPIRY]) + return -EINVAL; + getboottime64(&boot); + ug.h.flags = 0; + ug.h.expiry_time = nla_get_u64(tb[SUNRPC_A_UNIX_GID_EXPIRY]) - + boot.tv_sec; + + if (tb[SUNRPC_A_UNIX_GID_NEGATIVE]) { + ug.gi = groups_alloc(0); + if (!ug.gi) + return -ENOMEM; + } else { + /* Count gids */ + nla_for_each_nested_type(gid_attr, SUNRPC_A_UNIX_GID_GIDS, + attr, rem) + gids++; + + if (gids > 8192) + return -EINVAL; + + ug.gi = groups_alloc(gids); + if (!ug.gi) + return -ENOMEM; + + gids = 0; + nla_for_each_nested_type(gid_attr, SUNRPC_A_UNIX_GID_GIDS, + attr, rem) { + kgid_t kgid; + + kgid = make_kgid(current_user_ns(), + nla_get_u32(gid_attr)); + if (!gid_valid(kgid)) { + err = -EINVAL; + goto out; + } + ug.gi->gid[gids++] = kgid; + } + groups_sort(ug.gi); + } + + ugp = unix_gid_lookup(cd, uid); + if (ugp) { + struct cache_head *ch; + + ch = sunrpc_cache_update(cd, &ug.h, &ugp->h, + unix_gid_hash(uid)); + if (!ch) { + err = -ENOMEM; + } else { + err = 0; + cache_put(ch, cd); + } + } else { + err = -ENOMEM; + } +out: + if (ug.gi) + put_group_info(ug.gi); + return err; +} + +/** + * sunrpc_nl_unix_gid_set_reqs_doit - respond to unix_gid requests + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Parse one or more unix_gid cache responses from userspace and + * update the unix_gid cache accordingly. + * + * Returns 0 on success or a negative errno. + */ +int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info) +{ + struct sunrpc_net *sn; + struct cache_detail *cd; + const struct nlattr *attr; + int rem, ret = 0; + + sn = net_generic(genl_info_net(info), sunrpc_net_id); + + cd = sn->unix_gid_cache; + if (!cd) + return -ENODEV; + + nlmsg_for_each_attr_type(attr, SUNRPC_A_UNIX_GID_REQS_REQUESTS, + info->nlhdr, GENL_HDRLEN, rem) { + ret = sunrpc_nl_parse_one_unix_gid(cd, + (struct nlattr *)attr); + if (ret) + break; + } + + return ret; +} + static const struct cache_detail unix_gid_cache_template = { .owner = THIS_MODULE, .hash_size = GID_HASHMAX, .name = "auth.unix.gid", .cache_put = unix_gid_put, .cache_upcall = unix_gid_upcall, + .cache_notify = unix_gid_notify, .cache_request = unix_gid_request, .cache_parse = unix_gid_parse, .cache_show = unix_gid_show, From 912fc994b920a24543725d981e9cd082d1376ed7 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:31 -0400 Subject: [PATCH 014/106] nfsd: add netlink upcall for the svc_export cache Add netlink-based cache upcall support for the svc_export (nfsd.export) cache to Documentation/netlink/specs/nfsd.yaml and regenerate the resulting files. Implement nfsd_cache_notify() which sends a NFSD_CMD_CACHE_NOTIFY multicast event to the "exportd" group, carrying the cache type so userspace knows which cache has pending requests. Implement nfsd_nl_svc_export_get_reqs_dumpit() which snapshots pending svc_export cache requests and sends each entry's seqno, client name, and path over netlink. Implement nfsd_nl_svc_export_set_reqs_doit() which parses svc_export cache responses from userspace (client, path, expiry, flags, anon uid/gid, fslocations, uuid, secinfo, xprtsec, fsid, or negative flag) and updates the cache via svc_export_lookup() / svc_export_update(). Wire up the svc_export_notify() callback in svc_export_cache_template so cache misses trigger NFSD_CMD_CACHE_NOTIFY multicast events with NFSD_CACHE_TYPE_SVC_EXPORT. Note that the export-flags and xprtsec-mode enums are organized to match their counterparts in include/uapi/linux/nfsd/export.h. The intent is that future export options will only be added to the netlink headers, which should eliminate the need to keep so much in sync. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 162 ++++++++++ fs/nfsd/export.c | 444 +++++++++++++++++++++++++- fs/nfsd/netlink.c | 61 ++++ fs/nfsd/netlink.h | 13 + fs/nfsd/nfsctl.c | 28 ++ fs/nfsd/nfsd.h | 2 +- include/uapi/linux/nfsd_netlink.h | 101 ++++++ net/sunrpc/netlink.c | 17 +- net/sunrpc/netlink.h | 3 +- 9 files changed, 815 insertions(+), 16 deletions(-) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 8ab43c8253b2..9ba1cf348b40 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -6,7 +6,51 @@ uapi-header: linux/nfsd_netlink.h doc: NFSD configuration over generic netlink. +definitions: + - + type: flags + name: cache-type + entries: [svc_export] + - + type: flags + name: export-flags + doc: These flags are ordered to match the NFSEXP_* flags in include/linux/nfsd/export.h + entries: + - readonly + - insecure-port + - rootsquash + - allsquash + - async + - gathered-writes + - noreaddirplus + - security-label + - sign-fh + - nohide + - nosubtreecheck + - noauthnlm + - msnfs + - fsid + - crossmount + - noacl + - v4root + - pnfs + - + type: flags + name: xprtsec-mode + doc: These flags are ordered to match the NFSEXP_XPRTSEC_* flags in include/linux/nfsd/export.h + entries: + - none + - tls + - mtls + attribute-sets: + - + name: cache-notify + attributes: + - + name: cache-type + type: u32 + enum: cache-type - name: rpc-status attributes: @@ -132,6 +176,91 @@ attribute-sets: - name: npools type: u32 + - + name: fslocation + attributes: + - + name: host + type: string + - + name: path + type: string + - + name: fslocations + attributes: + - + name: location + type: nest + nested-attributes: fslocation + multi-attr: true + - + name: auth-flavor + attributes: + - + name: pseudoflavor + type: u32 + - + name: flags + type: u32 + enum: export-flags + enum-as-flags: true + - + name: svc-export + attributes: + - + name: seqno + type: u64 + - + name: client + type: string + - + name: path + type: string + - + name: negative + type: flag + - + name: expiry + type: u64 + - + name: anon-uid + type: u32 + - + name: anon-gid + type: u32 + - + name: fslocations + type: nest + nested-attributes: fslocations + - + name: uuid + type: binary + - + name: secinfo + type: nest + nested-attributes: auth-flavor + multi-attr: true + - + name: xprtsec + type: u32 + enum: xprtsec-mode + multi-attr: true + - + name: flags + type: u32 + enum: export-flags + enum-as-flags: true + - + name: fsid + type: s32 + - + name: svc-export-reqs + attributes: + - + name: requests + type: nest + nested-attributes: svc-export + multi-attr: true operations: list: @@ -233,3 +362,36 @@ operations: attributes: - mode - npools + - + name: cache-notify + doc: Notification that there are cache requests that need servicing + attribute-set: cache-notify + mcgrp: exportd + event: + attributes: + - cache-type + - + name: svc-export-get-reqs + doc: Dump all pending svc_export requests + attribute-set: svc-export-reqs + flags: [admin-perm] + dump: + request: + attributes: + - requests + - + name: svc-export-set-reqs + doc: Respond to one or more svc_export requests + attribute-set: svc-export-reqs + flags: [admin-perm] + do: + request: + attributes: + - requests + +mcast-groups: + list: + - + name: none + - + name: exportd diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index adc2266ea9f2..79bfa7a7087a 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "nfsd.h" #include "nfsfh.h" @@ -24,6 +26,7 @@ #include "pnfs.h" #include "filecache.h" #include "trace.h" +#include "netlink.h" #define NFSDDBG_FACILITY NFSDDBG_EXPORT @@ -386,11 +389,447 @@ static void svc_export_put(struct kref *ref) queue_rcu_work(nfsd_export_wq, &exp->ex_rwork); } +/** + * nfsd_nl_svc_export_get_reqs_dumpit - dump pending svc_export requests + * @skb: reply buffer + * @cb: netlink metadata and command arguments + * + * Walk the svc_export cache's pending request list and create a netlink + * message with a nested entry for each cache_request, containing the + * seqno, client string, and path. + * + * Uses cb->args[0] as a seqno cursor for dump continuation across + * multiple netlink messages. + * + * Returns the size of the reply or a negative errno. + */ +int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct nfsd_net *nn; + struct cache_detail *cd; + struct cache_head **items; + u64 *seqnos; + int cnt, i, emitted; + char *pathbuf; + void *hdr; + int ret; + + nn = net_generic(sock_net(skb->sk), nfsd_net_id); + + mutex_lock(&nfsd_mutex); + + cd = nn->svc_export_cache; + if (!cd) { + ret = -ENODEV; + goto out_unlock; + } + + cnt = sunrpc_cache_requests_count(cd); + if (!cnt) { + ret = 0; + goto out_unlock; + } + + items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + if (!items || !seqnos || !pathbuf) { + ret = -ENOMEM; + goto out_alloc; + } + + cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt, + cb->args[0]); + if (!cnt) { + ret = 0; + goto out_alloc; + } + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &nfsd_nl_family, + NLM_F_MULTI, NFSD_CMD_SVC_EXPORT_GET_REQS); + if (!hdr) { + ret = -ENOBUFS; + goto out_put; + } + + emitted = 0; + for (i = 0; i < cnt; i++) { + struct svc_export *exp; + struct nlattr *nest; + char *pth; + + exp = container_of(items[i], struct svc_export, h); + + pth = d_path(&exp->ex_path, pathbuf, PATH_MAX); + if (IS_ERR(pth)) + continue; + + nest = nla_nest_start(skb, + NFSD_A_SVC_EXPORT_REQS_REQUESTS); + if (!nest) + break; + + if (nla_put_u64_64bit(skb, NFSD_A_SVC_EXPORT_SEQNO, + seqnos[i], 0) || + nla_put_string(skb, NFSD_A_SVC_EXPORT_CLIENT, + exp->ex_client->name) || + nla_put_string(skb, NFSD_A_SVC_EXPORT_PATH, pth)) { + nla_nest_cancel(skb, nest); + break; + } + + nla_nest_end(skb, nest); + cb->args[0] = seqnos[i]; + emitted++; + } + + if (!emitted) { + genlmsg_cancel(skb, hdr); + ret = -EMSGSIZE; + goto out_put; + } + + genlmsg_end(skb, hdr); + ret = skb->len; +out_put: + for (i = 0; i < cnt; i++) + cache_put(items[i], cd); +out_alloc: + kfree(pathbuf); + kfree(seqnos); + kfree(items); +out_unlock: + mutex_unlock(&nfsd_mutex); + return ret; +} + +/** + * nfsd_nl_parse_fslocations - parse fslocations from netlink + * @attr: NFSD_A_SVC_EXPORT_FSLOCATIONS nested attribute + * @fsloc: fslocations struct to fill in + * + * Returns 0 on success or a negative errno. + */ +static int nfsd_nl_parse_fslocations(struct nlattr *attr, + struct nfsd4_fs_locations *fsloc) +{ + struct nlattr *loc_attr; + int rem, count = 0; + int err; + + if (fsloc->locations) + return -EINVAL; + + /* Count locations first */ + nla_for_each_nested_type(loc_attr, NFSD_A_FSLOCATIONS_LOCATION, + attr, rem) + count++; + + if (count > MAX_FS_LOCATIONS) + return -EINVAL; + if (!count) + return 0; + + fsloc->locations = kcalloc(count, sizeof(struct nfsd4_fs_location), + GFP_KERNEL); + if (!fsloc->locations) + return -ENOMEM; + + nla_for_each_nested_type(loc_attr, NFSD_A_FSLOCATIONS_LOCATION, + attr, rem) { + struct nlattr *tb[NFSD_A_FSLOCATION_PATH + 1]; + struct nfsd4_fs_location *loc; + + err = nla_parse_nested(tb, NFSD_A_FSLOCATION_PATH, loc_attr, + nfsd_fslocation_nl_policy, NULL); + if (err) + goto out_free; + + if (!tb[NFSD_A_FSLOCATION_HOST] || + !tb[NFSD_A_FSLOCATION_PATH]) { + err = -EINVAL; + goto out_free; + } + + loc = &fsloc->locations[fsloc->locations_count++]; + loc->hosts = kstrdup(nla_data(tb[NFSD_A_FSLOCATION_HOST]), + GFP_KERNEL); + loc->path = kstrdup(nla_data(tb[NFSD_A_FSLOCATION_PATH]), + GFP_KERNEL); + if (!loc->hosts || !loc->path) { + err = -ENOMEM; + goto out_free; + } + } + + return 0; +out_free: + nfsd4_fslocs_free(fsloc); + return err; +} + +static struct svc_export *svc_export_update(struct svc_export *new, + struct svc_export *old); +static struct svc_export *svc_export_lookup(struct svc_export *); +static int check_export(const struct path *path, int *flags, + unsigned char *uuid); + +/** + * nfsd_nl_parse_one_export - parse one svc_export entry from a netlink message + * @cd: cache_detail for the svc_export cache + * @attr: nested attribute containing svc-export fields + * + * Parses one svc-export entry from a netlink message and updates the + * cache. Mirrors the logic in svc_export_parse(). + * + * Returns 0 on success or a negative errno. + */ +static int nfsd_nl_parse_one_export(struct cache_detail *cd, + struct nlattr *attr) +{ + struct nlattr *tb[NFSD_A_SVC_EXPORT_FSID + 1]; + struct auth_domain *dom = NULL; + struct svc_export exp = {}, *expp; + struct nlattr *secinfo_attr; + struct timespec64 boot; + int err, rem; + + err = nla_parse_nested(tb, NFSD_A_SVC_EXPORT_FSID, attr, + nfsd_svc_export_nl_policy, NULL); + if (err) + return err; + + /* client (required) */ + if (!tb[NFSD_A_SVC_EXPORT_CLIENT]) + return -EINVAL; + + dom = auth_domain_find(nla_data(tb[NFSD_A_SVC_EXPORT_CLIENT])); + if (!dom) + return -ENOENT; + + /* path (required) */ + if (!tb[NFSD_A_SVC_EXPORT_PATH]) { + err = -EINVAL; + goto out_dom; + } + + err = kern_path(nla_data(tb[NFSD_A_SVC_EXPORT_PATH]), 0, + &exp.ex_path); + if (err) + goto out_dom; + + exp.ex_client = dom; + exp.cd = cd; + exp.ex_devid_map = NULL; + exp.ex_xprtsec_modes = NFSEXP_XPRTSEC_ALL; + + /* expiry (required, wallclock seconds) */ + if (!tb[NFSD_A_SVC_EXPORT_EXPIRY]) { + err = -EINVAL; + goto out_path; + } + getboottime64(&boot); + exp.h.expiry_time = nla_get_u64(tb[NFSD_A_SVC_EXPORT_EXPIRY]) - + boot.tv_sec; + + if (tb[NFSD_A_SVC_EXPORT_NEGATIVE]) { + set_bit(CACHE_NEGATIVE, &exp.h.flags); + } else { + /* flags */ + if (tb[NFSD_A_SVC_EXPORT_FLAGS]) + exp.ex_flags = nla_get_u32(tb[NFSD_A_SVC_EXPORT_FLAGS]); + + /* anon uid */ + if (tb[NFSD_A_SVC_EXPORT_ANON_UID]) { + u32 uid = nla_get_u32(tb[NFSD_A_SVC_EXPORT_ANON_UID]); + + exp.ex_anon_uid = make_kuid(current_user_ns(), uid); + } + + /* anon gid */ + if (tb[NFSD_A_SVC_EXPORT_ANON_GID]) { + u32 gid = nla_get_u32(tb[NFSD_A_SVC_EXPORT_ANON_GID]); + + exp.ex_anon_gid = make_kgid(current_user_ns(), gid); + } + + /* fsid */ + if (tb[NFSD_A_SVC_EXPORT_FSID]) + exp.ex_fsid = nla_get_s32(tb[NFSD_A_SVC_EXPORT_FSID]); + + /* fslocations */ + if (tb[NFSD_A_SVC_EXPORT_FSLOCATIONS]) { + struct nlattr *fsl = tb[NFSD_A_SVC_EXPORT_FSLOCATIONS]; + + err = nfsd_nl_parse_fslocations(fsl, + &exp.ex_fslocs); + if (err) + goto out_path; + } + + /* uuid */ + if (tb[NFSD_A_SVC_EXPORT_UUID]) { + if (nla_len(tb[NFSD_A_SVC_EXPORT_UUID]) != + EX_UUID_LEN) { + err = -EINVAL; + goto out_fslocs; + } + exp.ex_uuid = kmemdup(nla_data(tb[NFSD_A_SVC_EXPORT_UUID]), + EX_UUID_LEN, GFP_KERNEL); + if (!exp.ex_uuid) { + err = -ENOMEM; + goto out_fslocs; + } + } + + /* secinfo (multi-attr) */ + nla_for_each_nested_type(secinfo_attr, + NFSD_A_SVC_EXPORT_SECINFO, + attr, rem) { + struct nlattr *ftb[NFSD_A_AUTH_FLAVOR_FLAGS + 1]; + struct exp_flavor_info *f; + + if (exp.ex_nflavors >= MAX_SECINFO_LIST) { + err = -EINVAL; + goto out_uuid; + } + + err = nla_parse_nested(ftb, + NFSD_A_AUTH_FLAVOR_FLAGS, + secinfo_attr, + nfsd_auth_flavor_nl_policy, + NULL); + if (err) + goto out_uuid; + + f = &exp.ex_flavors[exp.ex_nflavors++]; + + if (ftb[NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR]) + f->pseudoflavor = nla_get_u32(ftb[NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR]); + + if (ftb[NFSD_A_AUTH_FLAVOR_FLAGS]) + f->flags = nla_get_u32(ftb[NFSD_A_AUTH_FLAVOR_FLAGS]); + + /* Only some flags are allowed to differ between flavors: */ + if (~NFSEXP_SECINFO_FLAGS & (f->flags ^ exp.ex_flags)) { + err = -EINVAL; + goto out_uuid; + } + } + + /* xprtsec (multi-attr u32) */ + if (tb[NFSD_A_SVC_EXPORT_XPRTSEC]) { + struct nlattr *xp_attr; + + exp.ex_xprtsec_modes = 0; + nla_for_each_nested_type(xp_attr, + NFSD_A_SVC_EXPORT_XPRTSEC, + attr, rem) { + u32 mode = nla_get_u32(xp_attr); + + if (mode > NFSEXP_XPRTSEC_MTLS) { + err = -EINVAL; + goto out_uuid; + } + exp.ex_xprtsec_modes |= mode; + } + } + + err = check_export(&exp.ex_path, &exp.ex_flags, + exp.ex_uuid); + if (err) + goto out_uuid; + + if (exp.h.expiry_time < seconds_since_boot()) + goto out_uuid; + + err = -EINVAL; + if (!uid_valid(exp.ex_anon_uid)) + goto out_uuid; + if (!gid_valid(exp.ex_anon_gid)) + goto out_uuid; + err = 0; + + nfsd4_setup_layout_type(&exp); + } + + expp = svc_export_lookup(&exp); + if (!expp) { + err = -ENOMEM; + goto out_uuid; + } + expp = svc_export_update(&exp, expp); + if (expp) { + trace_nfsd_export_update(expp); + cache_flush(); + exp_put(expp); + } else { + err = -ENOMEM; + } + +out_uuid: + kfree(exp.ex_uuid); +out_fslocs: + nfsd4_fslocs_free(&exp.ex_fslocs); +out_path: + path_put(&exp.ex_path); +out_dom: + auth_domain_put(dom); + return err; +} + +/** + * nfsd_nl_svc_export_set_reqs_doit - respond to svc_export requests + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Parse one or more svc_export cache responses from userspace and + * update the export cache accordingly. + * + * Returns 0 on success or a negative errno. + */ +int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info) +{ + struct nfsd_net *nn; + struct cache_detail *cd; + const struct nlattr *attr; + int rem, ret = 0; + + nn = net_generic(genl_info_net(info), nfsd_net_id); + + mutex_lock(&nfsd_mutex); + + cd = nn->svc_export_cache; + if (!cd) { + ret = -ENODEV; + goto out_unlock; + } + + nlmsg_for_each_attr_type(attr, NFSD_A_SVC_EXPORT_REQS_REQUESTS, + info->nlhdr, GENL_HDRLEN, rem) { + ret = nfsd_nl_parse_one_export(cd, (struct nlattr *)attr); + if (ret) + break; + } + +out_unlock: + mutex_unlock(&nfsd_mutex); + return ret; +} + static int svc_export_upcall(struct cache_detail *cd, struct cache_head *h) { return sunrpc_cache_upcall(cd, h); } +static int svc_export_notify(struct cache_detail *cd, struct cache_head *h) +{ + return nfsd_cache_notify(cd, h, NFSD_CACHE_TYPE_SVC_EXPORT); +} + static void svc_export_request(struct cache_detail *cd, struct cache_head *h, char **bpp, int *blen) @@ -410,10 +849,6 @@ static void svc_export_request(struct cache_detail *cd, (*bpp)[-1] = '\n'; } -static struct svc_export *svc_export_update(struct svc_export *new, - struct svc_export *old); -static struct svc_export *svc_export_lookup(struct svc_export *); - static int check_export(const struct path *path, int *flags, unsigned char *uuid) { struct inode *inode = d_inode(path->dentry); @@ -908,6 +1343,7 @@ static const struct cache_detail svc_export_cache_template = { .name = "nfsd.export", .cache_put = svc_export_put, .cache_upcall = svc_export_upcall, + .cache_notify = svc_export_notify, .cache_request = svc_export_request, .cache_parse = svc_export_parse, .cache_show = svc_export_show, diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index 81c943345d13..fb401d7302af 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -12,11 +12,41 @@ #include /* Common nested types */ +const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1] = { + [NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR] = { .type = NLA_U32, }, + [NFSD_A_AUTH_FLAVOR_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff), +}; + +const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1] = { + [NFSD_A_FSLOCATION_HOST] = { .type = NLA_NUL_STRING, }, + [NFSD_A_FSLOCATION_PATH] = { .type = NLA_NUL_STRING, }, +}; + +const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1] = { + [NFSD_A_FSLOCATIONS_LOCATION] = NLA_POLICY_NESTED(nfsd_fslocation_nl_policy), +}; + const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1] = { [NFSD_A_SOCK_ADDR] = { .type = NLA_BINARY, }, [NFSD_A_SOCK_TRANSPORT_NAME] = { .type = NLA_NUL_STRING, }, }; +const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1] = { + [NFSD_A_SVC_EXPORT_SEQNO] = { .type = NLA_U64, }, + [NFSD_A_SVC_EXPORT_CLIENT] = { .type = NLA_NUL_STRING, }, + [NFSD_A_SVC_EXPORT_PATH] = { .type = NLA_NUL_STRING, }, + [NFSD_A_SVC_EXPORT_NEGATIVE] = { .type = NLA_FLAG, }, + [NFSD_A_SVC_EXPORT_EXPIRY] = { .type = NLA_U64, }, + [NFSD_A_SVC_EXPORT_ANON_UID] = { .type = NLA_U32, }, + [NFSD_A_SVC_EXPORT_ANON_GID] = { .type = NLA_U32, }, + [NFSD_A_SVC_EXPORT_FSLOCATIONS] = NLA_POLICY_NESTED(nfsd_fslocations_nl_policy), + [NFSD_A_SVC_EXPORT_UUID] = { .type = NLA_BINARY, }, + [NFSD_A_SVC_EXPORT_SECINFO] = NLA_POLICY_NESTED(nfsd_auth_flavor_nl_policy), + [NFSD_A_SVC_EXPORT_XPRTSEC] = NLA_POLICY_MASK(NLA_U32, 0x7), + [NFSD_A_SVC_EXPORT_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff), + [NFSD_A_SVC_EXPORT_FSID] = { .type = NLA_S32, }, +}; + const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = { [NFSD_A_VERSION_MAJOR] = { .type = NLA_U32, }, [NFSD_A_VERSION_MINOR] = { .type = NLA_U32, }, @@ -48,6 +78,16 @@ static const struct nla_policy nfsd_pool_mode_set_nl_policy[NFSD_A_POOL_MODE_MOD [NFSD_A_POOL_MODE_MODE] = { .type = NLA_NUL_STRING, }, }; +/* NFSD_CMD_SVC_EXPORT_GET_REQS - dump */ +static const struct nla_policy nfsd_svc_export_get_reqs_nl_policy[NFSD_A_SVC_EXPORT_REQS_REQUESTS + 1] = { + [NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy), +}; + +/* NFSD_CMD_SVC_EXPORT_SET_REQS - do */ +static const struct nla_policy nfsd_svc_export_set_reqs_nl_policy[NFSD_A_SVC_EXPORT_REQS_REQUESTS + 1] = { + [NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy), +}; + /* Ops table for nfsd */ static const struct genl_split_ops nfsd_nl_ops[] = { { @@ -103,6 +143,25 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .doit = nfsd_nl_pool_mode_get_doit, .flags = GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_SVC_EXPORT_GET_REQS, + .dumpit = nfsd_nl_svc_export_get_reqs_dumpit, + .policy = nfsd_svc_export_get_reqs_nl_policy, + .maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = NFSD_CMD_SVC_EXPORT_SET_REQS, + .doit = nfsd_nl_svc_export_set_reqs_doit, + .policy = nfsd_svc_export_set_reqs_nl_policy, + .maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group nfsd_nl_mcgrps[] = { + [NFSD_NLGRP_NONE] = { "none", }, + [NFSD_NLGRP_EXPORTD] = { "exportd", }, }; struct genl_family nfsd_nl_family __ro_after_init = { @@ -113,4 +172,6 @@ struct genl_family nfsd_nl_family __ro_after_init = { .module = THIS_MODULE, .split_ops = nfsd_nl_ops, .n_split_ops = ARRAY_SIZE(nfsd_nl_ops), + .mcgrps = nfsd_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(nfsd_nl_mcgrps), }; diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index 478117ff6b8c..d6ed8d9b0bb1 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -13,7 +13,11 @@ #include /* Common nested types */ +extern const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1]; +extern const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1]; +extern const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1]; extern const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1]; +extern const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1]; extern const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1]; int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, @@ -26,6 +30,15 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info); + +enum { + NFSD_NLGRP_NONE, + NFSD_NLGRP_EXPORTD, +}; extern struct genl_family nfsd_nl_family; diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index a457ed836972..c93cf82e3647 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2212,6 +2212,34 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info) return err; } +int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type) +{ + struct genlmsghdr *hdr; + struct sk_buff *msg; + + if (!genl_has_listeners(&nfsd_nl_family, cd->net, NFSD_NLGRP_EXPORTD)) + return -ENOLINK; + + msg = genlmsg_new(nla_total_size(sizeof(u32)), GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &nfsd_nl_family, 0, NFSD_CMD_CACHE_NOTIFY); + if (!hdr) { + nlmsg_free(msg); + return -ENOMEM; + } + + if (nla_put_u32(msg, NFSD_A_CACHE_NOTIFY_CACHE_TYPE, cache_type)) { + nlmsg_free(msg); + return -ENOMEM; + } + + genlmsg_end(msg, hdr); + return genlmsg_multicast_netns(&nfsd_nl_family, cd->net, msg, 0, + NFSD_NLGRP_EXPORTD, GFP_KERNEL); +} + /** * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace * @net: a freshly-created network namespace diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 260bf8badb03..709da3c7a5fa 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -108,7 +108,7 @@ struct dentry *nfsd_client_mkdir(struct nfsd_net *nn, const struct tree_descr *, struct dentry **fdentries); void nfsd_client_rmdir(struct dentry *dentry); - +int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type); #if defined(CONFIG_NFSD_V2_ACL) || defined(CONFIG_NFSD_V3_ACL) #ifdef CONFIG_NFSD_V2_ACL diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index 97c7447f4d14..32a551b320e0 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -10,6 +10,52 @@ #define NFSD_FAMILY_NAME "nfsd" #define NFSD_FAMILY_VERSION 1 +enum nfsd_cache_type { + NFSD_CACHE_TYPE_SVC_EXPORT = 1, +}; + +/* + * These flags are ordered to match the NFSEXP_* flags in + * include/linux/nfsd/export.h + */ +enum nfsd_export_flags { + NFSD_EXPORT_FLAGS_READONLY = 1, + NFSD_EXPORT_FLAGS_INSECURE_PORT = 2, + NFSD_EXPORT_FLAGS_ROOTSQUASH = 4, + NFSD_EXPORT_FLAGS_ALLSQUASH = 8, + NFSD_EXPORT_FLAGS_ASYNC = 16, + NFSD_EXPORT_FLAGS_GATHERED_WRITES = 32, + NFSD_EXPORT_FLAGS_NOREADDIRPLUS = 64, + NFSD_EXPORT_FLAGS_SECURITY_LABEL = 128, + NFSD_EXPORT_FLAGS_SIGN_FH = 256, + NFSD_EXPORT_FLAGS_NOHIDE = 512, + NFSD_EXPORT_FLAGS_NOSUBTREECHECK = 1024, + NFSD_EXPORT_FLAGS_NOAUTHNLM = 2048, + NFSD_EXPORT_FLAGS_MSNFS = 4096, + NFSD_EXPORT_FLAGS_FSID = 8192, + NFSD_EXPORT_FLAGS_CROSSMOUNT = 16384, + NFSD_EXPORT_FLAGS_NOACL = 32768, + NFSD_EXPORT_FLAGS_V4ROOT = 65536, + NFSD_EXPORT_FLAGS_PNFS = 131072, +}; + +/* + * These flags are ordered to match the NFSEXP_XPRTSEC_* flags in + * include/linux/nfsd/export.h + */ +enum nfsd_xprtsec_mode { + NFSD_XPRTSEC_MODE_NONE = 1, + NFSD_XPRTSEC_MODE_TLS = 2, + NFSD_XPRTSEC_MODE_MTLS = 4, +}; + +enum { + NFSD_A_CACHE_NOTIFY_CACHE_TYPE = 1, + + __NFSD_A_CACHE_NOTIFY_MAX, + NFSD_A_CACHE_NOTIFY_MAX = (__NFSD_A_CACHE_NOTIFY_MAX - 1) +}; + enum { NFSD_A_RPC_STATUS_XID = 1, NFSD_A_RPC_STATUS_FLAGS, @@ -81,6 +127,55 @@ enum { NFSD_A_POOL_MODE_MAX = (__NFSD_A_POOL_MODE_MAX - 1) }; +enum { + NFSD_A_FSLOCATION_HOST = 1, + NFSD_A_FSLOCATION_PATH, + + __NFSD_A_FSLOCATION_MAX, + NFSD_A_FSLOCATION_MAX = (__NFSD_A_FSLOCATION_MAX - 1) +}; + +enum { + NFSD_A_FSLOCATIONS_LOCATION = 1, + + __NFSD_A_FSLOCATIONS_MAX, + NFSD_A_FSLOCATIONS_MAX = (__NFSD_A_FSLOCATIONS_MAX - 1) +}; + +enum { + NFSD_A_AUTH_FLAVOR_PSEUDOFLAVOR = 1, + NFSD_A_AUTH_FLAVOR_FLAGS, + + __NFSD_A_AUTH_FLAVOR_MAX, + NFSD_A_AUTH_FLAVOR_MAX = (__NFSD_A_AUTH_FLAVOR_MAX - 1) +}; + +enum { + NFSD_A_SVC_EXPORT_SEQNO = 1, + NFSD_A_SVC_EXPORT_CLIENT, + NFSD_A_SVC_EXPORT_PATH, + NFSD_A_SVC_EXPORT_NEGATIVE, + NFSD_A_SVC_EXPORT_EXPIRY, + NFSD_A_SVC_EXPORT_ANON_UID, + NFSD_A_SVC_EXPORT_ANON_GID, + NFSD_A_SVC_EXPORT_FSLOCATIONS, + NFSD_A_SVC_EXPORT_UUID, + NFSD_A_SVC_EXPORT_SECINFO, + NFSD_A_SVC_EXPORT_XPRTSEC, + NFSD_A_SVC_EXPORT_FLAGS, + NFSD_A_SVC_EXPORT_FSID, + + __NFSD_A_SVC_EXPORT_MAX, + NFSD_A_SVC_EXPORT_MAX = (__NFSD_A_SVC_EXPORT_MAX - 1) +}; + +enum { + NFSD_A_SVC_EXPORT_REQS_REQUESTS = 1, + + __NFSD_A_SVC_EXPORT_REQS_MAX, + NFSD_A_SVC_EXPORT_REQS_MAX = (__NFSD_A_SVC_EXPORT_REQS_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -91,9 +186,15 @@ enum { NFSD_CMD_LISTENER_GET, NFSD_CMD_POOL_MODE_SET, NFSD_CMD_POOL_MODE_GET, + NFSD_CMD_CACHE_NOTIFY, + NFSD_CMD_SVC_EXPORT_GET_REQS, + NFSD_CMD_SVC_EXPORT_SET_REQS, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) }; +#define NFSD_MCGRP_NONE "none" +#define NFSD_MCGRP_EXPORTD "exportd" + #endif /* _UAPI_LINUX_NFSD_NETLINK_H */ diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c index 41843f007c37..3ac6b0cac5fe 100644 --- a/net/sunrpc/netlink.c +++ b/net/sunrpc/netlink.c @@ -6,7 +6,6 @@ #include #include -#include #include "netlink.h" @@ -22,6 +21,14 @@ const struct nla_policy sunrpc_ip_map_nl_policy[SUNRPC_A_IP_MAP_EXPIRY + 1] = { [SUNRPC_A_IP_MAP_EXPIRY] = { .type = NLA_U64, }, }; +const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] = { + [SUNRPC_A_UNIX_GID_SEQNO] = { .type = NLA_U64, }, + [SUNRPC_A_UNIX_GID_UID] = { .type = NLA_U32, }, + [SUNRPC_A_UNIX_GID_GIDS] = { .type = NLA_U32, }, + [SUNRPC_A_UNIX_GID_NEGATIVE] = { .type = NLA_FLAG, }, + [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, }, +}; + /* SUNRPC_CMD_IP_MAP_GET_REQS - dump */ static const struct nla_policy sunrpc_ip_map_get_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = { [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), @@ -32,14 +39,6 @@ static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_ [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), }; -const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] = { - [SUNRPC_A_UNIX_GID_SEQNO] = { .type = NLA_U64, }, - [SUNRPC_A_UNIX_GID_UID] = { .type = NLA_U32, }, - [SUNRPC_A_UNIX_GID_GIDS] = { .type = NLA_U32, }, - [SUNRPC_A_UNIX_GID_NEGATIVE] = { .type = NLA_FLAG, }, - [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, }, -}; - /* SUNRPC_CMD_UNIX_GID_GET_REQS - dump */ static const struct nla_policy sunrpc_unix_gid_get_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = { [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h index 16b87519a409..2aec57d27a58 100644 --- a/net/sunrpc/netlink.h +++ b/net/sunrpc/netlink.h @@ -18,8 +18,7 @@ extern const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIR int sunrpc_nl_ip_map_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); -int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb, - struct genl_info *info); +int sunrpc_nl_ip_map_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb, From 01c2305795a3b6b164df48e72b12022a68fd60c1 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:32 -0400 Subject: [PATCH 015/106] nfsd: add netlink upcall for the nfsd.fh cache Add netlink-based cache upcall support for the expkey (nfsd.fh) cache, following the same pattern as the existing svc_export netlink support. Add expkey to the cache-type enum, a new expkey attribute-set with client, fsidtype, fsid, negative, expiry, and path fields, and the expkey-get-reqs / expkey-set-reqs operations to the nfsd YAML spec and generated headers. Implement nfsd_nl_expkey_get_reqs_dumpit() which snapshots pending expkey cache requests and sends each entry's seqno, client name, fsidtype, and fsid over netlink. Implement nfsd_nl_expkey_set_reqs_doit() which parses expkey cache responses from userspace (client, fsidtype, fsid, expiry, and path or negative flag) and updates the cache via svc_expkey_lookup() / svc_expkey_update(). Wire up the expkey_notify() callback in svc_expkey_cache_template so cache misses trigger NFSD_CMD_CACHE_NOTIFY multicast events with NFSD_CACHE_TYPE_EXPKEY. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 52 ++++- fs/nfsd/export.c | 266 ++++++++++++++++++++++++++ fs/nfsd/netlink.c | 34 ++++ fs/nfsd/netlink.h | 4 + include/uapi/linux/nfsd_netlink.h | 23 +++ 5 files changed, 378 insertions(+), 1 deletion(-) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 9ba1cf348b40..7030b40273fd 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -10,7 +10,7 @@ definitions: - type: flags name: cache-type - entries: [svc_export] + entries: [svc_export, expkey] - type: flags name: export-flags @@ -261,6 +261,38 @@ attribute-sets: type: nest nested-attributes: svc-export multi-attr: true + - + name: expkey + attributes: + - + name: seqno + type: u64 + - + name: client + type: string + - + name: fsidtype + type: u8 + - + name: fsid + type: binary + - + name: negative + type: flag + - + name: expiry + type: u64 + - + name: path + type: string + - + name: expkey-reqs + attributes: + - + name: requests + type: nest + nested-attributes: expkey + multi-attr: true operations: list: @@ -388,6 +420,24 @@ operations: request: attributes: - requests + - + name: expkey-get-reqs + doc: Dump all pending expkey requests + attribute-set: expkey-reqs + flags: [admin-perm] + dump: + request: + attributes: + - requests + - + name: expkey-set-reqs + doc: Respond to one or more expkey requests + attribute-set: expkey-reqs + flags: [admin-perm] + do: + request: + attributes: + - requests mcast-groups: list: diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index 79bfa7a7087a..eb020054f9a3 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -266,12 +266,18 @@ static void expkey_flush(void) mutex_unlock(&nfsd_mutex); } +static int expkey_notify(struct cache_detail *cd, struct cache_head *h) +{ + return nfsd_cache_notify(cd, h, NFSD_CACHE_TYPE_EXPKEY); +} + static const struct cache_detail svc_expkey_cache_template = { .owner = THIS_MODULE, .hash_size = EXPKEY_HASHMAX, .name = "nfsd.fh", .cache_put = expkey_put, .cache_upcall = expkey_upcall, + .cache_notify = expkey_notify, .cache_request = expkey_request, .cache_parse = expkey_parse, .cache_show = expkey_show, @@ -322,6 +328,266 @@ svc_expkey_update(struct cache_detail *cd, struct svc_expkey *new, return NULL; } +/** + * nfsd_nl_expkey_get_reqs_dumpit - dump pending expkey requests + * @skb: reply buffer + * @cb: netlink metadata and command arguments + * + * Walk the expkey cache's pending request list and create a netlink + * message with a nested entry for each cache_request, containing the + * seqno, client string, fsidtype and fsid. + * + * Uses cb->args[0] as a seqno cursor for dump continuation across + * multiple netlink messages. + * + * Returns the size of the reply or a negative errno. + */ +int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + struct nfsd_net *nn; + struct cache_detail *cd; + struct cache_head **items; + u64 *seqnos; + int cnt, i, emitted; + void *hdr; + int ret; + + nn = net_generic(sock_net(skb->sk), nfsd_net_id); + + mutex_lock(&nfsd_mutex); + + cd = nn->svc_expkey_cache; + if (!cd) { + ret = -ENODEV; + goto out_unlock; + } + + cnt = sunrpc_cache_requests_count(cd); + if (!cnt) { + ret = 0; + goto out_unlock; + } + + items = kcalloc(cnt, sizeof(*items), GFP_KERNEL); + seqnos = kcalloc(cnt, sizeof(*seqnos), GFP_KERNEL); + if (!items || !seqnos) { + ret = -ENOMEM; + goto out_alloc; + } + + cnt = sunrpc_cache_requests_snapshot(cd, items, seqnos, cnt, + cb->args[0]); + if (!cnt) { + ret = 0; + goto out_alloc; + } + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, &nfsd_nl_family, + NLM_F_MULTI, NFSD_CMD_EXPKEY_GET_REQS); + if (!hdr) { + ret = -ENOBUFS; + goto out_put; + } + + emitted = 0; + for (i = 0; i < cnt; i++) { + struct svc_expkey *ek; + struct nlattr *nest; + + ek = container_of(items[i], struct svc_expkey, h); + + nest = nla_nest_start(skb, NFSD_A_EXPKEY_REQS_REQUESTS); + if (!nest) + break; + + if (nla_put_u64_64bit(skb, NFSD_A_EXPKEY_SEQNO, + seqnos[i], 0) || + nla_put_string(skb, NFSD_A_EXPKEY_CLIENT, + ek->ek_client->name) || + nla_put_u8(skb, NFSD_A_EXPKEY_FSIDTYPE, + ek->ek_fsidtype) || + nla_put(skb, NFSD_A_EXPKEY_FSID, + key_len(ek->ek_fsidtype), ek->ek_fsid)) { + nla_nest_cancel(skb, nest); + break; + } + + nla_nest_end(skb, nest); + cb->args[0] = seqnos[i]; + emitted++; + } + + if (!emitted) { + genlmsg_cancel(skb, hdr); + ret = -EMSGSIZE; + goto out_put; + } + + genlmsg_end(skb, hdr); + ret = skb->len; +out_put: + for (i = 0; i < cnt; i++) + cache_put(items[i], cd); +out_alloc: + kfree(seqnos); + kfree(items); +out_unlock: + mutex_unlock(&nfsd_mutex); + return ret; +} + +/** + * nfsd_nl_parse_one_expkey - parse one expkey entry from netlink + * @cd: cache_detail for the expkey cache + * @attr: nested attribute containing expkey fields + * + * Parses one expkey entry from a netlink message and updates the + * cache. Mirrors the logic in expkey_parse(). + * + * Returns 0 on success or a negative errno. + */ +static int nfsd_nl_parse_one_expkey(struct cache_detail *cd, + struct nlattr *attr) +{ + struct nlattr *tb[NFSD_A_EXPKEY_PATH + 1]; + struct auth_domain *dom = NULL; + struct svc_expkey key; + struct svc_expkey *ek = NULL; + struct timespec64 boot; + int err; + u8 fsidtype; + int fsid_len; + + err = nla_parse_nested(tb, NFSD_A_EXPKEY_PATH, attr, + nfsd_expkey_nl_policy, NULL); + if (err) + return err; + + /* client (required) */ + if (!tb[NFSD_A_EXPKEY_CLIENT]) + return -EINVAL; + + dom = auth_domain_find(nla_data(tb[NFSD_A_EXPKEY_CLIENT])); + if (!dom) + return -ENOENT; + + /* fsidtype (required) */ + if (!tb[NFSD_A_EXPKEY_FSIDTYPE]) { + err = -EINVAL; + goto out_dom; + } + fsidtype = nla_get_u8(tb[NFSD_A_EXPKEY_FSIDTYPE]); + if (key_len(fsidtype) == 0) { + err = -EINVAL; + goto out_dom; + } + + /* fsid (required) */ + if (!tb[NFSD_A_EXPKEY_FSID]) { + err = -EINVAL; + goto out_dom; + } + fsid_len = nla_len(tb[NFSD_A_EXPKEY_FSID]); + if (fsid_len != key_len(fsidtype)) { + err = -EINVAL; + goto out_dom; + } + + /* expiry (required, wallclock seconds) */ + if (!tb[NFSD_A_EXPKEY_EXPIRY]) { + err = -EINVAL; + goto out_dom; + } + + key.h.flags = 0; + getboottime64(&boot); + key.h.expiry_time = nla_get_u64(tb[NFSD_A_EXPKEY_EXPIRY]) - + boot.tv_sec; + key.ek_client = dom; + key.ek_fsidtype = fsidtype; + memcpy(key.ek_fsid, nla_data(tb[NFSD_A_EXPKEY_FSID]), fsid_len); + + ek = svc_expkey_lookup(cd, &key); + if (!ek) { + err = -ENOMEM; + goto out_dom; + } + + if (tb[NFSD_A_EXPKEY_NEGATIVE]) { + set_bit(CACHE_NEGATIVE, &key.h.flags); + ek = svc_expkey_update(cd, &key, ek); + if (ek) + trace_nfsd_expkey_update(ek, NULL); + else + err = -ENOMEM; + } else if (tb[NFSD_A_EXPKEY_PATH]) { + err = kern_path(nla_data(tb[NFSD_A_EXPKEY_PATH]), 0, + &key.ek_path); + if (err) + goto out_ek; + ek = svc_expkey_update(cd, &key, ek); + if (ek) + trace_nfsd_expkey_update(ek, + nla_data(tb[NFSD_A_EXPKEY_PATH])); + else + err = -ENOMEM; + path_put(&key.ek_path); + } else { + err = -EINVAL; + goto out_ek; + } + + cache_flush(); + +out_ek: + if (ek) + cache_put(&ek->h, cd); +out_dom: + auth_domain_put(dom); + return err; +} + +/** + * nfsd_nl_expkey_set_reqs_doit - respond to expkey requests + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Parse one or more expkey cache responses from userspace and + * update the expkey cache accordingly. + * + * Returns 0 on success or a negative errno. + */ +int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, + struct genl_info *info) +{ + struct nfsd_net *nn; + struct cache_detail *cd; + const struct nlattr *attr; + int rem, ret = 0; + + nn = net_generic(genl_info_net(info), nfsd_net_id); + + mutex_lock(&nfsd_mutex); + + cd = nn->svc_expkey_cache; + if (!cd) { + ret = -ENODEV; + goto out_unlock; + } + + nlmsg_for_each_attr_type(attr, NFSD_A_EXPKEY_REQS_REQUESTS, + info->nlhdr, GENL_HDRLEN, rem) { + ret = nfsd_nl_parse_one_expkey(cd, (struct nlattr *)attr); + if (ret) + break; + } + +out_unlock: + mutex_unlock(&nfsd_mutex); + return ret; +} #define EXPORT_HASHBITS 8 #define EXPORT_HASHMAX (1<< EXPORT_HASHBITS) diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index fb401d7302af..394230e250a5 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -17,6 +17,16 @@ const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1] [NFSD_A_AUTH_FLAVOR_FLAGS] = NLA_POLICY_MASK(NLA_U32, 0x3ffff), }; +const struct nla_policy nfsd_expkey_nl_policy[NFSD_A_EXPKEY_PATH + 1] = { + [NFSD_A_EXPKEY_SEQNO] = { .type = NLA_U64, }, + [NFSD_A_EXPKEY_CLIENT] = { .type = NLA_NUL_STRING, }, + [NFSD_A_EXPKEY_FSIDTYPE] = { .type = NLA_U8, }, + [NFSD_A_EXPKEY_FSID] = { .type = NLA_BINARY, }, + [NFSD_A_EXPKEY_NEGATIVE] = { .type = NLA_FLAG, }, + [NFSD_A_EXPKEY_EXPIRY] = { .type = NLA_U64, }, + [NFSD_A_EXPKEY_PATH] = { .type = NLA_NUL_STRING, }, +}; + const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1] = { [NFSD_A_FSLOCATION_HOST] = { .type = NLA_NUL_STRING, }, [NFSD_A_FSLOCATION_PATH] = { .type = NLA_NUL_STRING, }, @@ -88,6 +98,16 @@ static const struct nla_policy nfsd_svc_export_set_reqs_nl_policy[NFSD_A_SVC_EXP [NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy), }; +/* NFSD_CMD_EXPKEY_GET_REQS - dump */ +static const struct nla_policy nfsd_expkey_get_reqs_nl_policy[NFSD_A_EXPKEY_REQS_REQUESTS + 1] = { + [NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy), +}; + +/* NFSD_CMD_EXPKEY_SET_REQS - do */ +static const struct nla_policy nfsd_expkey_set_reqs_nl_policy[NFSD_A_EXPKEY_REQS_REQUESTS + 1] = { + [NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy), +}; + /* Ops table for nfsd */ static const struct genl_split_ops nfsd_nl_ops[] = { { @@ -157,6 +177,20 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_EXPKEY_GET_REQS, + .dumpit = nfsd_nl_expkey_get_reqs_dumpit, + .policy = nfsd_expkey_get_reqs_nl_policy, + .maxattr = NFSD_A_EXPKEY_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = NFSD_CMD_EXPKEY_SET_REQS, + .doit = nfsd_nl_expkey_set_reqs_doit, + .policy = nfsd_expkey_set_reqs_nl_policy, + .maxattr = NFSD_A_EXPKEY_REQS_REQUESTS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group nfsd_nl_mcgrps[] = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index d6ed8d9b0bb1..f5b338777285 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -14,6 +14,7 @@ /* Common nested types */ extern const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1]; +extern const struct nla_policy nfsd_expkey_nl_policy[NFSD_A_EXPKEY_PATH + 1]; extern const struct nla_policy nfsd_fslocation_nl_policy[NFSD_A_FSLOCATION_PATH + 1]; extern const struct nla_policy nfsd_fslocations_nl_policy[NFSD_A_FSLOCATIONS_LOCATION + 1]; extern const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1]; @@ -34,6 +35,9 @@ int nfsd_nl_svc_export_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); +int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); enum { NFSD_NLGRP_NONE, diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index 32a551b320e0..fed7533c1533 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -12,6 +12,7 @@ enum nfsd_cache_type { NFSD_CACHE_TYPE_SVC_EXPORT = 1, + NFSD_CACHE_TYPE_EXPKEY = 2, }; /* @@ -176,6 +177,26 @@ enum { NFSD_A_SVC_EXPORT_REQS_MAX = (__NFSD_A_SVC_EXPORT_REQS_MAX - 1) }; +enum { + NFSD_A_EXPKEY_SEQNO = 1, + NFSD_A_EXPKEY_CLIENT, + NFSD_A_EXPKEY_FSIDTYPE, + NFSD_A_EXPKEY_FSID, + NFSD_A_EXPKEY_NEGATIVE, + NFSD_A_EXPKEY_EXPIRY, + NFSD_A_EXPKEY_PATH, + + __NFSD_A_EXPKEY_MAX, + NFSD_A_EXPKEY_MAX = (__NFSD_A_EXPKEY_MAX - 1) +}; + +enum { + NFSD_A_EXPKEY_REQS_REQUESTS = 1, + + __NFSD_A_EXPKEY_REQS_MAX, + NFSD_A_EXPKEY_REQS_MAX = (__NFSD_A_EXPKEY_REQS_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -189,6 +210,8 @@ enum { NFSD_CMD_CACHE_NOTIFY, NFSD_CMD_SVC_EXPORT_GET_REQS, NFSD_CMD_SVC_EXPORT_SET_REQS, + NFSD_CMD_EXPKEY_GET_REQS, + NFSD_CMD_EXPKEY_SET_REQS, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) From 565ee23ea2374a0ec42bb70227447baa81fcd196 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:33 -0400 Subject: [PATCH 016/106] sunrpc: add SUNRPC_CMD_CACHE_FLUSH netlink command Add a new SUNRPC_CMD_CACHE_FLUSH generic netlink command that allows userspace to flush the sunrpc auth caches (ip_map and unix_gid) without writing to /proc/net/rpc/*/flush. An optional SUNRPC_A_CACHE_FLUSH_MASK u32 attribute selects which caches to flush (bit 1 = ip_map, bit 2 = unix_gid). If the attribute is omitted, all sunrpc caches are flushed. This is used by exportfs to replace its /proc-based cache_flush() with a netlink equivalent, with /proc fallback for older kernels. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/sunrpc_cache.yaml | 17 ++++++++++ include/uapi/linux/sunrpc_netlink.h | 8 +++++ net/sunrpc/netlink.c | 12 +++++++ net/sunrpc/netlink.h | 1 + net/sunrpc/svcauth_unix.c | 32 +++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml index ed0ddb61ebcf..55dabc914dbc 100644 --- a/Documentation/netlink/specs/sunrpc_cache.yaml +++ b/Documentation/netlink/specs/sunrpc_cache.yaml @@ -76,6 +76,14 @@ attribute-sets: type: nest nested-attributes: unix-gid multi-attr: true + - + name: cache-flush + attributes: + - + name: mask + type: u32 + enum: cache-type + enum-as-flags: true operations: list: @@ -123,6 +131,15 @@ operations: request: attributes: - requests + - + name: cache-flush + doc: Flush sunrpc caches (ip_map and/or unix_gid) + attribute-set: cache-flush + flags: [admin-perm] + do: + request: + attributes: + - mask mcast-groups: list: diff --git a/include/uapi/linux/sunrpc_netlink.h b/include/uapi/linux/sunrpc_netlink.h index d71c623e92ab..34677f0ec2f9 100644 --- a/include/uapi/linux/sunrpc_netlink.h +++ b/include/uapi/linux/sunrpc_netlink.h @@ -59,12 +59,20 @@ enum { SUNRPC_A_UNIX_GID_REQS_MAX = (__SUNRPC_A_UNIX_GID_REQS_MAX - 1) }; +enum { + SUNRPC_A_CACHE_FLUSH_MASK = 1, + + __SUNRPC_A_CACHE_FLUSH_MAX, + SUNRPC_A_CACHE_FLUSH_MAX = (__SUNRPC_A_CACHE_FLUSH_MAX - 1) +}; + enum { SUNRPC_CMD_CACHE_NOTIFY = 1, SUNRPC_CMD_IP_MAP_GET_REQS, SUNRPC_CMD_IP_MAP_SET_REQS, SUNRPC_CMD_UNIX_GID_GET_REQS, SUNRPC_CMD_UNIX_GID_SET_REQS, + SUNRPC_CMD_CACHE_FLUSH, __SUNRPC_CMD_MAX, SUNRPC_CMD_MAX = (__SUNRPC_CMD_MAX - 1) diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c index 3ac6b0cac5fe..5ccf0967809c 100644 --- a/net/sunrpc/netlink.c +++ b/net/sunrpc/netlink.c @@ -49,6 +49,11 @@ static const struct nla_policy sunrpc_unix_gid_set_reqs_nl_policy[SUNRPC_A_UNIX_ [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), }; +/* SUNRPC_CMD_CACHE_FLUSH - do */ +static const struct nla_policy sunrpc_cache_flush_nl_policy[SUNRPC_A_CACHE_FLUSH_MASK + 1] = { + [SUNRPC_A_CACHE_FLUSH_MASK] = NLA_POLICY_MASK(NLA_U32, 0x3), +}; + /* Ops table for sunrpc */ static const struct genl_split_ops sunrpc_nl_ops[] = { { @@ -79,6 +84,13 @@ static const struct genl_split_ops sunrpc_nl_ops[] = { .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = SUNRPC_CMD_CACHE_FLUSH, + .doit = sunrpc_nl_cache_flush_doit, + .policy = sunrpc_cache_flush_nl_policy, + .maxattr = SUNRPC_A_CACHE_FLUSH_MASK, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group sunrpc_nl_mcgrps[] = { diff --git a/net/sunrpc/netlink.h b/net/sunrpc/netlink.h index 2aec57d27a58..2c1012303d48 100644 --- a/net/sunrpc/netlink.h +++ b/net/sunrpc/netlink.h @@ -23,6 +23,7 @@ int sunrpc_nl_unix_gid_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); +int sunrpc_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info); enum { SUNRPC_NLGRP_NONE, diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c index 7703523d4246..64a2658faddb 100644 --- a/net/sunrpc/svcauth_unix.c +++ b/net/sunrpc/svcauth_unix.c @@ -818,6 +818,38 @@ int sunrpc_nl_unix_gid_set_reqs_doit(struct sk_buff *skb, return ret; } +/** + * sunrpc_nl_cache_flush_doit - flush sunrpc caches via netlink + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Flush the ip_map and/or unix_gid caches. If SUNRPC_A_CACHE_FLUSH_MASK + * is provided, only flush the caches indicated by the bitmask (bit 1 = + * ip_map, bit 2 = unix_gid). If omitted, flush both. + * + * Return 0 on success or a negative errno. + */ +int sunrpc_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct sunrpc_net *sn; + u32 mask = ~0U; + + sn = net_generic(genl_info_net(info), sunrpc_net_id); + + if (info->attrs[SUNRPC_A_CACHE_FLUSH_MASK]) + mask = nla_get_u32(info->attrs[SUNRPC_A_CACHE_FLUSH_MASK]); + + if ((mask & SUNRPC_CACHE_TYPE_IP_MAP) && + sn->ip_map_cache) + cache_purge(sn->ip_map_cache); + + if ((mask & SUNRPC_CACHE_TYPE_UNIX_GID) && + sn->unix_gid_cache) + cache_purge(sn->unix_gid_cache); + + return 0; +} + static const struct cache_detail unix_gid_cache_template = { .owner = THIS_MODULE, .hash_size = GID_HASHMAX, From 70b7e3526c53d9dd7caccdbeff5b0485640d8cf1 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Wed, 25 Mar 2026 10:40:34 -0400 Subject: [PATCH 017/106] nfsd: add NFSD_CMD_CACHE_FLUSH netlink command Add a new NFSD_CMD_CACHE_FLUSH generic netlink command that allows userspace to flush the nfsd export caches (svc_export and expkey) without writing to /proc/net/rpc/*/flush. An optional NFSD_A_CACHE_FLUSH_MASK u32 attribute selects which caches to flush (bit 1 = svc_export, bit 2 = expkey). If the attribute is omitted, all nfsd caches are flushed. This is used by exportfs to replace its /proc-based cache_flush() with a netlink equivalent, with /proc fallback for older kernels. Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 17 +++++++++++++ fs/nfsd/netlink.c | 12 +++++++++ fs/nfsd/netlink.h | 1 + fs/nfsd/nfsctl.c | 36 +++++++++++++++++++++++++++ include/uapi/linux/nfsd_netlink.h | 8 ++++++ 5 files changed, 74 insertions(+) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 7030b40273fd..40eca7c15680 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -293,6 +293,14 @@ attribute-sets: type: nest nested-attributes: expkey multi-attr: true + - + name: cache-flush + attributes: + - + name: mask + type: u32 + enum: cache-type + enum-as-flags: true operations: list: @@ -438,6 +446,15 @@ operations: request: attributes: - requests + - + name: cache-flush + doc: Flush nfsd caches (svc_export and/or expkey) + attribute-set: cache-flush + flags: [admin-perm] + do: + request: + attributes: + - mask mcast-groups: list: diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index 394230e250a5..30c4f8be3df9 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -108,6 +108,11 @@ static const struct nla_policy nfsd_expkey_set_reqs_nl_policy[NFSD_A_EXPKEY_REQS [NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy), }; +/* NFSD_CMD_CACHE_FLUSH - do */ +static const struct nla_policy nfsd_cache_flush_nl_policy[NFSD_A_CACHE_FLUSH_MASK + 1] = { + [NFSD_A_CACHE_FLUSH_MASK] = NLA_POLICY_MASK(NLA_U32, 0x3), +}; + /* Ops table for nfsd */ static const struct genl_split_ops nfsd_nl_ops[] = { { @@ -191,6 +196,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .maxattr = NFSD_A_EXPKEY_REQS_REQUESTS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_CACHE_FLUSH, + .doit = nfsd_nl_cache_flush_doit, + .policy = nfsd_cache_flush_nl_policy, + .maxattr = NFSD_A_CACHE_FLUSH_MASK, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group nfsd_nl_mcgrps[] = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index f5b338777285..cc89732ed71b 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -38,6 +38,7 @@ int nfsd_nl_svc_export_set_reqs_doit(struct sk_buff *skb, int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info); enum { NFSD_NLGRP_NONE, diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index c93cf82e3647..bfb937e8ad7c 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -2212,6 +2213,41 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info) return err; } +/** + * nfsd_nl_cache_flush_doit - flush nfsd caches via netlink + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Flush the svc_export and/or expkey caches. If NFSD_A_CACHE_FLUSH_MASK + * is provided, only flush the caches indicated by the bitmask (bit 0 = + * svc_export, bit 1 = expkey). If omitted, flush both. + * + * Return 0 on success or a negative errno. + */ +int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + u32 mask = ~0U; + + if (info->attrs[NFSD_A_CACHE_FLUSH_MASK]) + mask = nla_get_u32(info->attrs[NFSD_A_CACHE_FLUSH_MASK]); + + mutex_lock(&nfsd_mutex); + + if ((mask & NFSD_CACHE_TYPE_SVC_EXPORT) && + nn->svc_export_cache) + cache_purge(nn->svc_export_cache); + + if ((mask & NFSD_CACHE_TYPE_EXPKEY) && + nn->svc_expkey_cache) + cache_purge(nn->svc_expkey_cache); + + mutex_unlock(&nfsd_mutex); + + return 0; +} + int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_type) { struct genlmsghdr *hdr; diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index fed7533c1533..060c43675599 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -197,6 +197,13 @@ enum { NFSD_A_EXPKEY_REQS_MAX = (__NFSD_A_EXPKEY_REQS_MAX - 1) }; +enum { + NFSD_A_CACHE_FLUSH_MASK = 1, + + __NFSD_A_CACHE_FLUSH_MAX, + NFSD_A_CACHE_FLUSH_MAX = (__NFSD_A_CACHE_FLUSH_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -212,6 +219,7 @@ enum { NFSD_CMD_SVC_EXPORT_SET_REQS, NFSD_CMD_EXPKEY_GET_REQS, NFSD_CMD_EXPKEY_SET_REQS, + NFSD_CMD_CACHE_FLUSH, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) From 80ec8054fe14ec024ab2e2073cb4a88d20abe7c0 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 23 Apr 2026 13:13:14 -0400 Subject: [PATCH 018/106] NFSD: Put cache get-reqs dump attrs under reply The new get-reqs dump operations added to sunrpc_cache.yaml and nfsd.yaml place the "requests" nested attribute under dump.request. A netlink dump carries an empty request; its payload travels back in the reply. Because the spec names no reply attributes, the YNL C code generator synthesizes a forward reference to a _rsp struct that is never defined, breaking any consumer of these specs. This first surfaced when Thorsten Leemhuis built tools/net/ynl against -next: nfsd-user.h:746: error: field 'obj' has incomplete type struct nfsd_svc_export_get_reqs_rsp obj ... nfsd-user.h:826: error: field 'obj' has incomplete type struct nfsd_expkey_get_reqs_rsp obj ... nfsd-user.c:1211: error: 'nfsd_svc_export_get_reqs_rsp_parse' undeclared sunrpc_cache.yaml has the same defect in ip-map-get-reqs and unix-gid-get-reqs, but nfsd.yaml errors out first in the Makefile's alphabetical build order and hides the sunrpc failures. These bugs were introduced by incorrect merge conflict resolution. Reported-by: Thorsten Leemhuis Closes: https://lore.kernel.org/linux-nfs/f6a3ca6d-e5cb-4a5c-9af2-8d2b1ce33ef0@leemhuis.info/ Fixes: 1045ccf519ce30 ("sunrpc: add netlink upcall for the auth.unix.ip cache") Tested-by: Thorsten Leemhuis Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 4 +-- Documentation/netlink/specs/sunrpc_cache.yaml | 4 +-- fs/nfsd/netlink.c | 26 +++++-------------- net/sunrpc/netlink.c | 26 +++++-------------- 4 files changed, 16 insertions(+), 44 deletions(-) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 40eca7c15680..25497b533185 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -416,7 +416,7 @@ operations: attribute-set: svc-export-reqs flags: [admin-perm] dump: - request: + reply: attributes: - requests - @@ -434,7 +434,7 @@ operations: attribute-set: expkey-reqs flags: [admin-perm] dump: - request: + reply: attributes: - requests - diff --git a/Documentation/netlink/specs/sunrpc_cache.yaml b/Documentation/netlink/specs/sunrpc_cache.yaml index 55dabc914dbc..f22ff22b9418 100644 --- a/Documentation/netlink/specs/sunrpc_cache.yaml +++ b/Documentation/netlink/specs/sunrpc_cache.yaml @@ -101,7 +101,7 @@ operations: attribute-set: ip-map-reqs flags: [admin-perm] dump: - request: + reply: attributes: - requests - @@ -119,7 +119,7 @@ operations: attribute-set: unix-gid-reqs flags: [admin-perm] dump: - request: + reply: attributes: - requests - diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index 30c4f8be3df9..f99add477cc7 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -88,21 +88,11 @@ static const struct nla_policy nfsd_pool_mode_set_nl_policy[NFSD_A_POOL_MODE_MOD [NFSD_A_POOL_MODE_MODE] = { .type = NLA_NUL_STRING, }, }; -/* NFSD_CMD_SVC_EXPORT_GET_REQS - dump */ -static const struct nla_policy nfsd_svc_export_get_reqs_nl_policy[NFSD_A_SVC_EXPORT_REQS_REQUESTS + 1] = { - [NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy), -}; - /* NFSD_CMD_SVC_EXPORT_SET_REQS - do */ static const struct nla_policy nfsd_svc_export_set_reqs_nl_policy[NFSD_A_SVC_EXPORT_REQS_REQUESTS + 1] = { [NFSD_A_SVC_EXPORT_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_svc_export_nl_policy), }; -/* NFSD_CMD_EXPKEY_GET_REQS - dump */ -static const struct nla_policy nfsd_expkey_get_reqs_nl_policy[NFSD_A_EXPKEY_REQS_REQUESTS + 1] = { - [NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy), -}; - /* NFSD_CMD_EXPKEY_SET_REQS - do */ static const struct nla_policy nfsd_expkey_set_reqs_nl_policy[NFSD_A_EXPKEY_REQS_REQUESTS + 1] = { [NFSD_A_EXPKEY_REQS_REQUESTS] = NLA_POLICY_NESTED(nfsd_expkey_nl_policy), @@ -169,11 +159,9 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .flags = GENL_CMD_CAP_DO, }, { - .cmd = NFSD_CMD_SVC_EXPORT_GET_REQS, - .dumpit = nfsd_nl_svc_export_get_reqs_dumpit, - .policy = nfsd_svc_export_get_reqs_nl_policy, - .maxattr = NFSD_A_SVC_EXPORT_REQS_REQUESTS, - .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + .cmd = NFSD_CMD_SVC_EXPORT_GET_REQS, + .dumpit = nfsd_nl_svc_export_get_reqs_dumpit, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, }, { .cmd = NFSD_CMD_SVC_EXPORT_SET_REQS, @@ -183,11 +171,9 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { - .cmd = NFSD_CMD_EXPKEY_GET_REQS, - .dumpit = nfsd_nl_expkey_get_reqs_dumpit, - .policy = nfsd_expkey_get_reqs_nl_policy, - .maxattr = NFSD_A_EXPKEY_REQS_REQUESTS, - .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + .cmd = NFSD_CMD_EXPKEY_GET_REQS, + .dumpit = nfsd_nl_expkey_get_reqs_dumpit, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, }, { .cmd = NFSD_CMD_EXPKEY_SET_REQS, diff --git a/net/sunrpc/netlink.c b/net/sunrpc/netlink.c index 5ccf0967809c..ce09ecc0faa2 100644 --- a/net/sunrpc/netlink.c +++ b/net/sunrpc/netlink.c @@ -29,21 +29,11 @@ const struct nla_policy sunrpc_unix_gid_nl_policy[SUNRPC_A_UNIX_GID_EXPIRY + 1] [SUNRPC_A_UNIX_GID_EXPIRY] = { .type = NLA_U64, }, }; -/* SUNRPC_CMD_IP_MAP_GET_REQS - dump */ -static const struct nla_policy sunrpc_ip_map_get_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = { - [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), -}; - /* SUNRPC_CMD_IP_MAP_SET_REQS - do */ static const struct nla_policy sunrpc_ip_map_set_reqs_nl_policy[SUNRPC_A_IP_MAP_REQS_REQUESTS + 1] = { [SUNRPC_A_IP_MAP_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_ip_map_nl_policy), }; -/* SUNRPC_CMD_UNIX_GID_GET_REQS - dump */ -static const struct nla_policy sunrpc_unix_gid_get_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = { - [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), -}; - /* SUNRPC_CMD_UNIX_GID_SET_REQS - do */ static const struct nla_policy sunrpc_unix_gid_set_reqs_nl_policy[SUNRPC_A_UNIX_GID_REQS_REQUESTS + 1] = { [SUNRPC_A_UNIX_GID_REQS_REQUESTS] = NLA_POLICY_NESTED(sunrpc_unix_gid_nl_policy), @@ -57,11 +47,9 @@ static const struct nla_policy sunrpc_cache_flush_nl_policy[SUNRPC_A_CACHE_FLUSH /* Ops table for sunrpc */ static const struct genl_split_ops sunrpc_nl_ops[] = { { - .cmd = SUNRPC_CMD_IP_MAP_GET_REQS, - .dumpit = sunrpc_nl_ip_map_get_reqs_dumpit, - .policy = sunrpc_ip_map_get_reqs_nl_policy, - .maxattr = SUNRPC_A_IP_MAP_REQS_REQUESTS, - .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + .cmd = SUNRPC_CMD_IP_MAP_GET_REQS, + .dumpit = sunrpc_nl_ip_map_get_reqs_dumpit, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, }, { .cmd = SUNRPC_CMD_IP_MAP_SET_REQS, @@ -71,11 +59,9 @@ static const struct genl_split_ops sunrpc_nl_ops[] = { .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { - .cmd = SUNRPC_CMD_UNIX_GID_GET_REQS, - .dumpit = sunrpc_nl_unix_gid_get_reqs_dumpit, - .policy = sunrpc_unix_gid_get_reqs_nl_policy, - .maxattr = SUNRPC_A_UNIX_GID_REQS_REQUESTS, - .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + .cmd = SUNRPC_CMD_UNIX_GID_GET_REQS, + .dumpit = sunrpc_nl_unix_gid_get_reqs_dumpit, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, }, { .cmd = SUNRPC_CMD_UNIX_GID_SET_REQS, From c8ae4aef6d50aca8412a80a7e2d517a233b75dbd Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 31 Mar 2026 11:35:57 -0400 Subject: [PATCH 019/106] NFSD: Update my maintainer email addresses Signed-off-by: Chuck Lever --- .mailmap | 6 +++--- MAINTAINERS | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.mailmap b/.mailmap index a009f73d7ea5..496d6598468a 100644 --- a/.mailmap +++ b/.mailmap @@ -201,9 +201,9 @@ Christophe Ricard Christopher Obbard Christoph Hellwig Christoph Manszewski -Chuck Lever -Chuck Lever -Chuck Lever +Chuck Lever +Chuck Lever +Chuck Lever Claudiu Beznea Colin Ian King Corey Minyard diff --git a/MAINTAINERS b/MAINTAINERS index 987aa067cc4e..a5b1a5a8aa5d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9866,7 +9866,7 @@ F: include/uapi/scsi/fc/ FILE LOCKING (flock() and fcntl()/lockf()) M: Jeff Layton -M: Chuck Lever +M: Chuck Lever R: Alexander Aring L: linux-fsdevel@vger.kernel.org S: Maintained @@ -9907,7 +9907,7 @@ F: init/do_mounts* F: init/*initramfs* FILESYSTEMS [EXPORTFS] -M: Chuck Lever +M: Chuck Lever M: Jeff Layton R: Amir Goldstein L: linux-fsdevel@vger.kernel.org @@ -11255,7 +11255,7 @@ Q: http://patchwork.linuxtv.org/project/linux-media/list/ F: drivers/media/usb/hackrf/ HANDSHAKE UPCALL FOR TRANSPORT LAYER SECURITY -M: Chuck Lever +M: Chuck Lever L: kernel-tls-handshake@lists.linux.dev L: netdev@vger.kernel.org S: Maintained @@ -13949,7 +13949,7 @@ S: Odd Fixes W: http://kernelnewbies.org/KernelJanitors KERNEL NFSD, SUNRPC, AND LOCKD SERVERS -M: Chuck Lever +M: Chuck Lever M: Jeff Layton R: NeilBrown R: Olga Kornievskaia From 86b9898920a6d02b4149f4fef9efd77b8aa3b9ca Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:00 -0400 Subject: [PATCH 020/106] NFSD: Handle layout stid in nfsd4_drop_revoked_stid() nfsd4_drop_revoked_stid() has no SC_TYPE_LAYOUT case, so when a client sends FREE_STATEID for an admin-revoked layout stid, the default branch releases cl_lock and returns without unhashing or releasing the stid. The stid remains in the IDR and on the per-client list until the client is destroyed. Remove the layout stid from the per-client list and call nfs4_put_stid() to drop the creation reference. When the refcount reaches zero, nfsd4_free_layout_stateid() handles the remaining cleanup: cancelling the fence worker, removing from the per-file list, and freeing the slab object. Fixes: 1e33e1414bec ("nfsd: allow layout state to be admin-revoked.") Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 3c2eb03f78c6..ed20f6102117 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -5056,6 +5056,7 @@ static void nfsd4_drop_revoked_stid(struct nfs4_stid *s) { struct nfs4_client *cl = s->sc_client; LIST_HEAD(reaplist); + struct nfs4_layout_stateid *ls; struct nfs4_ol_stateid *stp; struct nfs4_delegation *dp; bool unhashed; @@ -5081,6 +5082,12 @@ static void nfsd4_drop_revoked_stid(struct nfs4_stid *s) spin_unlock(&cl->cl_lock); nfs4_put_stid(s); break; + case SC_TYPE_LAYOUT: + ls = layoutstateid(s); + list_del_init(&ls->ls_perclnt); + spin_unlock(&cl->cl_lock); + nfs4_put_stid(s); + break; default: spin_unlock(&cl->cl_lock); } From 0a7c2aa5844c9ad57bee914d46bc1f84b2fd96a0 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:01 -0400 Subject: [PATCH 021/106] NFSD: Extract revoke_one_stid() utility function The per-stateid revocation logic in nfsd4_revoke_states() handles four stateid types in a deeply nested switch. Extract two helpers: revoke_ol_stid() performs admin-revocation of an open or lock stateid with st_mutex already held: marks the stateid as SC_STATUS_ADMIN_REVOKED, closes POSIX locks for lock stateids, and releases file access. revoke_one_stid() dispatches by sc_type, acquires st_mutex with the appropriate lockdep class for open and lock stateids, and handles delegation unhash and layout close inline. No functional change. Preparation for adding export-scoped state revocation which reuses revoke_one_stid(). Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 151 ++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 76 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index ed20f6102117..268aa1e5d19d 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1761,6 +1761,80 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp, return stid; } +static void revoke_ol_stid(struct nfs4_client *clp, + struct nfs4_ol_stateid *stp) +{ + struct nfs4_stid *stid = &stp->st_stid; + + lockdep_assert_held(&stp->st_mutex); + spin_lock(&clp->cl_lock); + if (stid->sc_status == 0) { + stid->sc_status |= SC_STATUS_ADMIN_REVOKED; + atomic_inc(&clp->cl_admin_revoked); + spin_unlock(&clp->cl_lock); + if (stid->sc_type == SC_TYPE_LOCK) { + struct nfs4_lockowner *lo = + lockowner(stp->st_stateowner); + struct nfsd_file *nf; + + nf = find_any_file(stp->st_stid.sc_file); + if (nf) { + get_file(nf->nf_file); + filp_close(nf->nf_file, (fl_owner_t)lo); + nfsd_file_put(nf); + } + } + release_all_access(stp); + } else + spin_unlock(&clp->cl_lock); +} + +static void revoke_one_stid(struct nfsd_net *nn, struct nfs4_client *clp, + struct nfs4_stid *stid) +{ + struct nfs4_ol_stateid *stp; + struct nfs4_delegation *dp; + + switch (stid->sc_type) { + case SC_TYPE_OPEN: + stp = openlockstateid(stid); + mutex_lock_nested(&stp->st_mutex, OPEN_STATEID_MUTEX); + revoke_ol_stid(clp, stp); + mutex_unlock(&stp->st_mutex); + break; + case SC_TYPE_LOCK: + stp = openlockstateid(stid); + mutex_lock_nested(&stp->st_mutex, LOCK_STATEID_MUTEX); + revoke_ol_stid(clp, stp); + mutex_unlock(&stp->st_mutex); + break; + case SC_TYPE_DELEG: + /* + * Extra reference guards against concurrent FREE_STATEID. + */ + refcount_inc(&stid->sc_count); + dp = delegstateid(stid); + spin_lock(&nn->deleg_lock); + if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED)) + dp = NULL; + spin_unlock(&nn->deleg_lock); + if (dp) + revoke_delegation(dp); + else + nfs4_put_stid(stid); + break; + case SC_TYPE_LAYOUT: + spin_lock(&clp->cl_lock); + if (stid->sc_status == 0) { + stid->sc_status |= SC_STATUS_ADMIN_REVOKED; + atomic_inc(&clp->cl_admin_revoked); + } + spin_unlock(&clp->cl_lock); + nfsd4_close_layout(layoutstateid(stid)); + break; + } +} + /** * nfsd4_revoke_states - revoke all nfsv4 states associated with given filesystem * @nn: used to identify instance of nfsd (there is one per net namespace) @@ -1791,83 +1865,8 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) struct nfs4_stid *stid = find_one_sb_stid(clp, sb, sc_types); if (stid) { - struct nfs4_ol_stateid *stp; - struct nfs4_delegation *dp; - struct nfs4_layout_stateid *ls; - spin_unlock(&nn->client_lock); - switch (stid->sc_type) { - case SC_TYPE_OPEN: - stp = openlockstateid(stid); - mutex_lock_nested(&stp->st_mutex, - OPEN_STATEID_MUTEX); - - spin_lock(&clp->cl_lock); - if (stid->sc_status == 0) { - stid->sc_status |= - SC_STATUS_ADMIN_REVOKED; - atomic_inc(&clp->cl_admin_revoked); - spin_unlock(&clp->cl_lock); - release_all_access(stp); - } else - spin_unlock(&clp->cl_lock); - mutex_unlock(&stp->st_mutex); - break; - case SC_TYPE_LOCK: - stp = openlockstateid(stid); - mutex_lock_nested(&stp->st_mutex, - LOCK_STATEID_MUTEX); - spin_lock(&clp->cl_lock); - if (stid->sc_status == 0) { - struct nfs4_lockowner *lo = - lockowner(stp->st_stateowner); - struct nfsd_file *nf; - - stid->sc_status |= - SC_STATUS_ADMIN_REVOKED; - atomic_inc(&clp->cl_admin_revoked); - spin_unlock(&clp->cl_lock); - nf = find_any_file(stp->st_stid.sc_file); - if (nf) { - get_file(nf->nf_file); - filp_close(nf->nf_file, - (fl_owner_t)lo); - nfsd_file_put(nf); - } - release_all_access(stp); - } else - spin_unlock(&clp->cl_lock); - mutex_unlock(&stp->st_mutex); - break; - case SC_TYPE_DELEG: - /* Extra reference guards against concurrent - * FREE_STATEID; revoke_delegation() consumes - * it, otherwise release it directly. - */ - refcount_inc(&stid->sc_count); - dp = delegstateid(stid); - spin_lock(&nn->deleg_lock); - if (!unhash_delegation_locked( - dp, SC_STATUS_ADMIN_REVOKED)) - dp = NULL; - spin_unlock(&nn->deleg_lock); - if (dp) - revoke_delegation(dp); - else - nfs4_put_stid(stid); - break; - case SC_TYPE_LAYOUT: - ls = layoutstateid(stid); - spin_lock(&clp->cl_lock); - if (stid->sc_status == 0) { - stid->sc_status |= - SC_STATUS_ADMIN_REVOKED; - atomic_inc(&clp->cl_admin_revoked); - } - spin_unlock(&clp->cl_lock); - nfsd4_close_layout(ls); - break; - } + revoke_one_stid(nn, clp, stid); nfs4_put_stid(stid); spin_lock(&nn->client_lock); if (clp->cl_minorversion == 0) From 978cda83de411fcbff22ac5b2b0024cae7df806f Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:02 -0400 Subject: [PATCH 022/106] NFSD: Add NFSD_CMD_UNLOCK_IP netlink command The existing write_unlock_ip procfs interface releases NLM file locks held by a specific client IP address, but procfs provides no structured way to extend that operation to other scopes such as revoking NFSv4 state. Add NFSD_CMD_UNLOCK_IP as a dedicated netlink command for releasing NLM locks by client address. The command accepts a binary sockaddr_in or sockaddr_in6 in its address attribute. The handler validates the address family and length, then calls nlmsvc_unlock_all_by_ip() to release matching NLM locks. Because lockd is a single global instance, that call operates across all network namespaces regardless of which namespace the caller inhabits. A separate netlink command for filesystem-scoped unlock is added in a subsequent commit. The nfsd_ctl_unlock_ip tracepoint is updated from string-based address logging to __sockaddr, which stores the binary sockaddr and formats it with %pISpc. This affects both the new netlink path and the existing procfs write_unlock_ip path, giving consistent structured output in both cases. Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 18 ++++++++++++ fs/nfsd/netlink.c | 12 ++++++++ fs/nfsd/netlink.h | 1 + fs/nfsd/nfsctl.c | 40 ++++++++++++++++++++++++++- fs/nfsd/trace.h | 13 +++++---- include/uapi/linux/nfsd_netlink.h | 8 ++++++ 6 files changed, 85 insertions(+), 7 deletions(-) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index 25497b533185..bf41682464c3 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -301,6 +301,15 @@ attribute-sets: type: u32 enum: cache-type enum-as-flags: true + - + name: unlock-ip + attributes: + - + name: address + type: binary + doc: struct sockaddr_in or struct sockaddr_in6. + checks: + min-len: 16 operations: list: @@ -455,6 +464,15 @@ operations: request: attributes: - mask + - + name: unlock-ip + doc: release NLM locks held by an IP address + attribute-set: unlock-ip + flags: [admin-perm] + do: + request: + attributes: + - address mcast-groups: list: diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index f99add477cc7..5830627c0288 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -103,6 +103,11 @@ static const struct nla_policy nfsd_cache_flush_nl_policy[NFSD_A_CACHE_FLUSH_MAS [NFSD_A_CACHE_FLUSH_MASK] = NLA_POLICY_MASK(NLA_U32, 0x3), }; +/* NFSD_CMD_UNLOCK_IP - do */ +static const struct nla_policy nfsd_unlock_ip_nl_policy[NFSD_A_UNLOCK_IP_ADDRESS + 1] = { + [NFSD_A_UNLOCK_IP_ADDRESS] = NLA_POLICY_MIN_LEN(16), +}; + /* Ops table for nfsd */ static const struct genl_split_ops nfsd_nl_ops[] = { { @@ -189,6 +194,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .maxattr = NFSD_A_CACHE_FLUSH_MASK, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_UNLOCK_IP, + .doit = nfsd_nl_unlock_ip_doit, + .policy = nfsd_unlock_ip_nl_policy, + .maxattr = NFSD_A_UNLOCK_IP_ADDRESS, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group nfsd_nl_mcgrps[] = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index cc89732ed71b..88edbbc68453 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -39,6 +39,7 @@ int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb, struct netlink_callback *cb); int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info); enum { NFSD_NLGRP_NONE, diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index bfb937e8ad7c..cf246652e478 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -247,7 +247,7 @@ static ssize_t write_unlock_ip(struct file *file, char *buf, size_t size) if (rpc_pton(net, fo_path, size, sap, salen) == 0) return -EINVAL; - trace_nfsd_ctl_unlock_ip(net, buf); + trace_nfsd_ctl_unlock_ip(net, sap, svc_addr_len(sap)); return nlmsvc_unlock_all_by_ip(sap); } @@ -2276,6 +2276,44 @@ int nfsd_cache_notify(struct cache_detail *cd, struct cache_head *h, u32 cache_t NFSD_NLGRP_EXPORTD, GFP_KERNEL); } +/** + * nfsd_nl_unlock_ip_doit - release NLM locks held by an IP address + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Return: 0 on success or a negative errno. + */ +int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct sockaddr *sap; + + if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_IP_ADDRESS)) + return -EINVAL; + sap = nla_data(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]); + switch (sap->sa_family) { + case AF_INET: + if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) < + sizeof(struct sockaddr_in)) + return -EINVAL; + break; + case AF_INET6: + if (nla_len(info->attrs[NFSD_A_UNLOCK_IP_ADDRESS]) < + sizeof(struct sockaddr_in6)) + return -EINVAL; + break; + default: + return -EAFNOSUPPORT; + } + /* + * nlmsvc_unlock_all_by_ip() releases matching locks + * across all network namespaces because lockd operates + * a single global instance. + */ + trace_nfsd_ctl_unlock_ip(genl_info_net(info), sap, + svc_addr_len(sap)); + return nlmsvc_unlock_all_by_ip(sap); +} + /** * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace * @net: a freshly-created network namespace diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index b631a472222b..9a73e7a1acf2 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -1985,19 +1985,20 @@ TRACE_EVENT(nfsd_cb_recall_any_done, TRACE_EVENT(nfsd_ctl_unlock_ip, TP_PROTO( const struct net *net, - const char *address + const struct sockaddr *addr, + const unsigned int addrlen ), - TP_ARGS(net, address), + TP_ARGS(net, addr, addrlen), TP_STRUCT__entry( __field(unsigned int, netns_ino) - __string(address, address) + __sockaddr(addr, addrlen) ), TP_fast_assign( __entry->netns_ino = net->ns.inum; - __assign_str(address); + __assign_sockaddr(addr, addr, addrlen); ), - TP_printk("address=%s", - __get_str(address) + TP_printk("addr=%pISpc", + __get_sockaddr(addr) ) ); diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index 060c43675599..90ef1e686769 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -204,6 +204,13 @@ enum { NFSD_A_CACHE_FLUSH_MAX = (__NFSD_A_CACHE_FLUSH_MAX - 1) }; +enum { + NFSD_A_UNLOCK_IP_ADDRESS = 1, + + __NFSD_A_UNLOCK_IP_MAX, + NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -220,6 +227,7 @@ enum { NFSD_CMD_EXPKEY_GET_REQS, NFSD_CMD_EXPKEY_SET_REQS, NFSD_CMD_CACHE_FLUSH, + NFSD_CMD_UNLOCK_IP, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) From 327c5168eff2e7f1760ca67fcb0f9053019fbfee Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:03 -0400 Subject: [PATCH 023/106] NFSD: Add NFSD_CMD_UNLOCK_FILESYSTEM netlink command Add NFSD_CMD_UNLOCK_FILESYSTEM as a dedicated netlink command for revoking NFS state under a filesystem path, providing a netlink equivalent of /proc/fs/nfsd/unlock_fs. The command requires a "path" string attribute containing the filesystem path whose state should be released. The handler resolves the path to its superblock, then cancels async copies, releases NLM locks, and revokes NFSv4 state on that superblock. Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 16 +++++++++++ fs/nfsd/netlink.c | 12 ++++++++ fs/nfsd/netlink.h | 1 + fs/nfsd/nfsctl.c | 40 +++++++++++++++++++++++++++ include/uapi/linux/nfsd_netlink.h | 8 ++++++ 5 files changed, 77 insertions(+) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index bf41682464c3..e121c54033a0 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -310,6 +310,13 @@ attribute-sets: doc: struct sockaddr_in or struct sockaddr_in6. checks: min-len: 16 + - + name: unlock-filesystem + attributes: + - + name: path + type: string + doc: Filesystem path whose state should be released. operations: list: @@ -473,6 +480,15 @@ operations: request: attributes: - address + - + name: unlock-filesystem + doc: revoke NFS state under a filesystem path + attribute-set: unlock-filesystem + flags: [admin-perm] + do: + request: + attributes: + - path mcast-groups: list: diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index 5830627c0288..63df3c4cf63a 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -108,6 +108,11 @@ static const struct nla_policy nfsd_unlock_ip_nl_policy[NFSD_A_UNLOCK_IP_ADDRESS [NFSD_A_UNLOCK_IP_ADDRESS] = NLA_POLICY_MIN_LEN(16), }; +/* NFSD_CMD_UNLOCK_FILESYSTEM - do */ +static const struct nla_policy nfsd_unlock_filesystem_nl_policy[NFSD_A_UNLOCK_FILESYSTEM_PATH + 1] = { + [NFSD_A_UNLOCK_FILESYSTEM_PATH] = { .type = NLA_NUL_STRING, }, +}; + /* Ops table for nfsd */ static const struct genl_split_ops nfsd_nl_ops[] = { { @@ -201,6 +206,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .maxattr = NFSD_A_UNLOCK_IP_ADDRESS, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_UNLOCK_FILESYSTEM, + .doit = nfsd_nl_unlock_filesystem_doit, + .policy = nfsd_unlock_filesystem_nl_policy, + .maxattr = NFSD_A_UNLOCK_FILESYSTEM_PATH, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group nfsd_nl_mcgrps[] = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index 88edbbc68453..29bd5468d401 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -40,6 +40,7 @@ int nfsd_nl_expkey_get_reqs_dumpit(struct sk_buff *skb, int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info); enum { NFSD_NLGRP_NONE, diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index cf246652e478..f7f104cc457d 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2314,6 +2314,46 @@ int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info) return nlmsvc_unlock_all_by_ip(sap); } +/** + * nfsd_nl_unlock_filesystem_doit - revoke NFS state under a filesystem path + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Return: 0 on success or a negative errno. + */ +int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, + struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + struct path path; + int error; + + if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_FILESYSTEM_PATH)) + return -EINVAL; + + trace_nfsd_ctl_unlock_fs(net, + nla_data(info->attrs[NFSD_A_UNLOCK_FILESYSTEM_PATH])); + error = kern_path( + nla_data(info->attrs[NFSD_A_UNLOCK_FILESYSTEM_PATH]), + 0, &path); + if (error) + return error; + + nfsd4_cancel_copy_by_sb(net, path.dentry->d_sb); + error = nlmsvc_unlock_all_by_sb(path.dentry->d_sb); + + mutex_lock(&nfsd_mutex); + if (nn->nfsd_serv) + nfsd4_revoke_states(nn, path.dentry->d_sb); + else + error = -EINVAL; + mutex_unlock(&nfsd_mutex); + + path_put(&path); + return error; +} + /** * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace * @net: a freshly-created network namespace diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index 90ef1e686769..d01096c06d72 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -211,6 +211,13 @@ enum { NFSD_A_UNLOCK_IP_MAX = (__NFSD_A_UNLOCK_IP_MAX - 1) }; +enum { + NFSD_A_UNLOCK_FILESYSTEM_PATH = 1, + + __NFSD_A_UNLOCK_FILESYSTEM_MAX, + NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -228,6 +235,7 @@ enum { NFSD_CMD_EXPKEY_SET_REQS, NFSD_CMD_CACHE_FLUSH, NFSD_CMD_UNLOCK_IP, + NFSD_CMD_UNLOCK_FILESYSTEM, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) From 8fc274883530a9f2c9f69d96e0255df584b5ff61 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:04 -0400 Subject: [PATCH 024/106] NFSD: Replace idr_for_each_entry_ul in find_one_sb_stid() Replace idr_for_each_entry_ul() with a while loop over idr_get_next_ul() for consistency with find_one_export_stid(), added in a subsequent commit. No change in behavior. Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 268aa1e5d19d..d96994627ba8 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1746,17 +1746,19 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp, struct super_block *sb, unsigned int sc_types) { - unsigned long id, tmp; + unsigned long id = 0; struct nfs4_stid *stid; spin_lock(&clp->cl_lock); - idr_for_each_entry_ul(&clp->cl_stateids, stid, tmp, id) + while ((stid = idr_get_next_ul(&clp->cl_stateids, &id)) != NULL) { if ((stid->sc_type & sc_types) && stid->sc_status == 0 && stid->sc_file->fi_inode->i_sb == sb) { refcount_inc(&stid->sc_count); break; } + id++; + } spin_unlock(&clp->cl_lock); return stid; } From ba0cde5dc81d214684b8ea0bd87414ba2f48fe02 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:05 -0400 Subject: [PATCH 025/106] NFSD: Track svc_export in nfs4_stid Add an sc_export field to struct nfs4_stid so that each stateid records the export under which it was acquired. The export reference is taken via exp_get() at stateid creation and released via exp_put() in nfs4_put_stid(). Open stateids record the export from current_fh->fh_export. Lock stateids and delegations inherit the export from their parent open stateid. Layout stateids inherit from their parent stateid. Directory delegations record the export from cstate->current_fh. A subsequent commit uses sc_export to scope state revocation to a specific export, avoiding the need to walk inode dentry aliases at revocation time. Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- fs/nfsd/nfs4layouts.c | 2 ++ fs/nfsd/nfs4state.c | 42 +++++++++++++++++++++++++++++++++++++++--- fs/nfsd/state.h | 1 + 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index c3543d456702..c550b83f4432 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -234,6 +234,8 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate, get_nfs4_file(fp); stp->sc_file = fp; + if (parent->sc_export) + stp->sc_export = exp_get(parent->sc_export); ls = layoutstateid(stp); INIT_LIST_HEAD(&ls->ls_perclnt); diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index d96994627ba8..475d2b36ecd1 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1168,6 +1168,7 @@ alloc_init_deleg(struct nfs4_client *clp, struct nfs4_file *fp, void nfs4_put_stid(struct nfs4_stid *s) { + struct svc_export *exp = s->sc_export; struct nfs4_file *fp = s->sc_file; struct nfs4_client *clp = s->sc_client; @@ -1183,6 +1184,8 @@ nfs4_put_stid(struct nfs4_stid *s) nfs4_free_cpntf_statelist(clp->net, s); spin_unlock(&clp->cl_lock); s->sc_free(s); + if (exp) + exp_put(exp); if (fp) put_nfs4_file(fp); } @@ -1763,6 +1766,25 @@ static struct nfs4_stid *find_one_sb_stid(struct nfs4_client *clp, return stid; } +/* + * Release the export reference an admin-revoked stateid holds, + * so the svc_export (and its vfsmount) is not pinned until the + * client issues FREE_STATEID. sc_export is no longer consulted + * once SC_STATUS_ADMIN_REVOKED is set. + */ +static void drop_stid_export(struct nfs4_client *clp, + struct nfs4_stid *stid) +{ + struct svc_export *exp; + + spin_lock(&clp->cl_lock); + exp = stid->sc_export; + stid->sc_export = NULL; + spin_unlock(&clp->cl_lock); + if (exp) + exp_put(exp); +} + static void revoke_ol_stid(struct nfs4_client *clp, struct nfs4_ol_stateid *stp) { @@ -1787,6 +1809,7 @@ static void revoke_ol_stid(struct nfs4_client *clp, } } release_all_access(stp); + drop_stid_export(clp, stid); } else spin_unlock(&clp->cl_lock); } @@ -1820,9 +1843,10 @@ static void revoke_one_stid(struct nfsd_net *nn, struct nfs4_client *clp, if (!unhash_delegation_locked(dp, SC_STATUS_ADMIN_REVOKED)) dp = NULL; spin_unlock(&nn->deleg_lock); - if (dp) + if (dp) { revoke_delegation(dp); - else + drop_stid_export(clp, stid); + } else nfs4_put_stid(stid); break; case SC_TYPE_LAYOUT: @@ -1833,6 +1857,7 @@ static void revoke_one_stid(struct nfsd_net *nn, struct nfs4_client *clp, } spin_unlock(&clp->cl_lock); nfsd4_close_layout(layoutstateid(stid)); + drop_stid_export(clp, stid); break; } } @@ -6172,6 +6197,8 @@ nfs4_set_delegation(struct nfsd4_open *open, struct nfs4_ol_stateid *stp, dp = alloc_init_deleg(clp, fp, odstate, dl_type); if (!dp) goto out_delegees; + if (stp->st_stid.sc_export) + dp->dl_stid.sc_export = exp_get(stp->st_stid.sc_export); fl = nfs4_alloc_init_lease(dp); if (!fl) @@ -6505,8 +6532,11 @@ nfsd4_process_open2(struct svc_rqst *rqstp, struct svc_fh *current_fh, struct nf goto out; } - if (!open->op_stp) + if (!open->op_stp) { new_stp = true; + stp->st_stid.sc_export = + exp_get(current_fh->fh_export); + } } /* @@ -8202,6 +8232,9 @@ init_lock_stateid(struct nfs4_ol_stateid *stp, struct nfs4_lockowner *lo, stp->st_stateowner = nfs4_get_stateowner(&lo->lo_owner); get_nfs4_file(fp); stp->st_stid.sc_file = fp; + if (open_stp->st_stid.sc_export) + stp->st_stid.sc_export = + exp_get(open_stp->st_stid.sc_export); stp->st_access_bmap = 0; stp->st_deny_bmap = open_stp->st_deny_bmap; stp->st_openstp = open_stp; @@ -9532,6 +9565,9 @@ nfsd_get_dir_deleg(struct nfsd4_compound_state *cstate, dp = alloc_init_deleg(clp, fp, NULL, NFS4_OPEN_DELEGATE_READ); if (!dp) goto out_delegees; + if (cstate->current_fh.fh_export) + dp->dl_stid.sc_export = + exp_get(cstate->current_fh.fh_export); fl = nfs4_alloc_init_lease(dp); if (!fl) diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index c5ccea64c281..43bd965077e1 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -145,6 +145,7 @@ struct nfs4_stid { spinlock_t sc_lock; struct nfs4_client *sc_client; struct nfs4_file *sc_file; + struct svc_export *sc_export; void (*sc_free)(struct nfs4_stid *); }; From 2eac189bb059d31a29937b29ee0f477394198610 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:06 -0400 Subject: [PATCH 026/106] NFSD: Add NFSD_CMD_UNLOCK_EXPORT netlink command When a filesystem is exported to NFS clients, NFSv4 state (opens, locks, delegations, layouts) holds references that prevent the underlying filesystem from being unmounted. NFSD_CMD_UNLOCK_FILESYSTEM addresses this at superblock granularity, but administrators unexporting a single path on a shared filesystem (e.g., one of several exports on the same device) need finer control. Add NFSD_CMD_UNLOCK_EXPORT, which revokes NFSv4 state acquired through exports of a specific path. Matching is by path identity (dentry + vfsmount) via the sc_export field on each nfs4_stid, so multiple svc_export objects for the same path -- one per auth_domain -- are handled correctly without requiring the caller to name a specific client. The command takes a single "path" attribute. Userspace (exportfs -u) sends this after removing the last client for a given path, enabling the underlying filesystem to be unmounted. When multiple clients share an export path, individual unexports do not trigger state revocation; only the final one does. Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- Documentation/netlink/specs/nfsd.yaml | 27 +++++++++++ fs/nfsd/netlink.c | 12 +++++ fs/nfsd/netlink.h | 1 + fs/nfsd/nfs4state.c | 67 +++++++++++++++++++++++++++ fs/nfsd/nfsctl.c | 45 ++++++++++++++++++ fs/nfsd/state.h | 5 ++ fs/nfsd/trace.h | 19 ++++++++ include/uapi/linux/nfsd_netlink.h | 8 ++++ 8 files changed, 184 insertions(+) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index e121c54033a0..8f36fadd68f7 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -317,6 +317,19 @@ attribute-sets: name: path type: string doc: Filesystem path whose state should be released. + - + name: unlock-export + attributes: + - + name: path + type: string + doc: >- + Export path whose NFSv4 state should be revoked. + All state (opens, locks, delegations, layouts) acquired + through any export of this path is revoked, regardless + of which client holds the state. Intended for use after + all clients have been unexported from a given path, + enabling the underlying filesystem to be unmounted. operations: list: @@ -489,6 +502,20 @@ operations: request: attributes: - path + - + name: unlock-export + doc: >- + Revoke NFSv4 state acquired through exports of a given path. + Unlike unlock-filesystem, which operates at superblock granularity, + this command targets only state associated with a specific export + path. Userspace (exportfs -u) sends this after removing the last + client for a path so the underlying filesystem can be unmounted. + attribute-set: unlock-export + flags: [admin-perm] + do: + request: + attributes: + - path mcast-groups: list: diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index 63df3c4cf63a..fbee3676d253 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -113,6 +113,11 @@ static const struct nla_policy nfsd_unlock_filesystem_nl_policy[NFSD_A_UNLOCK_FI [NFSD_A_UNLOCK_FILESYSTEM_PATH] = { .type = NLA_NUL_STRING, }, }; +/* NFSD_CMD_UNLOCK_EXPORT - do */ +static const struct nla_policy nfsd_unlock_export_nl_policy[NFSD_A_UNLOCK_EXPORT_PATH + 1] = { + [NFSD_A_UNLOCK_EXPORT_PATH] = { .type = NLA_NUL_STRING, }, +}; + /* Ops table for nfsd */ static const struct genl_split_ops nfsd_nl_ops[] = { { @@ -213,6 +218,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .maxattr = NFSD_A_UNLOCK_FILESYSTEM_PATH, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_UNLOCK_EXPORT, + .doit = nfsd_nl_unlock_export_doit, + .policy = nfsd_unlock_export_nl_policy, + .maxattr = NFSD_A_UNLOCK_EXPORT_PATH, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group nfsd_nl_mcgrps[] = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index 29bd5468d401..af41aa0d4a65 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -41,6 +41,7 @@ int nfsd_nl_expkey_set_reqs_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_cache_flush_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_ip_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info); enum { NFSD_NLGRP_NONE, diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 475d2b36ecd1..2cf021b202a6 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -1911,6 +1911,73 @@ void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) spin_unlock(&nn->client_lock); } +static struct nfs4_stid *find_one_export_stid(struct nfs4_client *clp, + const struct path *path, + unsigned int sc_types) +{ + unsigned long id = 0; + struct nfs4_stid *stid; + + spin_lock(&clp->cl_lock); + while ((stid = idr_get_next_ul(&clp->cl_stateids, &id)) != NULL) { + if ((stid->sc_type & sc_types) && + stid->sc_status == 0 && + stid->sc_export && + path_equal(&stid->sc_export->ex_path, path)) { + refcount_inc(&stid->sc_count); + break; + } + id++; + } + spin_unlock(&clp->cl_lock); + return stid; +} + +/** + * nfsd4_revoke_export_states - revoke nfsv4 states acquired through an export + * @nn: used to identify instance of nfsd (there is one per net namespace) + * @path: export path whose states should be revoked + * + * All nfs4 states (open, lock, delegation, layout) acquired through any + * export matching @path are revoked, regardless of which client holds + * them. Matching is by path identity (dentry + vfsmount), so multiple + * svc_export objects for the same path -- one per auth_domain -- are + * handled correctly. + * + * Userspace (exportfs -u) sends this after removing the last client + * for a path, enabling the underlying filesystem to be unmounted. + */ +void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path) +{ + unsigned int idhashval; + unsigned int sc_types; + + sc_types = SC_TYPE_OPEN | SC_TYPE_LOCK | SC_TYPE_DELEG | SC_TYPE_LAYOUT; + + spin_lock(&nn->client_lock); + for (idhashval = 0; idhashval < CLIENT_HASH_SIZE; idhashval++) { + struct list_head *head = &nn->conf_id_hashtbl[idhashval]; + struct nfs4_client *clp; + retry: + list_for_each_entry(clp, head, cl_idhash) { + struct nfs4_stid *stid = find_one_export_stid( + clp, path, + sc_types); + if (stid) { + spin_unlock(&nn->client_lock); + revoke_one_stid(nn, clp, stid); + nfs4_put_stid(stid); + spin_lock(&nn->client_lock); + if (clp->cl_minorversion == 0) + nn->nfs40_last_revoke = + ktime_get_boottime_seconds(); + goto retry; + } + } + } + spin_unlock(&nn->client_lock); +} + static inline int hash_sessionid(struct nfs4_sessionid *sessionid) { diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index f7f104cc457d..dd4fb9249213 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2354,6 +2354,51 @@ int nfsd_nl_unlock_filesystem_doit(struct sk_buff *skb, return error; } +/** + * nfsd_nl_unlock_export_doit - revoke NFSv4 state for an export path + * @skb: reply buffer + * @info: netlink metadata and command arguments + * + * Revokes all NFSv4 state (opens, locks, delegations, layouts) acquired + * through any export of the given path, regardless of which client holds + * the state. Userspace (exportfs -u) sends this after removing the last + * client for a path so the underlying filesystem can be unmounted. + * + * Unlike NFSD_CMD_UNLOCK_FILESYSTEM, which operates at superblock + * granularity, this command revokes only the state associated with + * exports of a specific path. + * + * Return: 0 on success or a negative errno. + */ +int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct net *net = genl_info_net(info); + struct nfsd_net *nn = net_generic(net, nfsd_net_id); + struct path path; + int error; + + if (GENL_REQ_ATTR_CHECK(info, NFSD_A_UNLOCK_EXPORT_PATH)) + return -EINVAL; + + trace_nfsd_ctl_unlock_export(net, + nla_data(info->attrs[NFSD_A_UNLOCK_EXPORT_PATH])); + error = kern_path( + nla_data(info->attrs[NFSD_A_UNLOCK_EXPORT_PATH]), + 0, &path); + if (error) + return error; + + mutex_lock(&nfsd_mutex); + if (nn->nfsd_serv) + nfsd4_revoke_export_states(nn, &path); + else + error = -EINVAL; + mutex_unlock(&nfsd_mutex); + + path_put(&path); + return error; +} + /** * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace * @net: a freshly-created network namespace diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index 43bd965077e1..dec83e92650d 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -863,6 +863,7 @@ struct nfsd_file *find_any_file(struct nfs4_file *f); #ifdef CONFIG_NFSD_V4 void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb); +void nfsd4_revoke_export_states(struct nfsd_net *nn, const struct path *path); void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb); int nfsd_net_cb_init(struct nfsd_net *nn); void nfsd_net_cb_shutdown(struct nfsd_net *nn); @@ -870,6 +871,10 @@ void nfsd_net_cb_shutdown(struct nfsd_net *nn); static inline void nfsd4_revoke_states(struct nfsd_net *nn, struct super_block *sb) { } +static inline void nfsd4_revoke_export_states(struct nfsd_net *nn, + const struct path *path) +{ +} static inline void nfsd4_cancel_copy_by_sb(struct net *net, struct super_block *sb) { } diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index 9a73e7a1acf2..1c5a1e50f946 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -2021,6 +2021,25 @@ TRACE_EVENT(nfsd_ctl_unlock_fs, ) ); +TRACE_EVENT(nfsd_ctl_unlock_export, + TP_PROTO( + const struct net *net, + const char *path + ), + TP_ARGS(net, path), + TP_STRUCT__entry( + __field(unsigned int, netns_ino) + __string(path, path) + ), + TP_fast_assign( + __entry->netns_ino = net->ns.inum; + __assign_str(path); + ), + TP_printk("path=%s", + __get_str(path) + ) +); + TRACE_EVENT(nfsd_ctl_filehandle, TP_PROTO( const struct net *net, diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index d01096c06d72..f5b75d5caba9 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/nfsd_netlink.h @@ -218,6 +218,13 @@ enum { NFSD_A_UNLOCK_FILESYSTEM_MAX = (__NFSD_A_UNLOCK_FILESYSTEM_MAX - 1) }; +enum { + NFSD_A_UNLOCK_EXPORT_PATH = 1, + + __NFSD_A_UNLOCK_EXPORT_MAX, + NFSD_A_UNLOCK_EXPORT_MAX = (__NFSD_A_UNLOCK_EXPORT_MAX - 1) +}; + enum { NFSD_CMD_RPC_STATUS_GET = 1, NFSD_CMD_THREADS_SET, @@ -236,6 +243,7 @@ enum { NFSD_CMD_CACHE_FLUSH, NFSD_CMD_UNLOCK_IP, NFSD_CMD_UNLOCK_FILESYSTEM, + NFSD_CMD_UNLOCK_EXPORT, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) From 9870b5cb9218a445d6c28662ae401819db6c4301 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sun, 19 Apr 2026 14:53:07 -0400 Subject: [PATCH 027/106] NFSD: Close cached file handles when revoking export state When NFSD_CMD_UNLOCK_EXPORT revokes NFSv4 state for an export path, GC-managed nfsd_file entries for files under that path may remain in the file cache. These cached handles hold the underlying filesystem busy, preventing a subsequent unmount. Add nfsd_file_close_export(), which walks the nfsd_file hash table and closes GC-eligible entries whose underlying file resides on the same filesystem and is a descendant of the export path. Because nfsd_file entries do not carry an export reference, the ancestry check uses is_subdir() on the file's dentry. False positives -- closing a cached handle that did not originate from the target export -- are harmless; the handle is simply reopened on the next access. The handler calls nfsd_file_close_export() before revoking NFSv4 state, mirroring the order used by NFSD_CMD_UNLOCK_FILESYSTEM (which cancels copies and releases NLM locks before revoking state). Both calls run under nfsd_mutex. Reviewed-by: Jeff Layton Tested-by: Dai Ngo Signed-off-by: Chuck Lever --- fs/nfsd/filecache.c | 46 +++++++++++++++++++++++++++++++++++++++++++++ fs/nfsd/filecache.h | 1 + fs/nfsd/nfsctl.c | 5 +++-- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c index 1e2b38ed1d35..24511c3208db 100644 --- a/fs/nfsd/filecache.c +++ b/fs/nfsd/filecache.c @@ -724,6 +724,52 @@ nfsd_file_close_inode_sync(struct inode *inode) nfsd_file_dispose_list(&dispose); } +/** + * nfsd_file_close_export - close cached file handles for an export + * @net: net namespace in which to operate + * @path: export path whose cached files should be closed + * + * Close out GC-managed nfsd_file entries whose underlying file is on + * the same filesystem as, and a descendant of, @path. nfsd_file + * entries do not carry an export reference, so the check uses the + * file's dentry ancestry. False positives (closing a cached handle + * that did not originate from the target export) are harmless -- the + * handle is simply reopened on the next access. + * + * Called from the NFSD_CMD_UNLOCK_EXPORT handler before revoking + * NFSv4 state, to ensure that cached file handles do not hold the + * filesystem busy. + */ +void nfsd_file_close_export(struct net *net, const struct path *path) +{ + struct rhashtable_iter iter; + struct nfsd_file *nf; + LIST_HEAD(dispose); + + rhltable_walk_enter(&nfsd_file_rhltable, &iter); + do { + rhashtable_walk_start(&iter); + + nf = rhashtable_walk_next(&iter); + while (!IS_ERR_OR_NULL(nf)) { + if (nf->nf_net == net && + test_bit(NFSD_FILE_GC, &nf->nf_flags) && + nf->nf_file && + file_inode(nf->nf_file)->i_sb == + path->dentry->d_sb && + is_subdir(nf->nf_file->f_path.dentry, + path->dentry)) + nfsd_file_cond_queue(nf, &dispose); + nf = rhashtable_walk_next(&iter); + } + + rhashtable_walk_stop(&iter); + } while (nf == ERR_PTR(-EAGAIN)); + rhashtable_walk_exit(&iter); + + nfsd_file_dispose_list(&dispose); +} + static int nfsd_file_lease_notifier_call(struct notifier_block *nb, unsigned long arg, void *data) diff --git a/fs/nfsd/filecache.h b/fs/nfsd/filecache.h index b383dbc5b921..683b6437cacc 100644 --- a/fs/nfsd/filecache.h +++ b/fs/nfsd/filecache.h @@ -70,6 +70,7 @@ struct net *nfsd_file_put_local(struct nfsd_file __rcu **nf); struct nfsd_file *nfsd_file_get(struct nfsd_file *nf); struct file *nfsd_file_file(struct nfsd_file *nf); void nfsd_file_close_inode_sync(struct inode *inode); +void nfsd_file_close_export(struct net *net, const struct path *path); void nfsd_file_net_dispose(struct nfsd_net *nn); bool nfsd_file_is_cached(struct inode *inode); __be32 nfsd_file_acquire_gc(struct svc_rqst *rqstp, struct svc_fh *fhp, diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index dd4fb9249213..92f4c333f0ff 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2389,9 +2389,10 @@ int nfsd_nl_unlock_export_doit(struct sk_buff *skb, struct genl_info *info) return error; mutex_lock(&nfsd_mutex); - if (nn->nfsd_serv) + if (nn->nfsd_serv) { + nfsd_file_close_export(net, &path); nfsd4_revoke_export_states(nn, &path); - else + } else error = -EINVAL; mutex_unlock(&nfsd_mutex); From ae9a0cce07574baf259fbc3f0e34583167db0de1 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 20 Apr 2026 11:38:30 -0400 Subject: [PATCH 028/106] NFSD: Increase the default max_block_size to 4MB Commit 8a81f16de64f ("NFSD: Add a "default" block size") introduced NFSSVC_DEFBLKSIZE at 1MB, well below the 4MB NFSSVC_MAXBLKSIZE ceiling, with the stated intent that a later change would raise the default. Raising the default reduces per-RPC overhead on fast networks by amortizing header processing and scheduling costs across larger payloads. The halving loop in nfsd_get_default_max_blksize() constrains the returned value to 1/4096 of available RAM, so the new 4MB default takes effect only on systems with at least 16GB of RAM. Smaller machines continue to receive the same computed value as before. Administrators can still override the computed value through /proc/fs/nfsd/max_block_size. On systems where the new default takes effect, svc_sock_setbufsize() sizes each service socket's send and receive buffers as nreqs * max_mesg * 2. Quadrupling max_mesg therefore quadruples the per-socket buffer reservation at a fixed thread count, which operators tuning large thread pools should account for. Note well: Your NFS client implementation must support large read and write size settings to benefit from this change. Reviewed-by: Jeff Layton Reviewed-by: Roland Mainz Signed-off-by: Chuck Lever --- fs/nfsd/nfsd.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fs/nfsd/nfsd.h b/fs/nfsd/nfsd.h index 709da3c7a5fa..11bce03b9031 100644 --- a/fs/nfsd/nfsd.h +++ b/fs/nfsd/nfsd.h @@ -45,11 +45,10 @@ bool nfsd_support_version(int vers); /* * Default and maximum payload size (NFS READ or WRITE), in bytes. - * The default is historical, and the maximum is an implementation - * limit. + * The maximum is an implementation limit. */ enum { - NFSSVC_DEFBLKSIZE = 1 * 1024 * 1024, + NFSSVC_DEFBLKSIZE = 4 * 1024 * 1024, NFSSVC_MAXBLKSIZE = RPCSVC_MAXPAYLOAD, }; From bf8672b99402abd8f21392e4ef8f15468854206d Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:45 -0400 Subject: [PATCH 029/106] SUNRPC: Add Kconfig dependency on CRYPTO_KRB5 The rpcsec_gss_krb5 module currently contains its own Kerberos 5 crypto implementation (key derivation, encryption, checksumming) that duplicates functionality available in the common crypto/krb5 library. As a first step toward migrating to that library, add a Kconfig select so that building rpcsec_gss_krb5 pulls in the common Kerberos 5 crypto support. The per-enctype Kconfig options (AES_SHA1, CAMELLIA, AES_SHA2) remain: they continue to gate which encryption types are offered by the GSS mechanism. The individual crypto algorithm selects they carry become redundant once the migration is complete, since CRYPTO_KRB5 already selects all needed ciphers and hashes. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/net/sunrpc/Kconfig b/net/sunrpc/Kconfig index a570e7adf270..381e76975ea9 100644 --- a/net/sunrpc/Kconfig +++ b/net/sunrpc/Kconfig @@ -21,6 +21,7 @@ config RPCSEC_GSS_KRB5 depends on SUNRPC && CRYPTO default y select SUNRPC_GSS + select CRYPTO_KRB5 select CRYPTO_SKCIPHER select CRYPTO_HASH help From f8b942fc380c3b38b8a0e2d97f020b162fa3f3ae Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:46 -0400 Subject: [PATCH 030/106] SUNRPC: Add crypto/krb5 enctype lookup to krb5_ctx Each krb5_ctx currently points to a gss_krb5_enctype, the rpcsec_gss_krb5 module's own enctype descriptor. To begin using the common crypto/krb5 library, store a pointer to the corresponding struct krb5_enctype (from ) as well. The lookup is performed in gss_import_v2_context() immediately after the existing gss_krb5_lookup_enctype() call. If crypto_krb5_find_enctype() cannot find a matching enctype the context import fails, ensuring the module never operates with a partially-initialized krb5_ctx. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_internal.h | 3 +++ net/sunrpc/auth_gss/gss_krb5_mech.c | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 8769e9e705bf..11402c3b4972 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -8,6 +8,8 @@ #ifndef _NET_SUNRPC_AUTH_GSS_KRB5_INTERNAL_H #define _NET_SUNRPC_AUTH_GSS_KRB5_INTERNAL_H +#include + /* * The RFCs often specify payload lengths in bits. This helper * converts a specified bit-length to the number of octets/bytes. @@ -62,6 +64,7 @@ struct krb5_ctx { u32 enctype; u32 flags; const struct gss_krb5_enctype *gk5e; /* enctype-specific info */ + const struct krb5_enctype *krb5e; /* crypto/krb5 enctype */ struct crypto_sync_skcipher *enc; struct crypto_sync_skcipher *seq; struct crypto_sync_skcipher *acceptor_enc; diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 6db64a9111a9..060d8fc4358e 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -432,6 +432,13 @@ gss_import_v2_context(const void *p, const void *end, struct krb5_ctx *ctx, p = ERR_PTR(-EINVAL); goto out_err; } + ctx->krb5e = crypto_krb5_find_enctype(ctx->enctype); + if (!ctx->krb5e) { + dprintk("gss_kerberos_mech: crypto/krb5 missing enctype %u\n", + ctx->enctype); + p = ERR_PTR(-EINVAL); + goto out_err; + } keylen = ctx->gk5e->keylength; p = simple_get_bytes(p, end, ctx->Ksess, keylen); From e9be933959b581effd426f93b86654f5fbf0c574 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:47 -0400 Subject: [PATCH 031/106] SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists The crypto/krb5 library accepts data in scatterlist form, but the GSS-API layer presents RPC payloads as struct xdr_buf. Bridge that gap with a pair of helper functions: xdr_buf_to_sg() - populate a caller-supplied scatterlist array from a byte range xdr_buf_to_sg_alloc() - populate a caller-supplied inline scatterlist, chaining to a heap- allocated overflow for large payloads The inline array (typically stack-allocated at eight entries) covers the common case of small RPCs with no heap allocation on the encrypt/decrypt path. Only buffers spanning many pages incur a kmalloc for the chained extension. The segment-walking logic follows the same head, page array, tail traversal as xdr_process_buf(), but populates a scatterlist directly rather than invoking a per-segment callback. sg_next() traversal makes the walker safe for chained scatterlists. Once subsequent patches reroute all per-message crypto operations through crypto/krb5, xdr_process_buf() loses its last callers and is removed. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- include/linux/sunrpc/xdr.h | 15 +++ net/sunrpc/xdr.c | 199 +++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h index b639a6fafcbc..f82446993fde 100644 --- a/include/linux/sunrpc/xdr.h +++ b/include/linux/sunrpc/xdr.h @@ -140,6 +140,21 @@ int xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp); void xdr_free_bvec(struct xdr_buf *buf); unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, const struct xdr_buf *xdr); +int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset, + unsigned int len, struct scatterlist *sg, unsigned int nsg); +int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset, + unsigned int len, struct scatterlist *sg_head, + unsigned int sg_head_nents, + struct scatterlist **sg_overflow, gfp_t gfp); + +/* + * Inline scatterlist entries for xdr_buf_to_sg_alloc(). Sized to cover the + * head kvec, tail kvec, and a few page fragments without any heap allocation. + */ +enum { + XDR_BUF_TO_SG_NENTS = 8, +}; + static inline __be32 *xdr_encode_array(__be32 *p, const void *s, unsigned int len) { diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index e83d5d0be78b..516833b4c114 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -187,6 +187,205 @@ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, } EXPORT_SYMBOL_GPL(xdr_buf_to_bvec); +/** + * xdr_buf_to_sg - Populate a scatterlist from an xdr_buf range + * @buf: xdr_buf to map + * @offset: starting byte offset within @buf + * @len: number of bytes to cover + * @sg: scatterlist array initialized with sg_init_table() + * @nsg: number of entries available in @sg + * + * @sg is traversed with sg_next(), so callers may pass a list + * assembled with sg_chain(). + * + * Return: on success, the number of scatterlist entries used; the + * last used entry is marked with sg_mark_end(). On failure, a + * negative errno. + */ +int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset, + unsigned int len, struct scatterlist *sg, unsigned int nsg) +{ + unsigned int page_len, thislen, page_offset; + struct scatterlist *cur = sg, *prev = NULL; + int nents = 0; + int i; + + if (len == 0) + return 0; + + if (offset >= buf->head[0].iov_len) { + offset -= buf->head[0].iov_len; + } else { + thislen = min_t(unsigned int, + buf->head[0].iov_len - offset, len); + if (nents >= nsg) + return -ENOSPC; + sg_set_buf(cur, buf->head[0].iov_base + offset, + thislen); + prev = cur; + cur = sg_next(cur); + nents++; + len -= thislen; + offset = 0; + } + if (len == 0) + goto done; + + if (offset >= buf->page_len) { + offset -= buf->page_len; + } else { + page_len = min(buf->page_len - offset, len); + len -= page_len; + page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1); + i = (offset + buf->page_base) >> PAGE_SHIFT; + thislen = PAGE_SIZE - page_offset; + do { + if (thislen > page_len) + thislen = page_len; + if (nents >= nsg) + return -ENOSPC; + sg_set_page(cur, buf->pages[i], + thislen, page_offset); + prev = cur; + cur = sg_next(cur); + nents++; + page_len -= thislen; + i++; + page_offset = 0; + thislen = PAGE_SIZE; + } while (page_len != 0); + offset = 0; + } + if (len == 0) + goto done; + + if (offset < buf->tail[0].iov_len) { + thislen = min_t(unsigned int, + buf->tail[0].iov_len - offset, len); + if (nents >= nsg) + return -ENOSPC; + sg_set_buf(cur, buf->tail[0].iov_base + offset, + thislen); + prev = cur; + nents++; + len -= thislen; + } + if (len != 0) + return -EINVAL; + +done: + if (prev) + sg_mark_end(prev); + return nents; +} +EXPORT_SYMBOL_GPL(xdr_buf_to_sg); + +/* + * Count the scatterlist entries needed to cover [offset, offset + len) + * within @buf. Mirrors the walk in xdr_buf_to_sg() so the caller can + * size an allocation that matches the requested sub-range rather than + * the full xdr_buf. + */ +static unsigned int xdr_buf_sg_nents(const struct xdr_buf *buf, + unsigned int offset, unsigned int len) +{ + unsigned int nsg = 0, thislen, page_offset; + + if (len == 0) + return 0; + + if (offset < buf->head[0].iov_len) { + thislen = min_t(unsigned int, + buf->head[0].iov_len - offset, len); + nsg++; + len -= thislen; + offset = 0; + } else { + offset -= buf->head[0].iov_len; + } + if (len == 0) + return nsg; + + if (offset < buf->page_len) { + thislen = min(buf->page_len - offset, len); + page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1); + nsg += DIV_ROUND_UP(page_offset + thislen, PAGE_SIZE); + len -= thislen; + offset = 0; + } else { + offset -= buf->page_len; + } + if (len == 0) + return nsg; + + if (offset < buf->tail[0].iov_len) + nsg++; + return nsg; +} + +/** + * xdr_buf_to_sg_alloc - Populate a scatterlist for an xdr_buf range + * @buf: xdr_buf to map + * @offset: starting byte offset within @buf + * @len: number of bytes to cover + * @sg_head: caller-provided scatterlist array (typically stack-allocated) + * @sg_head_nents: number of entries in @sg_head + * @sg_overflow: OUT: chained extension, or NULL when @sg_head sufficed + * @gfp: memory allocation flags for overflow + * + * Populates @sg_head directly when the xdr_buf fits. When more + * entries are needed, an overflow scatterlist is allocated and + * chained from @sg_head so that the result is traversable with + * sg_next(). + * + * Return: on success, the number of populated scatterlist entries + * (counting only data entries, not chain entries). @sg_head is + * the head of the resulting list. Caller must kfree @sg_overflow + * when done. On failure, a negative errno. + */ +int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset, + unsigned int len, struct scatterlist *sg_head, + unsigned int sg_head_nents, + struct scatterlist **sg_overflow, gfp_t gfp) +{ + unsigned int nsg; + int ret; + + *sg_overflow = NULL; + if (len == 0) + return 0; + + nsg = xdr_buf_sg_nents(buf, offset, len); + if (nsg == 0) + return -EINVAL; + + if (nsg <= sg_head_nents) { + sg_init_table(sg_head, nsg); + } else { + /* +1 replaces the slot sg_chain() consumes as the link. */ + unsigned int overflow_nents = nsg - sg_head_nents + 1; + struct scatterlist *overflow; + + overflow = kmalloc_array(overflow_nents, sizeof(*overflow), + gfp); + if (!overflow) + return -ENOMEM; + + sg_init_table(sg_head, sg_head_nents); + sg_init_table(overflow, overflow_nents); + sg_chain(sg_head, sg_head_nents, overflow); + *sg_overflow = overflow; + } + + ret = xdr_buf_to_sg(buf, offset, len, sg_head, nsg); + if (ret < 0) { + kfree(*sg_overflow); + *sg_overflow = NULL; + } + return ret; +} +EXPORT_SYMBOL_GPL(xdr_buf_to_sg_alloc); + /** * xdr_inline_pages - Prepare receive buffer for a large reply * @xdr: xdr_buf into which reply will be placed From 6e5e0c58dbd059eef27f01805c032b8fa3cd5eb1 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:48 -0400 Subject: [PATCH 032/106] SUNRPC: Add errno-to-GSS status conversion helper The crypto/krb5 library returns standard negative errno values, but the GSS mechanism layer reports results as GSS_S_* major status codes. A translation is needed at each call site that will be switched to the new library. Rather than open-coding the mapping in every wrapper, provide a single helper function. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_internal.h | 2 ++ net/sunrpc/auth_gss/gss_krb5_mech.c | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 11402c3b4972..a3fe4be3b9ae 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -180,6 +180,8 @@ u32 krb5_etm_encrypt(struct krb5_ctx *kctx, u32 offset, struct xdr_buf *buf, u32 krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, struct xdr_buf *buf, u32 *headskip, u32 *tailskip); +u32 gss_krb5_errno_to_status(int err); + #if IS_ENABLED(CONFIG_KUNIT) void krb5_nfold(u32 inbits, const u8 *in, u32 outbits, u8 *out); const struct gss_krb5_enctype *gss_krb5_lookup_enctype(u32 etype); diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 060d8fc4358e..7606bbd7b8c4 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -516,6 +516,30 @@ gss_krb5_delete_sec_context(void *internal_ctx) kfree(kctx); } +/** + * gss_krb5_errno_to_status - Map a negative errno to a GSS major status + * @err: negative errno value, or zero + * + * Returns: + * %GSS_S_COMPLETE if @err is zero + * %GSS_S_BAD_SIG if @err is -EBADMSG (integrity check failure) + * %GSS_S_DEFECTIVE_TOKEN if @err is -EPROTO (malformed token) + * %GSS_S_FAILURE for all other negative values + */ +u32 gss_krb5_errno_to_status(int err) +{ + switch (err) { + case 0: + return GSS_S_COMPLETE; + case -EBADMSG: + return GSS_S_BAD_SIG; + case -EPROTO: + return GSS_S_DEFECTIVE_TOKEN; + default: + return GSS_S_FAILURE; + } +} + /** * gss_krb5_get_mic - get_mic for the Kerberos GSS mechanism * @gctx: GSS context From 25ccbefc9fd6f62acbf2f3d8eb9e2b3950e5fe28 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:49 -0400 Subject: [PATCH 033/106] SUNRPC: Prepare crypto/krb5 encryption and checksum handles Allocate crypto_aead handles for encryption (one per direction) and crypto_shash handles for checksumming (one per direction) using the crypto/krb5 library's key preparation functions. These four handles derive their subkeys from the session key and the RFC 4121 usage numbers and are ready for use in encrypt, decrypt, get_mic, and verify_mic operations. The existing crypto_sync_skcipher and crypto_ahash handles remain in place for now; subsequent patches switch the per-message operations to the new handles and then remove the old ones. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_internal.h | 4 +++ net/sunrpc/auth_gss/gss_krb5_mech.c | 45 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index a3fe4be3b9ae..33d41d972bd1 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -65,6 +65,10 @@ struct krb5_ctx { u32 flags; const struct gss_krb5_enctype *gk5e; /* enctype-specific info */ const struct krb5_enctype *krb5e; /* crypto/krb5 enctype */ + struct crypto_aead *initiator_enc_aead; + struct crypto_aead *acceptor_enc_aead; + struct crypto_shash *initiator_sign_shash; + struct crypto_shash *acceptor_sign_shash; struct crypto_sync_skcipher *enc; struct crypto_sync_skcipher *seq; struct crypto_sync_skcipher *acceptor_enc; diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 7606bbd7b8c4..35189c57fd0c 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -300,6 +300,10 @@ gss_krb5_import_ctx_v2(struct krb5_ctx *ctx, gfp_t gfp_mask) .len = ctx->gk5e->keylength, .data = ctx->Ksess, }; + struct krb5_buffer TK = { + .len = ctx->gk5e->keylength, + .data = ctx->Ksess, + }; struct xdr_netobj keyout; int ret = -EINVAL; @@ -374,12 +378,49 @@ gss_krb5_import_ctx_v2(struct krb5_ctx *ctx, gfp_t gfp_mask) if (ctx->acceptor_integ == NULL) goto out_free; + ctx->initiator_enc_aead = + crypto_krb5_prepare_encryption(ctx->krb5e, &TK, + KG_USAGE_INITIATOR_SEAL, + gfp_mask); + if (IS_ERR(ctx->initiator_enc_aead)) { + ret = PTR_ERR(ctx->initiator_enc_aead); + goto out_free; + } + ctx->acceptor_enc_aead = + crypto_krb5_prepare_encryption(ctx->krb5e, &TK, + KG_USAGE_ACCEPTOR_SEAL, + gfp_mask); + if (IS_ERR(ctx->acceptor_enc_aead)) { + ret = PTR_ERR(ctx->acceptor_enc_aead); + goto out_free; + } + ctx->initiator_sign_shash = + crypto_krb5_prepare_checksum(ctx->krb5e, &TK, + KG_USAGE_INITIATOR_SIGN, + gfp_mask); + if (IS_ERR(ctx->initiator_sign_shash)) { + ret = PTR_ERR(ctx->initiator_sign_shash); + goto out_free; + } + ctx->acceptor_sign_shash = + crypto_krb5_prepare_checksum(ctx->krb5e, &TK, + KG_USAGE_ACCEPTOR_SIGN, + gfp_mask); + if (IS_ERR(ctx->acceptor_sign_shash)) { + ret = PTR_ERR(ctx->acceptor_sign_shash); + goto out_free; + } + ret = 0; out: kfree_sensitive(keyout.data); return ret; out_free: + crypto_free_shash(ctx->acceptor_sign_shash); + crypto_free_shash(ctx->initiator_sign_shash); + crypto_free_aead(ctx->acceptor_enc_aead); + crypto_free_aead(ctx->initiator_enc_aead); crypto_free_ahash(ctx->acceptor_integ); crypto_free_ahash(ctx->initiator_integ); crypto_free_ahash(ctx->acceptor_sign); @@ -502,6 +543,10 @@ gss_krb5_delete_sec_context(void *internal_ctx) { struct krb5_ctx *kctx = internal_ctx; + crypto_free_shash(kctx->acceptor_sign_shash); + crypto_free_shash(kctx->initiator_sign_shash); + crypto_free_aead(kctx->acceptor_enc_aead); + crypto_free_aead(kctx->initiator_enc_aead); crypto_free_sync_skcipher(kctx->seq); crypto_free_sync_skcipher(kctx->enc); crypto_free_sync_skcipher(kctx->acceptor_enc); From f1965a3bcb1dc9b3491f750abc0f30a595c3de9f Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:50 -0400 Subject: [PATCH 034/106] SUNRPC: Switch wrap token encryption to crypto/krb5 Replace the per-enctype .encrypt callbacks (gss_krb5_aes_encrypt and krb5_etm_encrypt) with a single gss_krb5_aead_encrypt() wrapper that delegates to crypto_krb5_encrypt(). The xdr_buf setup -- GSS header insertion, confounder space allocation, and token header copy -- remains unchanged. The difference is that the CBC-CTS encryption and HMAC computation are now a single AEAD operation through the crypto/krb5 library. Both the MtE construction (RFC 3962) and the EtM construction (RFC 8009) are handled transparently by the AEAD transform. The plaintext page data must be copied from the page cache pages to the scratch output pages before building the scatterlist, since the AEAD operates in-place rather than using separate input and output scatterlists. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_crypto.c | 97 +++++++++++++++++++++++++ net/sunrpc/auth_gss/gss_krb5_internal.h | 5 ++ net/sunrpc/auth_gss/gss_krb5_mech.c | 12 +-- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c index 16dcf115de1e..85425d4a28c2 100644 --- a/net/sunrpc/auth_gss/gss_krb5_crypto.c +++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c @@ -953,3 +953,100 @@ krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, ret = GSS_S_FAILURE; return ret; } + +/** + * gss_krb5_aead_encrypt - Encrypt a wrap token using crypto/krb5 + * @kctx: Kerberos context + * @offset: byte offset of the GSS token header in @buf + * @buf: OUT: send buffer + * @pages: plaintext payload pages (page cache data) + * + * The xdr_buf setup mirrors the original per-enctype encrypt + * functions, but the CBC-CTS encryption and HMAC are replaced + * by a single AEAD operation through the crypto/krb5 library. + * + * Return values: + * %GSS_S_COMPLETE: Encryption successful + * %GSS_S_FAILURE: Encryption failed + */ +u32 +gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset, + struct xdr_buf *buf, struct page **pages) +{ + const struct krb5_enctype *krb5 = kctx->krb5e; + struct crypto_aead *aead = kctx->initiate ? + kctx->initiator_enc_aead : kctx->acceptor_enc_aead; + unsigned int conflen = krb5->conf_len; + unsigned int cksum_len = krb5->cksum_len; + unsigned int sec_offset, sec_len, data_len; + struct scatterlist sg[XDR_BUF_TO_SG_NENTS]; + struct scatterlist *sg_overflow = NULL; + ssize_t ret; + int nsg; + + /* Insert space for the confounder */ + if (xdr_extend_head(buf, offset + GSS_KRB5_TOK_HDR_LEN, conflen)) + return GSS_S_FAILURE; + + /* Ensure a tail segment exists */ + if (buf->tail[0].iov_base == NULL) { + buf->tail[0].iov_base = buf->head[0].iov_base + + buf->head[0].iov_len; + buf->tail[0].iov_len = 0; + } + + /* Append a copy of the plaintext GSS token header (RFC 4121 Sec 4.2.4) */ + memcpy(buf->tail[0].iov_base + buf->tail[0].iov_len, + buf->head[0].iov_base + offset, GSS_KRB5_TOK_HDR_LEN); + buf->tail[0].iov_len += GSS_KRB5_TOK_HDR_LEN; + buf->len += GSS_KRB5_TOK_HDR_LEN; + + /* Reserve space for the integrity checksum */ + buf->tail[0].iov_len += cksum_len; + buf->len += cksum_len; + + /* + * The AEAD operates in-place, but on the client send path the + * plaintext payload lives in page cache pages that must not be + * modified. Copy the payload into the scratch output pages + * first. On the server send path @pages and buf->pages are + * the same array, and no copy is needed. + * + * Both arrays share buf->page_base, so the same index and + * intra-page offset address corresponding data in each. + */ + if (pages != buf->pages) { + unsigned int poff = buf->page_base; + unsigned int plen = buf->page_len; + unsigned int i = poff >> PAGE_SHIFT; + unsigned int off = offset_in_page(poff); + + while (plen) { + unsigned int n = min_t(unsigned int, plen, + PAGE_SIZE - off); + memcpy_page(buf->pages[i], off, pages[i], off, n); + plen -= n; + i++; + off = 0; + } + } + + /* Build scatterlist covering the secured region */ + sec_offset = offset + GSS_KRB5_TOK_HDR_LEN; + sec_len = buf->len - sec_offset; + data_len = sec_len - conflen - cksum_len; + + nsg = xdr_buf_to_sg_alloc(buf, sec_offset, sec_len, + sg, ARRAY_SIZE(sg), + &sg_overflow, GFP_NOFS); + if (nsg < 0) + return GSS_S_FAILURE; + + ret = crypto_krb5_encrypt(krb5, aead, sg, nsg, sec_len, + conflen, data_len, false); + kfree(sg_overflow); + if (ret < 0) + return GSS_S_FAILURE; + + return GSS_S_COMPLETE; +} diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 33d41d972bd1..ce43e1be7577 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -186,6 +186,11 @@ u32 krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, u32 gss_krb5_errno_to_status(int err); +u32 gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset, + struct xdr_buf *buf, struct page **pages); +u32 gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, + struct xdr_buf *buf, u32 *headskip, u32 *tailskip); + #if IS_ENABLED(CONFIG_KUNIT) void krb5_nfold(u32 inbits, const u8 *in, u32 outbits, u8 *out); const struct gss_krb5_enctype *gss_krb5_lookup_enctype(u32 etype); diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 35189c57fd0c..6cd7eb203350 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -43,7 +43,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .aux_cipher = "cbc(aes)", .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -72,7 +72,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .aux_cipher = "cbc(aes)", .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -111,7 +111,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(128), .derive_key = krb5_kdf_feedback_cmac, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -137,7 +137,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(256), .derive_key = krb5_kdf_feedback_cmac, - .encrypt = gss_krb5_aes_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aes_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -166,7 +166,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(128), .derive_key = krb5_kdf_hmac_sha2, - .encrypt = krb5_etm_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = krb5_etm_decrypt, .get_mic = gss_krb5_get_mic_v2, @@ -192,7 +192,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(192), .derive_key = krb5_kdf_hmac_sha2, - .encrypt = krb5_etm_encrypt, + .encrypt = gss_krb5_aead_encrypt, .decrypt = krb5_etm_decrypt, .get_mic = gss_krb5_get_mic_v2, From 598de25deaffb80758ad8bdce5b4bacbbea59582 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:51 -0400 Subject: [PATCH 035/106] SUNRPC: Switch wrap token decryption to crypto/krb5 Replace the per-enctype .decrypt callbacks (gss_krb5_aes_decrypt and krb5_etm_decrypt) with a single gss_krb5_aead_decrypt() wrapper that delegates to crypto_krb5_decrypt(). The new wrapper builds a scatterlist covering the secured region (confounder through checksum), passes it to the AEAD decrypt operation, and derives the confounder and checksum lengths from the data offset and length that crypto_krb5_decrypt() reports. The caller's token header verification and buffer adjustment logic is unchanged. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_crypto.c | 53 +++++++++++++++++++++++++++ net/sunrpc/auth_gss/gss_krb5_mech.c | 8 ++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c index 85425d4a28c2..31c2c86b873f 100644 --- a/net/sunrpc/auth_gss/gss_krb5_crypto.c +++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c @@ -1050,3 +1050,56 @@ gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset, return GSS_S_COMPLETE; } + +/** + * gss_krb5_aead_decrypt - Decrypt a wrap token using crypto/krb5 + * @kctx: Kerberos context + * @offset: byte offset of the GSS token header in @buf + * @len: total length of the GSS token + * @buf: ciphertext buffer, decrypted in-place + * @headskip: OUT: confounder length, in octets + * @tailskip: OUT: checksum length, in octets + * + * Return values: + * %GSS_S_COMPLETE: Decryption and integrity verification succeeded + * %GSS_S_BAD_SIG: Integrity checksum did not match + * %GSS_S_DEFECTIVE_TOKEN: Token is malformed or truncated + * %GSS_S_FAILURE: Decryption failed + */ +u32 +gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, + struct xdr_buf *buf, u32 *headskip, u32 *tailskip) +{ + const struct krb5_enctype *krb5 = kctx->krb5e; + struct crypto_aead *aead = kctx->initiate ? + kctx->acceptor_enc_aead : kctx->initiator_enc_aead; + unsigned int sec_offset, sec_len; + size_t data_offset, data_len; + struct scatterlist sg[XDR_BUF_TO_SG_NENTS]; + struct scatterlist *sg_overflow = NULL; + int nsg, ret; + + /* Secured region starts after the GSS token header */ + sec_offset = offset + GSS_KRB5_TOK_HDR_LEN; + if (len < sec_offset) + return GSS_S_DEFECTIVE_TOKEN; + sec_len = len - sec_offset; + + nsg = xdr_buf_to_sg_alloc(buf, sec_offset, sec_len, + sg, ARRAY_SIZE(sg), + &sg_overflow, GFP_NOFS); + if (nsg < 0) + return GSS_S_FAILURE; + + data_offset = 0; + data_len = sec_len; + ret = crypto_krb5_decrypt(krb5, aead, sg, nsg, + &data_offset, &data_len); + kfree(sg_overflow); + if (ret < 0) + return gss_krb5_errno_to_status(ret); + + *headskip = data_offset; + *tailskip = sec_len - data_offset - data_len; + return GSS_S_COMPLETE; +} diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 6cd7eb203350..66372e152c3b 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -44,7 +44,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aes_decrypt, + .decrypt = gss_krb5_aead_decrypt, .get_mic = gss_krb5_get_mic_v2, .verify_mic = gss_krb5_verify_mic_v2, @@ -73,7 +73,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aes_decrypt, + .decrypt = gss_krb5_aead_decrypt, .get_mic = gss_krb5_get_mic_v2, .verify_mic = gss_krb5_verify_mic_v2, @@ -167,7 +167,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .derive_key = krb5_kdf_hmac_sha2, .encrypt = gss_krb5_aead_encrypt, - .decrypt = krb5_etm_decrypt, + .decrypt = gss_krb5_aead_decrypt, .get_mic = gss_krb5_get_mic_v2, .verify_mic = gss_krb5_verify_mic_v2, @@ -193,7 +193,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .derive_key = krb5_kdf_hmac_sha2, .encrypt = gss_krb5_aead_encrypt, - .decrypt = krb5_etm_decrypt, + .decrypt = gss_krb5_aead_decrypt, .get_mic = gss_krb5_get_mic_v2, .verify_mic = gss_krb5_verify_mic_v2, From 315fc6f2a132f17f1c2b27791031e7e500c4d441 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:52 -0400 Subject: [PATCH 036/106] SUNRPC: Switch Camellia decrypt to crypto/krb5 The Camellia enctypes (RFC 6803) use the same MtE authenticated encryption construction as AES-SHA1 (RFC 3962), implemented in crypto/krb5 by the rfc3961_simplified profile. The encrypt path already uses gss_krb5_aead_encrypt() for Camellia, but the decrypt path was left on the old gss_krb5_aes_decrypt() code when the AES enctypes were migrated. Switch the Camellia .decrypt callback to gss_krb5_aead_decrypt() to complete the AEAD migration for all enctypes. The conf_len and cksum_len values in crypto/krb5's Camellia enctype descriptors match the block size and checksum length that gss_krb5_aes_decrypt() was using, so the headskip and tailskip returned to the unwrap layer are unchanged. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_mech.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 66372e152c3b..9a5e367fef5b 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -112,7 +112,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .derive_key = krb5_kdf_feedback_cmac, .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aes_decrypt, + .decrypt = gss_krb5_aead_decrypt, .get_mic = gss_krb5_get_mic_v2, .verify_mic = gss_krb5_verify_mic_v2, @@ -138,7 +138,7 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .derive_key = krb5_kdf_feedback_cmac, .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aes_decrypt, + .decrypt = gss_krb5_aead_decrypt, .get_mic = gss_krb5_get_mic_v2, .verify_mic = gss_krb5_verify_mic_v2, From a9b9eb085884cd50de41b8ecb8d0fb9d3268cdfb Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:53 -0400 Subject: [PATCH 037/106] SUNRPC: Switch MIC token generation to crypto/krb5 gss_krb5_get_mic_v2() currently computes the MIC checksum by driving a crypto_ahash directly, calling gss_krb5_checksum() with the message body and GSS token header. Replace this with a call to crypto_krb5_get_mic(), which performs the same keyed hash operation through the crypto/krb5 library. RFC 4121 Section 4.2.4 specifies that the checksum covers the message body followed by the token header. Because the crypto/krb5 metadata parameter is hashed before the data, the GSS header cannot be passed as metadata. Instead, the header is appended to the scatterlist after the body data, producing the correct hash input ordering without using the metadata parameter. The scatterlist layout is: [checksum_output | message_body | gss_header] The first scatterlist entry points directly into the token buffer, so the checksum is written in place. A shared helper, gss_krb5_mic_build_sg(), is introduced in gss_krb5_crypto.c to construct this scatterlist layout. The helper handles overflow allocation and scatterlist chaining for large xdr_buf page arrays. It is reused by the verify_mic counterpart in the following commit. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_crypto.c | 82 +++++++++++++++++++++++++ net/sunrpc/auth_gss/gss_krb5_internal.h | 6 ++ net/sunrpc/auth_gss/gss_krb5_seal.c | 45 ++++++++++---- 3 files changed, 121 insertions(+), 12 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c index 31c2c86b873f..3a8e6710a51b 100644 --- a/net/sunrpc/auth_gss/gss_krb5_crypto.c +++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c @@ -1103,3 +1103,85 @@ gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, *tailskip = sec_len - data_offset - data_len; return GSS_S_COMPLETE; } + +/** + * gss_krb5_mic_build_sg - Build scatterlist for MIC token operations + * @body: xdr_buf containing the message body + * @cksum: pointer to checksum area in the token buffer + * @cksum_len: length of checksum area + * @hdr: pointer to GSS token header + * @sg_head: caller-provided scatterlist array; if more than + * XDR_BUF_TO_SG_NENTS entries are needed, an overflow + * scatterlist is allocated and chained automatically + * @sg_overflow: OUT: overflow scatterlist, caller must kfree + * + * Per RFC 4121 Section 4.2.4, MIC token checksums cover the + * message body followed by the token header. The checksum + * output or received checksum occupies the first scatterlist + * entry. This layout cannot be constructed by + * xdr_buf_to_sg_alloc() because the checksum area and the GSS + * header lie outside the xdr_buf. + * + * Returns the number of scatterlist entries on success, or a + * negative errno on failure. + */ +int gss_krb5_mic_build_sg(const struct xdr_buf *body, + void *cksum, unsigned int cksum_len, + void *hdr, + struct scatterlist *sg_head, + struct scatterlist **sg_overflow) +{ + struct scatterlist *entry; + int body_max, body_nsg, nsg; + + *sg_overflow = NULL; + + body_max = 2; + if (body->page_len) + body_max += DIV_ROUND_UP(body->page_len + + offset_in_page(body->page_base), + PAGE_SIZE); + nsg = 1 + body_max + 1; + if (nsg <= XDR_BUF_TO_SG_NENTS) { + sg_init_table(sg_head, nsg); + } else { + unsigned int overflow_nents = + nsg - XDR_BUF_TO_SG_NENTS + 1; + + *sg_overflow = kmalloc_array(overflow_nents, + sizeof(**sg_overflow), + GFP_NOFS); + if (!*sg_overflow) + return -ENOMEM; + + sg_init_table(sg_head, XDR_BUF_TO_SG_NENTS); + sg_init_table(*sg_overflow, overflow_nents); + sg_chain(sg_head, XDR_BUF_TO_SG_NENTS, *sg_overflow); + } + + sg_set_buf(&sg_head[0], cksum, cksum_len); + body_nsg = xdr_buf_to_sg(body, 0, body->len, + sg_next(&sg_head[0]), body_max); + if (body_nsg < 0) + goto out_err; + + /* + * xdr_buf_to_sg marks the last body entry as end-of-list; + * clear it so the trailing header entry is reachable. + */ + if (body_nsg > 0) { + entry = sg_last(sg_next(&sg_head[0]), body_nsg); + sg_unmark_end(entry); + entry = sg_next(entry); + } else { + entry = sg_next(&sg_head[0]); + } + sg_set_buf(entry, hdr, GSS_KRB5_TOK_HDR_LEN); + sg_mark_end(entry); + return 1 + body_nsg + 1; + +out_err: + kfree(*sg_overflow); + *sg_overflow = NULL; + return body_nsg; +} diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index ce43e1be7577..83e969494b54 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -186,6 +186,12 @@ u32 krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, u32 gss_krb5_errno_to_status(int err); +int gss_krb5_mic_build_sg(const struct xdr_buf *body, + void *cksum, unsigned int cksum_len, + void *hdr, + struct scatterlist *sg_head, + struct scatterlist **sg_overflow); + u32 gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset, struct xdr_buf *buf, struct page **pages); u32 gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, diff --git a/net/sunrpc/auth_gss/gss_krb5_seal.c b/net/sunrpc/auth_gss/gss_krb5_seal.c index ce540df9bce4..66c179337029 100644 --- a/net/sunrpc/auth_gss/gss_krb5_seal.c +++ b/net/sunrpc/auth_gss/gss_krb5_seal.c @@ -64,6 +64,8 @@ #include #include #include +#include +#include #include "gss_krb5_internal.h" @@ -78,10 +80,10 @@ setup_token_v2(struct krb5_ctx *ctx, struct xdr_netobj *token) void *krb5_hdr; u8 *p, flags = 0x00; - if ((ctx->flags & KRB5_CTX_FLAG_INITIATOR) == 0) - flags |= 0x01; + if (!ctx->initiate) + flags |= KG2_TOKEN_FLAG_SENTBYACCEPTOR; if (ctx->flags & KRB5_CTX_FLAG_ACCEPTOR_SUBKEY) - flags |= 0x04; + flags |= KG2_TOKEN_FLAG_ACCEPTORSUBKEY; /* Per rfc 4121, sec 4.2.6.1, there is no header, * just start the token. @@ -97,7 +99,7 @@ setup_token_v2(struct krb5_ctx *ctx, struct xdr_netobj *token) *ptr++ = 0xffff; *ptr = 0xffff; - token->len = GSS_KRB5_TOK_HDR_LEN + ctx->gk5e->cksumlength; + token->len = GSS_KRB5_TOK_HDR_LEN + ctx->krb5e->cksum_len; return krb5_hdr; } @@ -105,14 +107,17 @@ u32 gss_krb5_get_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *text, struct xdr_netobj *token) { - struct crypto_ahash *tfm = ctx->initiate ? - ctx->initiator_sign : ctx->acceptor_sign; - struct xdr_netobj cksumobj = { - .len = ctx->gk5e->cksumlength, - }; + const struct krb5_enctype *krb5 = ctx->krb5e; + struct crypto_shash *shash = ctx->initiate ? + ctx->initiator_sign_shash : ctx->acceptor_sign_shash; + unsigned int cksum_len = krb5->cksum_len; + struct scatterlist sg_head[XDR_BUF_TO_SG_NENTS]; + struct scatterlist *sg_overflow; __be64 seq_send_be64; void *krb5_hdr; time64_t now; + ssize_t ret; + int nsg; dprintk("RPC: %s\n", __func__); @@ -123,9 +128,25 @@ gss_krb5_get_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *text, seq_send_be64 = cpu_to_be64(atomic64_fetch_inc(&ctx->seq_send64)); memcpy(krb5_hdr + 8, (char *) &seq_send_be64, 8); - cksumobj.data = krb5_hdr + GSS_KRB5_TOK_HDR_LEN; - if (gss_krb5_checksum(tfm, krb5_hdr, GSS_KRB5_TOK_HDR_LEN, - text, 0, &cksumobj)) + /* + * The checksum is written directly into the token buffer. + * This is safe: crypto_krb5_get_mic uses shash (software + * hash), so the scatterlist is never DMA-mapped. + */ + nsg = gss_krb5_mic_build_sg(text, + krb5_hdr + GSS_KRB5_TOK_HDR_LEN, + cksum_len, krb5_hdr, + sg_head, &sg_overflow); + if (nsg < 0) + return GSS_S_FAILURE; + + ret = crypto_krb5_get_mic(krb5, shash, NULL, sg_head, nsg, + cksum_len + text->len + + GSS_KRB5_TOK_HDR_LEN, + cksum_len, + text->len + GSS_KRB5_TOK_HDR_LEN); + kfree(sg_overflow); + if (ret < 0) return GSS_S_FAILURE; now = ktime_get_real_seconds(); From d4f6bd01c5bf7afbf4a3d07f2229e06c2236e8ed Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:54 -0400 Subject: [PATCH 038/106] SUNRPC: Switch MIC token verification to crypto/krb5 gss_krb5_verify_mic_v2() currently recomputes a checksum using gss_krb5_checksum() and then compares it against the received checksum with memcmp(). Replace this with a call to crypto_krb5_verify_mic(), which performs the hash, comparison, and offset/length adjustment in a single operation through the crypto/krb5 library. The scatterlist layout required by RFC 4121 Section 4.2.4 is constructed via gss_krb5_mic_build_sg(), the shared helper introduced in the preceding commit. The received checksum occupies the first scatterlist entry, pointing directly into the token buffer. The errno result from crypto_krb5_verify_mic() is mapped to a GSS major status code via gss_krb5_errno_to_status(), which returns GSS_S_BAD_SIG for -EBADMSG (checksum mismatch). Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_unseal.c | 36 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_unseal.c b/net/sunrpc/auth_gss/gss_krb5_unseal.c index ef0e6af9fc95..b5fb70419faa 100644 --- a/net/sunrpc/auth_gss/gss_krb5_unseal.c +++ b/net/sunrpc/auth_gss/gss_krb5_unseal.c @@ -60,6 +60,8 @@ #include #include #include +#include +#include #include "gss_krb5_internal.h" @@ -71,18 +73,19 @@ u32 gss_krb5_verify_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *message_buffer, struct xdr_netobj *read_token) { - struct crypto_ahash *tfm = ctx->initiate ? - ctx->acceptor_sign : ctx->initiator_sign; - char cksumdata[GSS_KRB5_MAX_CKSUM_LEN]; - struct xdr_netobj cksumobj = { - .len = ctx->gk5e->cksumlength, - .data = cksumdata, - }; + const struct krb5_enctype *krb5 = ctx->krb5e; + struct crypto_shash *shash = ctx->initiate ? + ctx->acceptor_sign_shash : ctx->initiator_sign_shash; + unsigned int cksum_len = krb5->cksum_len; + struct scatterlist sg_head[XDR_BUF_TO_SG_NENTS]; + struct scatterlist *sg_overflow; + size_t mic_offset, mic_len; u8 *ptr = read_token->data; __be16 be16_ptr; time64_t now; u8 flags; - int i; + int nsg, i; + int ret; dprintk("RPC: %s\n", __func__); @@ -104,13 +107,20 @@ gss_krb5_verify_mic_v2(struct krb5_ctx *ctx, struct xdr_buf *message_buffer, if (ptr[i] != 0xff) return GSS_S_DEFECTIVE_TOKEN; - if (gss_krb5_checksum(tfm, ptr, GSS_KRB5_TOK_HDR_LEN, - message_buffer, 0, &cksumobj)) + nsg = gss_krb5_mic_build_sg(message_buffer, + ptr + GSS_KRB5_TOK_HDR_LEN, + cksum_len, ptr, + sg_head, &sg_overflow); + if (nsg < 0) return GSS_S_FAILURE; - if (memcmp(cksumobj.data, ptr + GSS_KRB5_TOK_HDR_LEN, - ctx->gk5e->cksumlength)) - return GSS_S_BAD_SIG; + mic_offset = 0; + mic_len = cksum_len + message_buffer->len + GSS_KRB5_TOK_HDR_LEN; + ret = crypto_krb5_verify_mic(krb5, shash, NULL, sg_head, nsg, + &mic_offset, &mic_len); + kfree(sg_overflow); + if (ret) + return gss_krb5_errno_to_status(ret); /* it got through unscathed. Make sure the context is unexpired */ now = ktime_get_real_seconds(); From 0308f694276ea8943bde3cc5848669ea828f0c22 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:55 -0400 Subject: [PATCH 039/106] SUNRPC: Remove get_mic/verify_mic function pointers from enctype table Every enctype in the table points .get_mic and .verify_mic at the same pair of functions. The indirection served no purpose after the v1 enctype support was removed. Call gss_krb5_get_mic_v2() and gss_krb5_verify_mic_v2() directly from the GSS mechanism dispatch and drop the function pointers from struct gss_krb5_enctype. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_internal.h | 4 ---- net/sunrpc/auth_gss/gss_krb5_mech.c | 16 ++-------------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 83e969494b54..a63f8d465b63 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -44,10 +44,6 @@ struct gss_krb5_enctype { struct xdr_buf *buf, struct page **pages); u32 (*decrypt)(struct krb5_ctx *kctx, u32 offset, u32 len, struct xdr_buf *buf, u32 *headskip, u32 *tailskip); - u32 (*get_mic)(struct krb5_ctx *kctx, struct xdr_buf *text, - struct xdr_netobj *token); - u32 (*verify_mic)(struct krb5_ctx *kctx, struct xdr_buf *message_buffer, - struct xdr_netobj *read_token); u32 (*wrap)(struct krb5_ctx *kctx, int offset, struct xdr_buf *buf, struct page **pages); u32 (*unwrap)(struct krb5_ctx *kctx, int offset, int len, diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 9a5e367fef5b..511e19e0e786 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -46,8 +46,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .get_mic = gss_krb5_get_mic_v2, - .verify_mic = gss_krb5_verify_mic_v2, .wrap = gss_krb5_wrap_v2, .unwrap = gss_krb5_unwrap_v2, @@ -75,8 +73,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .get_mic = gss_krb5_get_mic_v2, - .verify_mic = gss_krb5_verify_mic_v2, .wrap = gss_krb5_wrap_v2, .unwrap = gss_krb5_unwrap_v2, @@ -114,8 +110,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .get_mic = gss_krb5_get_mic_v2, - .verify_mic = gss_krb5_verify_mic_v2, .wrap = gss_krb5_wrap_v2, .unwrap = gss_krb5_unwrap_v2, }, @@ -140,8 +134,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .get_mic = gss_krb5_get_mic_v2, - .verify_mic = gss_krb5_verify_mic_v2, .wrap = gss_krb5_wrap_v2, .unwrap = gss_krb5_unwrap_v2, }, @@ -169,8 +161,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .get_mic = gss_krb5_get_mic_v2, - .verify_mic = gss_krb5_verify_mic_v2, .wrap = gss_krb5_wrap_v2, .unwrap = gss_krb5_unwrap_v2, }, @@ -195,8 +185,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .get_mic = gss_krb5_get_mic_v2, - .verify_mic = gss_krb5_verify_mic_v2, .wrap = gss_krb5_wrap_v2, .unwrap = gss_krb5_unwrap_v2, }, @@ -601,7 +589,7 @@ static u32 gss_krb5_get_mic(struct gss_ctx *gctx, struct xdr_buf *text, { struct krb5_ctx *kctx = gctx->internal_ctx_id; - return kctx->gk5e->get_mic(kctx, text, token); + return gss_krb5_get_mic_v2(kctx, text, token); } /** @@ -623,7 +611,7 @@ static u32 gss_krb5_verify_mic(struct gss_ctx *gctx, { struct krb5_ctx *kctx = gctx->internal_ctx_id; - return kctx->gk5e->verify_mic(kctx, message_buffer, read_token); + return gss_krb5_verify_mic_v2(kctx, message_buffer, read_token); } /** From 5fc7721223cff64e2a6740f97ef8db7cd05c9552 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:56 -0400 Subject: [PATCH 040/106] SUNRPC: Remove wrap/unwrap function pointers from enctype table Every enctype points .wrap and .unwrap at gss_krb5_wrap_v2() and gss_krb5_unwrap_v2(). As with get_mic/verify_mic, the indirection dates from when v1 enctypes had different wrap implementations. Call the functions directly and remove the pointers from struct gss_krb5_enctype. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_internal.h | 5 ----- net/sunrpc/auth_gss/gss_krb5_mech.c | 18 ++---------------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index a63f8d465b63..92b0baed920c 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -44,11 +44,6 @@ struct gss_krb5_enctype { struct xdr_buf *buf, struct page **pages); u32 (*decrypt)(struct krb5_ctx *kctx, u32 offset, u32 len, struct xdr_buf *buf, u32 *headskip, u32 *tailskip); - u32 (*wrap)(struct krb5_ctx *kctx, int offset, - struct xdr_buf *buf, struct page **pages); - u32 (*unwrap)(struct krb5_ctx *kctx, int offset, int len, - struct xdr_buf *buf, unsigned int *slack, - unsigned int *align); }; /* krb5_ctx flags definitions */ diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 511e19e0e786..d027ddab132f 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -46,9 +46,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .wrap = gss_krb5_wrap_v2, - .unwrap = gss_krb5_unwrap_v2, - .signalg = -1, .sealalg = -1, .keybytes = 16, @@ -73,9 +70,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .wrap = gss_krb5_wrap_v2, - .unwrap = gss_krb5_unwrap_v2, - .signalg = -1, .sealalg = -1, .keybytes = 32, @@ -110,8 +104,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .wrap = gss_krb5_wrap_v2, - .unwrap = gss_krb5_unwrap_v2, }, /* * Camellia-256 with CMAC (RFC 6803) @@ -134,8 +126,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .wrap = gss_krb5_wrap_v2, - .unwrap = gss_krb5_unwrap_v2, }, #endif @@ -161,8 +151,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .wrap = gss_krb5_wrap_v2, - .unwrap = gss_krb5_unwrap_v2, }, /* * AES-256 with SHA-384 (RFC 8009) @@ -185,8 +173,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .encrypt = gss_krb5_aead_encrypt, .decrypt = gss_krb5_aead_decrypt, - .wrap = gss_krb5_wrap_v2, - .unwrap = gss_krb5_unwrap_v2, }, #endif }; @@ -631,7 +617,7 @@ static u32 gss_krb5_wrap(struct gss_ctx *gctx, int offset, { struct krb5_ctx *kctx = gctx->internal_ctx_id; - return kctx->gk5e->wrap(kctx, offset, buf, pages); + return gss_krb5_wrap_v2(kctx, offset, buf, pages); } /** @@ -653,7 +639,7 @@ static u32 gss_krb5_unwrap(struct gss_ctx *gctx, int offset, { struct krb5_ctx *kctx = gctx->internal_ctx_id; - return kctx->gk5e->unwrap(kctx, offset, len, buf, + return gss_krb5_unwrap_v2(kctx, offset, len, buf, &gctx->slack, &gctx->align); } From 5f02e760614238dd826268429f340cfb80074f32 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:57 -0400 Subject: [PATCH 041/106] SUNRPC: Remove encrypt/decrypt function pointers from enctype table All enctypes now route through gss_krb5_aead_encrypt() and gss_krb5_aead_decrypt(). The per-enctype .encrypt and .decrypt function pointers served the same purpose as .get_mic and .wrap before them: dispatching v1 versus v2 implementations. With v1 support long removed and the Camellia decrypt path migrated in a preceding patch, every table entry points to the same pair of functions. Call gss_krb5_aead_encrypt() and gss_krb5_aead_decrypt() directly from gss_krb5_wrap_v2() and gss_krb5_unwrap_v2(), and drop the function pointers from struct gss_krb5_enctype. While here, propagate the GSS status code returned by gss_krb5_aead_decrypt() instead of discarding it. The old indirect call sites returned GSS_S_FAILURE unconditionally, losing the distinction between an integrity failure (GSS_S_BAD_SIG) and a structural error (GSS_S_DEFECTIVE_TOKEN). Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_internal.h | 4 ---- net/sunrpc/auth_gss/gss_krb5_mech.c | 12 ------------ net/sunrpc/auth_gss/gss_krb5_wrap.c | 12 ++++++------ 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 92b0baed920c..8258e6862aa2 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -40,10 +40,6 @@ struct gss_krb5_enctype { struct xdr_netobj *out, const struct xdr_netobj *label, gfp_t gfp_mask); - u32 (*encrypt)(struct krb5_ctx *kctx, u32 offset, - struct xdr_buf *buf, struct page **pages); - u32 (*decrypt)(struct krb5_ctx *kctx, u32 offset, u32 len, - struct xdr_buf *buf, u32 *headskip, u32 *tailskip); }; /* krb5_ctx flags definitions */ diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index d027ddab132f..912821efc937 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -43,8 +43,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .aux_cipher = "cbc(aes)", .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, - .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aead_decrypt, .signalg = -1, .sealalg = -1, @@ -67,8 +65,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .aux_cipher = "cbc(aes)", .cksum_name = "hmac(sha1)", .derive_key = krb5_derive_key_v2, - .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aead_decrypt, .signalg = -1, .sealalg = -1, @@ -101,8 +97,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(128), .derive_key = krb5_kdf_feedback_cmac, - .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aead_decrypt, }, /* @@ -123,8 +117,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(256), .derive_key = krb5_kdf_feedback_cmac, - .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aead_decrypt, }, #endif @@ -148,8 +140,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(128), .derive_key = krb5_kdf_hmac_sha2, - .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aead_decrypt, }, /* @@ -170,8 +160,6 @@ static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { .Ki_length = BITS2OCTETS(192), .derive_key = krb5_kdf_hmac_sha2, - .encrypt = gss_krb5_aead_encrypt, - .decrypt = gss_krb5_aead_decrypt, }, #endif diff --git a/net/sunrpc/auth_gss/gss_krb5_wrap.c b/net/sunrpc/auth_gss/gss_krb5_wrap.c index b3e1738ff6bf..93aa7500d032 100644 --- a/net/sunrpc/auth_gss/gss_krb5_wrap.c +++ b/net/sunrpc/auth_gss/gss_krb5_wrap.c @@ -112,9 +112,9 @@ gss_krb5_wrap_v2(struct krb5_ctx *kctx, int offset, *ptr++ = (unsigned char) ((KG2_TOK_WRAP>>8) & 0xff); *ptr++ = (unsigned char) (KG2_TOK_WRAP & 0xff); - if ((kctx->flags & KRB5_CTX_FLAG_INITIATOR) == 0) + if (!kctx->initiate) flags |= KG2_TOKEN_FLAG_SENTBYACCEPTOR; - if ((kctx->flags & KRB5_CTX_FLAG_ACCEPTOR_SUBKEY) != 0) + if (kctx->flags & KRB5_CTX_FLAG_ACCEPTOR_SUBKEY) flags |= KG2_TOKEN_FLAG_ACCEPTORSUBKEY; /* We always do confidentiality in wrap tokens */ flags |= KG2_TOKEN_FLAG_SEALED; @@ -130,7 +130,7 @@ gss_krb5_wrap_v2(struct krb5_ctx *kctx, int offset, be64ptr = (__be64 *)be16ptr; *be64ptr = cpu_to_be64(atomic64_fetch_inc(&kctx->seq_send64)); - err = (*kctx->gk5e->encrypt)(kctx, offset, buf, pages); + err = gss_krb5_aead_encrypt(kctx, offset, buf, pages); if (err) return err; @@ -184,10 +184,10 @@ gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len, if (rrc != 0) rotate_left(offset + 16, buf, rrc); - err = (*kctx->gk5e->decrypt)(kctx, offset, len, buf, - &headskip, &tailskip); + err = gss_krb5_aead_decrypt(kctx, offset, len, buf, + &headskip, &tailskip); if (err) - return GSS_S_FAILURE; + return err; /* * Retrieve the decrypted gss token header and verify From 218c56ddf687e8d243826343831c6e734857fb51 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:58 -0400 Subject: [PATCH 042/106] SUNRPC: Remove legacy skcipher/ahash handles from krb5_ctx Previous patches switched all per-message crypto operations (encrypt, decrypt, get_mic, verify_mic) from the internal skcipher/ahash primitives to crypto/krb5 AEAD and shash handles. The old crypto_sync_skcipher and crypto_ahash fields in struct krb5_ctx are no longer referenced at runtime. Remove the ten legacy handle fields from struct krb5_ctx along with the key derivation and handle allocation code in gss_krb5_import_ctx_v2() that populated them. Context import now prepares only the four crypto/krb5 handles (two AEAD for encryption, two shash for checksums). The corresponding cleanup in gss_krb5_delete_sec_context() and the error path is likewise reduced. The krb5_derive_key() inline wrapper, gss_krb5_alloc_cipher_v2(), and gss_krb5_alloc_hash_v2() become unused and are removed. The per-enctype encrypt/decrypt functions (gss_krb5_aes_encrypt, gss_krb5_aes_decrypt, krb5_etm_encrypt, krb5_etm_decrypt) that were the sole remaining consumers of these fields are also removed; their function-pointer call sites were already deleted in earlier patches. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/auth_gss/gss_krb5_crypto.c | 307 ------------------------ net/sunrpc/auth_gss/gss_krb5_internal.h | 54 ----- net/sunrpc/auth_gss/gss_krb5_mech.c | 135 +---------- 3 files changed, 3 insertions(+), 493 deletions(-) diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c index 3a8e6710a51b..cfd5b56d1b52 100644 --- a/net/sunrpc/auth_gss/gss_krb5_crypto.c +++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c @@ -578,137 +578,6 @@ int krb5_cbc_cts_decrypt(struct crypto_sync_skcipher *cts_tfm, } EXPORT_SYMBOL_IF_KUNIT(krb5_cbc_cts_decrypt); -u32 -gss_krb5_aes_encrypt(struct krb5_ctx *kctx, u32 offset, - struct xdr_buf *buf, struct page **pages) -{ - u32 err; - struct xdr_netobj hmac; - u8 *ecptr; - struct crypto_sync_skcipher *cipher, *aux_cipher; - struct crypto_ahash *ahash; - struct page **save_pages; - unsigned int conflen; - - if (kctx->initiate) { - cipher = kctx->initiator_enc; - aux_cipher = kctx->initiator_enc_aux; - ahash = kctx->initiator_integ; - } else { - cipher = kctx->acceptor_enc; - aux_cipher = kctx->acceptor_enc_aux; - ahash = kctx->acceptor_integ; - } - conflen = crypto_sync_skcipher_blocksize(cipher); - - /* hide the gss token header and insert the confounder */ - offset += GSS_KRB5_TOK_HDR_LEN; - if (xdr_extend_head(buf, offset, conflen)) - return GSS_S_FAILURE; - krb5_make_confounder(buf->head[0].iov_base + offset, conflen); - offset -= GSS_KRB5_TOK_HDR_LEN; - - if (buf->tail[0].iov_base != NULL) { - ecptr = buf->tail[0].iov_base + buf->tail[0].iov_len; - } else { - buf->tail[0].iov_base = buf->head[0].iov_base - + buf->head[0].iov_len; - buf->tail[0].iov_len = 0; - ecptr = buf->tail[0].iov_base; - } - - /* copy plaintext gss token header after filler (if any) */ - memcpy(ecptr, buf->head[0].iov_base + offset, GSS_KRB5_TOK_HDR_LEN); - buf->tail[0].iov_len += GSS_KRB5_TOK_HDR_LEN; - buf->len += GSS_KRB5_TOK_HDR_LEN; - - hmac.len = kctx->gk5e->cksumlength; - hmac.data = buf->tail[0].iov_base + buf->tail[0].iov_len; - - /* - * When we are called, pages points to the real page cache - * data -- which we can't go and encrypt! buf->pages points - * to scratch pages which we are going to send off to the - * client/server. Swap in the plaintext pages to calculate - * the hmac. - */ - save_pages = buf->pages; - buf->pages = pages; - - err = gss_krb5_checksum(ahash, NULL, 0, buf, - offset + GSS_KRB5_TOK_HDR_LEN, &hmac); - buf->pages = save_pages; - if (err) - return GSS_S_FAILURE; - - err = krb5_cbc_cts_encrypt(cipher, aux_cipher, - offset + GSS_KRB5_TOK_HDR_LEN, - buf, pages, NULL, 0); - if (err) - return GSS_S_FAILURE; - - /* Now update buf to account for HMAC */ - buf->tail[0].iov_len += kctx->gk5e->cksumlength; - buf->len += kctx->gk5e->cksumlength; - - return GSS_S_COMPLETE; -} - -u32 -gss_krb5_aes_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, - struct xdr_buf *buf, u32 *headskip, u32 *tailskip) -{ - struct crypto_sync_skcipher *cipher, *aux_cipher; - struct crypto_ahash *ahash; - struct xdr_netobj our_hmac_obj; - u8 our_hmac[GSS_KRB5_MAX_CKSUM_LEN]; - u8 pkt_hmac[GSS_KRB5_MAX_CKSUM_LEN]; - struct xdr_buf subbuf; - u32 ret = 0; - - if (kctx->initiate) { - cipher = kctx->acceptor_enc; - aux_cipher = kctx->acceptor_enc_aux; - ahash = kctx->acceptor_integ; - } else { - cipher = kctx->initiator_enc; - aux_cipher = kctx->initiator_enc_aux; - ahash = kctx->initiator_integ; - } - - /* create a segment skipping the header and leaving out the checksum */ - xdr_buf_subsegment(buf, &subbuf, offset + GSS_KRB5_TOK_HDR_LEN, - (len - offset - GSS_KRB5_TOK_HDR_LEN - - kctx->gk5e->cksumlength)); - - ret = krb5_cbc_cts_decrypt(cipher, aux_cipher, 0, &subbuf); - if (ret) - goto out_err; - - our_hmac_obj.len = kctx->gk5e->cksumlength; - our_hmac_obj.data = our_hmac; - ret = gss_krb5_checksum(ahash, NULL, 0, &subbuf, 0, &our_hmac_obj); - if (ret) - goto out_err; - - /* Get the packet's hmac value */ - ret = read_bytes_from_xdr_buf(buf, len - kctx->gk5e->cksumlength, - pkt_hmac, kctx->gk5e->cksumlength); - if (ret) - goto out_err; - - if (crypto_memneq(pkt_hmac, our_hmac, kctx->gk5e->cksumlength) != 0) { - ret = GSS_S_BAD_SIG; - goto out_err; - } - *headskip = crypto_sync_skcipher_blocksize(cipher); - *tailskip = kctx->gk5e->cksumlength; -out_err: - if (ret && ret != GSS_S_BAD_SIG) - ret = GSS_S_FAILURE; - return ret; -} - /** * krb5_etm_checksum - Compute a MAC for a GSS Wrap token * @cipher: an initialized cipher transform @@ -778,182 +647,6 @@ u32 krb5_etm_checksum(struct crypto_sync_skcipher *cipher, } EXPORT_SYMBOL_IF_KUNIT(krb5_etm_checksum); -/** - * krb5_etm_encrypt - Encrypt using the RFC 8009 rules - * @kctx: Kerberos context - * @offset: starting offset of the payload, in bytes - * @buf: OUT: send buffer to contain the encrypted payload - * @pages: plaintext payload - * - * The main difference with aes_encrypt is that "The HMAC is - * calculated over the cipher state concatenated with the AES - * output, instead of being calculated over the confounder and - * plaintext. This allows the message receiver to verify the - * integrity of the message before decrypting the message." - * - * RFC 8009 Section 5: - * - * encryption function: as follows, where E() is AES encryption in - * CBC-CS3 mode, and h is the size of truncated HMAC (128 bits or - * 192 bits as described above). - * - * N = random value of length 128 bits (the AES block size) - * IV = cipher state - * C = E(Ke, N | plaintext, IV) - * H = HMAC(Ki, IV | C) - * ciphertext = C | H[1..h] - * - * This encryption formula provides AEAD EtM with key separation. - * - * Return values: - * %GSS_S_COMPLETE: Encryption successful - * %GSS_S_FAILURE: Encryption failed - */ -u32 -krb5_etm_encrypt(struct krb5_ctx *kctx, u32 offset, - struct xdr_buf *buf, struct page **pages) -{ - struct crypto_sync_skcipher *cipher, *aux_cipher; - struct crypto_ahash *ahash; - struct xdr_netobj hmac; - unsigned int conflen; - u8 *ecptr; - u32 err; - - if (kctx->initiate) { - cipher = kctx->initiator_enc; - aux_cipher = kctx->initiator_enc_aux; - ahash = kctx->initiator_integ; - } else { - cipher = kctx->acceptor_enc; - aux_cipher = kctx->acceptor_enc_aux; - ahash = kctx->acceptor_integ; - } - conflen = crypto_sync_skcipher_blocksize(cipher); - - offset += GSS_KRB5_TOK_HDR_LEN; - if (xdr_extend_head(buf, offset, conflen)) - return GSS_S_FAILURE; - krb5_make_confounder(buf->head[0].iov_base + offset, conflen); - offset -= GSS_KRB5_TOK_HDR_LEN; - - if (buf->tail[0].iov_base) { - ecptr = buf->tail[0].iov_base + buf->tail[0].iov_len; - } else { - buf->tail[0].iov_base = buf->head[0].iov_base - + buf->head[0].iov_len; - buf->tail[0].iov_len = 0; - ecptr = buf->tail[0].iov_base; - } - - memcpy(ecptr, buf->head[0].iov_base + offset, GSS_KRB5_TOK_HDR_LEN); - buf->tail[0].iov_len += GSS_KRB5_TOK_HDR_LEN; - buf->len += GSS_KRB5_TOK_HDR_LEN; - - err = krb5_cbc_cts_encrypt(cipher, aux_cipher, - offset + GSS_KRB5_TOK_HDR_LEN, - buf, pages, NULL, 0); - if (err) - return GSS_S_FAILURE; - - hmac.data = buf->tail[0].iov_base + buf->tail[0].iov_len; - hmac.len = kctx->gk5e->cksumlength; - err = krb5_etm_checksum(cipher, ahash, - buf, offset + GSS_KRB5_TOK_HDR_LEN, &hmac); - if (err) - goto out_err; - buf->tail[0].iov_len += kctx->gk5e->cksumlength; - buf->len += kctx->gk5e->cksumlength; - - return GSS_S_COMPLETE; - -out_err: - return GSS_S_FAILURE; -} - -/** - * krb5_etm_decrypt - Decrypt using the RFC 8009 rules - * @kctx: Kerberos context - * @offset: starting offset of the ciphertext, in bytes - * @len: size of ciphertext to unwrap - * @buf: ciphertext to unwrap - * @headskip: OUT: the enctype's confounder length, in octets - * @tailskip: OUT: the enctype's HMAC length, in octets - * - * RFC 8009 Section 5: - * - * decryption function: as follows, where D() is AES decryption in - * CBC-CS3 mode, and h is the size of truncated HMAC. - * - * (C, H) = ciphertext - * (Note: H is the last h bits of the ciphertext.) - * IV = cipher state - * if H != HMAC(Ki, IV | C)[1..h] - * stop, report error - * (N, P) = D(Ke, C, IV) - * - * Return values: - * %GSS_S_COMPLETE: Decryption successful - * %GSS_S_BAD_SIG: computed HMAC != received HMAC - * %GSS_S_FAILURE: Decryption failed - */ -u32 -krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, - struct xdr_buf *buf, u32 *headskip, u32 *tailskip) -{ - struct crypto_sync_skcipher *cipher, *aux_cipher; - u8 our_hmac[GSS_KRB5_MAX_CKSUM_LEN]; - u8 pkt_hmac[GSS_KRB5_MAX_CKSUM_LEN]; - struct xdr_netobj our_hmac_obj; - struct crypto_ahash *ahash; - struct xdr_buf subbuf; - u32 ret = 0; - - if (kctx->initiate) { - cipher = kctx->acceptor_enc; - aux_cipher = kctx->acceptor_enc_aux; - ahash = kctx->acceptor_integ; - } else { - cipher = kctx->initiator_enc; - aux_cipher = kctx->initiator_enc_aux; - ahash = kctx->initiator_integ; - } - - /* Extract the ciphertext into @subbuf. */ - xdr_buf_subsegment(buf, &subbuf, offset + GSS_KRB5_TOK_HDR_LEN, - (len - offset - GSS_KRB5_TOK_HDR_LEN - - kctx->gk5e->cksumlength)); - - our_hmac_obj.data = our_hmac; - our_hmac_obj.len = kctx->gk5e->cksumlength; - ret = krb5_etm_checksum(cipher, ahash, &subbuf, 0, &our_hmac_obj); - if (ret) - goto out_err; - ret = read_bytes_from_xdr_buf(buf, len - kctx->gk5e->cksumlength, - pkt_hmac, kctx->gk5e->cksumlength); - if (ret) - goto out_err; - if (crypto_memneq(pkt_hmac, our_hmac, kctx->gk5e->cksumlength) != 0) { - ret = GSS_S_BAD_SIG; - goto out_err; - } - - ret = krb5_cbc_cts_decrypt(cipher, aux_cipher, 0, &subbuf); - if (ret) { - ret = GSS_S_FAILURE; - goto out_err; - } - - *headskip = crypto_sync_skcipher_blocksize(cipher); - *tailskip = kctx->gk5e->cksumlength; - return GSS_S_COMPLETE; - -out_err: - if (ret != GSS_S_BAD_SIG) - ret = GSS_S_FAILURE; - return ret; -} - /** * gss_krb5_aead_encrypt - Encrypt a wrap token using crypto/krb5 * @kctx: Kerberos context diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 8258e6862aa2..6b08a7486e0b 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -56,16 +56,6 @@ struct krb5_ctx { struct crypto_aead *acceptor_enc_aead; struct crypto_shash *initiator_sign_shash; struct crypto_shash *acceptor_sign_shash; - struct crypto_sync_skcipher *enc; - struct crypto_sync_skcipher *seq; - struct crypto_sync_skcipher *acceptor_enc; - struct crypto_sync_skcipher *initiator_enc; - struct crypto_sync_skcipher *acceptor_enc_aux; - struct crypto_sync_skcipher *initiator_enc_aux; - struct crypto_ahash *acceptor_sign; - struct crypto_ahash *initiator_sign; - struct crypto_ahash *initiator_integ; - struct crypto_ahash *acceptor_integ; u8 Ksess[GSS_KRB5_MAX_KEYLEN]; /* session key */ u8 cksum[GSS_KRB5_MAX_KEYLEN]; atomic_t seq_send; @@ -115,38 +105,6 @@ int krb5_kdf_feedback_cmac(const struct gss_krb5_enctype *gk5e, const struct xdr_netobj *in_constant, gfp_t gfp_mask); -/** - * krb5_derive_key - Derive a subkey from a protocol key - * @kctx: Kerberos 5 context - * @inkey: base protocol key - * @outkey: OUT: derived key - * @usage: key usage value - * @seed: key usage seed (one octet) - * @gfp_mask: memory allocation control flags - * - * Caller sets @outkey->len to the desired length of the derived key. - * - * On success, returns 0 and fills in @outkey. A negative errno value - * is returned on failure. - */ -static inline int krb5_derive_key(struct krb5_ctx *kctx, - const struct xdr_netobj *inkey, - struct xdr_netobj *outkey, - u32 usage, u8 seed, gfp_t gfp_mask) -{ - const struct gss_krb5_enctype *gk5e = kctx->gk5e; - u8 label_data[GSS_KRB5_K5CLENGTH]; - struct xdr_netobj label = { - .len = sizeof(label_data), - .data = label_data, - }; - __be32 *p = (__be32 *)label_data; - - *p = cpu_to_be32(usage); - label_data[4] = seed; - return gk5e->derive_key(gk5e, inkey, outkey, &label, gfp_mask); -} - void krb5_make_confounder(u8 *p, int conflen); u32 gss_krb5_checksum(struct crypto_ahash *tfm, char *header, int hdrlen, @@ -159,18 +117,6 @@ u32 krb5_encrypt(struct crypto_sync_skcipher *key, void *iv, void *in, int xdr_extend_head(struct xdr_buf *buf, unsigned int base, unsigned int shiftlen); -u32 gss_krb5_aes_encrypt(struct krb5_ctx *kctx, u32 offset, - struct xdr_buf *buf, struct page **pages); - -u32 gss_krb5_aes_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, - struct xdr_buf *buf, u32 *plainoffset, u32 *plainlen); - -u32 krb5_etm_encrypt(struct krb5_ctx *kctx, u32 offset, struct xdr_buf *buf, - struct page **pages); - -u32 krb5_etm_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, - struct xdr_buf *buf, u32 *headskip, u32 *tailskip); - u32 gss_krb5_errno_to_status(int err); int gss_krb5_mic_build_sg(const struct xdr_buf *body, diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 912821efc937..d8cb79fd2463 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -9,8 +9,6 @@ * J. Bruce Fields */ -#include -#include #include #include #include @@ -225,120 +223,14 @@ const struct gss_krb5_enctype *gss_krb5_lookup_enctype(u32 etype) } EXPORT_SYMBOL_IF_KUNIT(gss_krb5_lookup_enctype); -static struct crypto_sync_skcipher * -gss_krb5_alloc_cipher_v2(const char *cname, const struct xdr_netobj *key) -{ - struct crypto_sync_skcipher *tfm; - - tfm = crypto_alloc_sync_skcipher(cname, 0, 0); - if (IS_ERR(tfm)) - return NULL; - if (crypto_sync_skcipher_setkey(tfm, key->data, key->len)) { - crypto_free_sync_skcipher(tfm); - return NULL; - } - return tfm; -} - -static struct crypto_ahash * -gss_krb5_alloc_hash_v2(struct krb5_ctx *kctx, const struct xdr_netobj *key) -{ - struct crypto_ahash *tfm; - - tfm = crypto_alloc_ahash(kctx->gk5e->cksum_name, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(tfm)) - return NULL; - if (crypto_ahash_setkey(tfm, key->data, key->len)) { - crypto_free_ahash(tfm); - return NULL; - } - return tfm; -} - static int gss_krb5_import_ctx_v2(struct krb5_ctx *ctx, gfp_t gfp_mask) { - struct xdr_netobj keyin = { - .len = ctx->gk5e->keylength, - .data = ctx->Ksess, - }; struct krb5_buffer TK = { .len = ctx->gk5e->keylength, .data = ctx->Ksess, }; - struct xdr_netobj keyout; - int ret = -EINVAL; - - keyout.data = kmalloc(GSS_KRB5_MAX_KEYLEN, gfp_mask); - if (!keyout.data) - return -ENOMEM; - - /* initiator seal encryption */ - keyout.len = ctx->gk5e->Ke_length; - if (krb5_derive_key(ctx, &keyin, &keyout, KG_USAGE_INITIATOR_SEAL, - KEY_USAGE_SEED_ENCRYPTION, gfp_mask)) - goto out; - ctx->initiator_enc = gss_krb5_alloc_cipher_v2(ctx->gk5e->encrypt_name, - &keyout); - if (ctx->initiator_enc == NULL) - goto out; - if (ctx->gk5e->aux_cipher) { - ctx->initiator_enc_aux = - gss_krb5_alloc_cipher_v2(ctx->gk5e->aux_cipher, - &keyout); - if (ctx->initiator_enc_aux == NULL) - goto out_free; - } - - /* acceptor seal encryption */ - if (krb5_derive_key(ctx, &keyin, &keyout, KG_USAGE_ACCEPTOR_SEAL, - KEY_USAGE_SEED_ENCRYPTION, gfp_mask)) - goto out_free; - ctx->acceptor_enc = gss_krb5_alloc_cipher_v2(ctx->gk5e->encrypt_name, - &keyout); - if (ctx->acceptor_enc == NULL) - goto out_free; - if (ctx->gk5e->aux_cipher) { - ctx->acceptor_enc_aux = - gss_krb5_alloc_cipher_v2(ctx->gk5e->aux_cipher, - &keyout); - if (ctx->acceptor_enc_aux == NULL) - goto out_free; - } - - /* initiator sign checksum */ - keyout.len = ctx->gk5e->Kc_length; - if (krb5_derive_key(ctx, &keyin, &keyout, KG_USAGE_INITIATOR_SIGN, - KEY_USAGE_SEED_CHECKSUM, gfp_mask)) - goto out_free; - ctx->initiator_sign = gss_krb5_alloc_hash_v2(ctx, &keyout); - if (ctx->initiator_sign == NULL) - goto out_free; - - /* acceptor sign checksum */ - if (krb5_derive_key(ctx, &keyin, &keyout, KG_USAGE_ACCEPTOR_SIGN, - KEY_USAGE_SEED_CHECKSUM, gfp_mask)) - goto out_free; - ctx->acceptor_sign = gss_krb5_alloc_hash_v2(ctx, &keyout); - if (ctx->acceptor_sign == NULL) - goto out_free; - - /* initiator seal integrity */ - keyout.len = ctx->gk5e->Ki_length; - if (krb5_derive_key(ctx, &keyin, &keyout, KG_USAGE_INITIATOR_SEAL, - KEY_USAGE_SEED_INTEGRITY, gfp_mask)) - goto out_free; - ctx->initiator_integ = gss_krb5_alloc_hash_v2(ctx, &keyout); - if (ctx->initiator_integ == NULL) - goto out_free; - - /* acceptor seal integrity */ - if (krb5_derive_key(ctx, &keyin, &keyout, KG_USAGE_ACCEPTOR_SEAL, - KEY_USAGE_SEED_INTEGRITY, gfp_mask)) - goto out_free; - ctx->acceptor_integ = gss_krb5_alloc_hash_v2(ctx, &keyout); - if (ctx->acceptor_integ == NULL) - goto out_free; + int ret; ctx->initiator_enc_aead = crypto_krb5_prepare_encryption(ctx->krb5e, &TK, @@ -373,25 +265,14 @@ gss_krb5_import_ctx_v2(struct krb5_ctx *ctx, gfp_t gfp_mask) goto out_free; } - ret = 0; -out: - kfree_sensitive(keyout.data); - return ret; + return 0; out_free: crypto_free_shash(ctx->acceptor_sign_shash); crypto_free_shash(ctx->initiator_sign_shash); crypto_free_aead(ctx->acceptor_enc_aead); crypto_free_aead(ctx->initiator_enc_aead); - crypto_free_ahash(ctx->acceptor_integ); - crypto_free_ahash(ctx->initiator_integ); - crypto_free_ahash(ctx->acceptor_sign); - crypto_free_ahash(ctx->initiator_sign); - crypto_free_sync_skcipher(ctx->acceptor_enc_aux); - crypto_free_sync_skcipher(ctx->acceptor_enc); - crypto_free_sync_skcipher(ctx->initiator_enc_aux); - crypto_free_sync_skcipher(ctx->initiator_enc); - goto out; + return ret; } static int @@ -509,16 +390,6 @@ gss_krb5_delete_sec_context(void *internal_ctx) crypto_free_shash(kctx->initiator_sign_shash); crypto_free_aead(kctx->acceptor_enc_aead); crypto_free_aead(kctx->initiator_enc_aead); - crypto_free_sync_skcipher(kctx->seq); - crypto_free_sync_skcipher(kctx->enc); - crypto_free_sync_skcipher(kctx->acceptor_enc); - crypto_free_sync_skcipher(kctx->initiator_enc); - crypto_free_sync_skcipher(kctx->acceptor_enc_aux); - crypto_free_sync_skcipher(kctx->initiator_enc_aux); - crypto_free_ahash(kctx->acceptor_sign); - crypto_free_ahash(kctx->initiator_sign); - crypto_free_ahash(kctx->acceptor_integ); - crypto_free_ahash(kctx->initiator_integ); kfree(kctx->mech_used.data); kfree(kctx); } From 979accbc6bcb551b095b678a6f0c41899080ccd1 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:50:59 -0400 Subject: [PATCH 043/106] SUNRPC: Remove dead code from rpcsec_gss_krb5 With all per-message crypto operations routed through crypto/krb5, a substantial body of code in rpcsec_gss_krb5 has no remaining callers. The internal key derivation functions (krb5_derive_key_v2, krb5_kdf_hmac_sha2, krb5_kdf_feedback_cmac) and the low-level crypto primitives (krb5_encrypt, gss_krb5_checksum, krb5_cbc_cts_ encrypt/decrypt, krb5_etm_checksum) are unreachable because their only call sites were the per-enctype function pointers removed in previous patches. Delete gss_krb5_keys.c entirely and strip the dead functions from gss_krb5_crypto.c. The KUnit test suite in gss_krb5_test.c exercised exactly these internal functions: RFC 3961 n-fold, RFC 3962 key derivation, RFC 6803 Camellia key derivation, and RFC 8009 AES-SHA2 key derivation, plus encryption self-tests that drove the now-removed encrypt routines. The corresponding test coverage is provided by the crypto/krb5 selftests in crypto/krb5/selftest.c. Remove the test file, the RPCSEC_GSS_KRB5_KUNIT_TEST Kconfig symbol, the .kunitconfig, and all VISIBLE_IF_KUNIT / EXPORT_SYMBOL_IF_KUNIT annotations. xdr_process_buf() walked xdr_buf segments through a per-segment callback and existed solely for the crypto routines in gss_krb5_crypto.c. With that file removed, xdr_process_buf() has no remaining callers. Its successor, xdr_buf_to_sg(), populates a scatterlist directly from an xdr_buf byte range and was introduced earlier in this series. With every consumer of struct gss_krb5_enctype removed, replace its remaining uses with the equivalent fields from struct krb5_enctype (key_len). Remove struct gss_krb5_enctype, the supported_gss_krb5_enctypes[] table, gss_krb5_lookup_enctype(), and the gk5e pointer from krb5_ctx. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- include/linux/sunrpc/xdr.h | 1 - net/sunrpc/.kunitconfig | 29 - net/sunrpc/Kconfig | 15 - net/sunrpc/auth_gss/Makefile | 4 +- net/sunrpc/auth_gss/gss_krb5_crypto.c | 561 +------ net/sunrpc/auth_gss/gss_krb5_internal.h | 75 - net/sunrpc/auth_gss/gss_krb5_keys.c | 546 ------- net/sunrpc/auth_gss/gss_krb5_mech.c | 178 +-- net/sunrpc/auth_gss/gss_krb5_test.c | 1868 ----------------------- net/sunrpc/xdr.c | 67 - 10 files changed, 6 insertions(+), 3338 deletions(-) delete mode 100644 net/sunrpc/.kunitconfig delete mode 100644 net/sunrpc/auth_gss/gss_krb5_keys.c delete mode 100644 net/sunrpc/auth_gss/gss_krb5_test.c diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h index f82446993fde..31971b01d962 100644 --- a/include/linux/sunrpc/xdr.h +++ b/include/linux/sunrpc/xdr.h @@ -275,7 +275,6 @@ extern void xdr_finish_decode(struct xdr_stream *xdr); extern __be32 *xdr_inline_decode(struct xdr_stream *xdr, size_t nbytes); extern unsigned int xdr_read_pages(struct xdr_stream *xdr, unsigned int len); extern void xdr_enter_page(struct xdr_stream *xdr, unsigned int len); -extern int xdr_process_buf(const struct xdr_buf *buf, unsigned int offset, unsigned int len, int (*actor)(struct scatterlist *, void *), void *data); extern void xdr_set_pagelen(struct xdr_stream *, unsigned int len); extern bool xdr_stream_subsegment(struct xdr_stream *xdr, struct xdr_buf *subbuf, unsigned int len); diff --git a/net/sunrpc/.kunitconfig b/net/sunrpc/.kunitconfig deleted file mode 100644 index eb02b906c295..000000000000 --- a/net/sunrpc/.kunitconfig +++ /dev/null @@ -1,29 +0,0 @@ -CONFIG_KUNIT=y -CONFIG_UBSAN=y -CONFIG_STACKTRACE=y -CONFIG_NET=y -CONFIG_NETWORK_FILESYSTEMS=y -CONFIG_INET=y -CONFIG_FILE_LOCKING=y -CONFIG_MULTIUSER=y -CONFIG_CRYPTO=y -CONFIG_CRYPTO_CBC=y -CONFIG_CRYPTO_CTS=y -CONFIG_CRYPTO_ECB=y -CONFIG_CRYPTO_HMAC=y -CONFIG_CRYPTO_CMAC=y -CONFIG_CRYPTO_MD5=y -CONFIG_CRYPTO_SHA1=y -CONFIG_CRYPTO_SHA256=y -CONFIG_CRYPTO_SHA512=y -CONFIG_CRYPTO_DES=y -CONFIG_CRYPTO_AES=y -CONFIG_CRYPTO_CAMELLIA=y -CONFIG_NFS_FS=y -CONFIG_SUNRPC=y -CONFIG_SUNRPC_GSS=y -CONFIG_RPCSEC_GSS_KRB5=y -CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1=y -CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA=y -CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2=y -CONFIG_RPCSEC_GSS_KRB5_KUNIT_TEST=y diff --git a/net/sunrpc/Kconfig b/net/sunrpc/Kconfig index 381e76975ea9..1c2e1fe9d365 100644 --- a/net/sunrpc/Kconfig +++ b/net/sunrpc/Kconfig @@ -73,21 +73,6 @@ config RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2 SHA-2 digests. These include aes128-cts-hmac-sha256-128 and aes256-cts-hmac-sha384-192. -config RPCSEC_GSS_KRB5_KUNIT_TEST - tristate "KUnit tests for RPCSEC GSS Kerberos" if !KUNIT_ALL_TESTS - depends on RPCSEC_GSS_KRB5 && KUNIT - default KUNIT_ALL_TESTS - help - This builds the KUnit tests for RPCSEC GSS Kerberos 5. - - KUnit tests run during boot and output the results to the debug - log in TAP format (https://testanything.org/). Only useful for - kernel devs running KUnit test harness and are not for inclusion - into a production build. - - For more information on KUnit and unit tests in general, refer - to the KUnit documentation in Documentation/dev-tools/kunit/. - config SUNRPC_DEBUG bool "RPC: Enable dprintk debugging" depends on SUNRPC && SYSCTL diff --git a/net/sunrpc/auth_gss/Makefile b/net/sunrpc/auth_gss/Makefile index 452f67deebc6..68676389d65f 100644 --- a/net/sunrpc/auth_gss/Makefile +++ b/net/sunrpc/auth_gss/Makefile @@ -12,6 +12,4 @@ auth_rpcgss-y := auth_gss.o \ obj-$(CONFIG_RPCSEC_GSS_KRB5) += rpcsec_gss_krb5.o rpcsec_gss_krb5-y := gss_krb5_mech.o gss_krb5_seal.o gss_krb5_unseal.o \ - gss_krb5_wrap.o gss_krb5_crypto.o gss_krb5_keys.o - -obj-$(CONFIG_RPCSEC_GSS_KRB5_KUNIT_TEST) += gss_krb5_test.o + gss_krb5_wrap.o gss_krb5_crypto.o diff --git a/net/sunrpc/auth_gss/gss_krb5_crypto.c b/net/sunrpc/auth_gss/gss_krb5_crypto.c index cfd5b56d1b52..cf461ebcdde5 100644 --- a/net/sunrpc/auth_gss/gss_krb5_crypto.c +++ b/net/sunrpc/auth_gss/gss_krb5_crypto.c @@ -34,19 +34,14 @@ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ -#include -#include -#include #include #include #include #include #include #include -#include #include #include -#include #include "gss_krb5_internal.h" @@ -54,303 +49,6 @@ # define RPCDBG_FACILITY RPCDBG_AUTH #endif -/** - * krb5_make_confounder - Generate a confounder string - * @p: memory location into which to write the string - * @conflen: string length to write, in octets - * - * RFCs 1964 and 3961 mention only "a random confounder" without going - * into detail about its function or cryptographic requirements. The - * assumed purpose is to prevent repeated encryption of a plaintext with - * the same key from generating the same ciphertext. It is also used to - * pad minimum plaintext length to at least a single cipher block. - * - * However, in situations like the GSS Kerberos 5 mechanism, where the - * encryption IV is always all zeroes, the confounder also effectively - * functions like an IV. Thus, not only must it be unique from message - * to message, but it must also be difficult to predict. Otherwise an - * attacker can correlate the confounder to previous or future values, - * making the encryption easier to break. - * - * Given that the primary consumer of this encryption mechanism is a - * network storage protocol, a type of traffic that often carries - * predictable payloads (eg, all zeroes when reading unallocated blocks - * from a file), our confounder generation has to be cryptographically - * strong. - */ -void krb5_make_confounder(u8 *p, int conflen) -{ - get_random_bytes(p, conflen); -} - -/** - * krb5_encrypt - simple encryption of an RPCSEC GSS payload - * @tfm: initialized cipher transform - * @iv: pointer to an IV - * @in: plaintext to encrypt - * @out: OUT: ciphertext - * @length: length of input and output buffers, in bytes - * - * @iv may be NULL to force the use of an all-zero IV. - * The buffer containing the IV must be as large as the - * cipher's ivsize. - * - * Return values: - * %0: @in successfully encrypted into @out - * negative errno: @in not encrypted - */ -u32 -krb5_encrypt( - struct crypto_sync_skcipher *tfm, - void * iv, - void * in, - void * out, - int length) -{ - u32 ret = -EINVAL; - struct scatterlist sg[1]; - u8 local_iv[GSS_KRB5_MAX_BLOCKSIZE] = {0}; - SYNC_SKCIPHER_REQUEST_ON_STACK(req, tfm); - - if (length % crypto_sync_skcipher_blocksize(tfm) != 0) - goto out; - - if (crypto_sync_skcipher_ivsize(tfm) > GSS_KRB5_MAX_BLOCKSIZE) { - dprintk("RPC: gss_k5encrypt: tfm iv size too large %d\n", - crypto_sync_skcipher_ivsize(tfm)); - goto out; - } - - if (iv) - memcpy(local_iv, iv, crypto_sync_skcipher_ivsize(tfm)); - - memcpy(out, in, length); - sg_init_one(sg, out, length); - - skcipher_request_set_sync_tfm(req, tfm); - skcipher_request_set_callback(req, 0, NULL, NULL); - skcipher_request_set_crypt(req, sg, sg, length, local_iv); - - ret = crypto_skcipher_encrypt(req); - skcipher_request_zero(req); -out: - dprintk("RPC: krb5_encrypt returns %d\n", ret); - return ret; -} - -static int -checksummer(struct scatterlist *sg, void *data) -{ - struct ahash_request *req = data; - - ahash_request_set_crypt(req, sg, NULL, sg->length); - - return crypto_ahash_update(req); -} - -/** - * gss_krb5_checksum - Compute the MAC for a GSS Wrap or MIC token - * @tfm: an initialized hash transform - * @header: pointer to a buffer containing the token header, or NULL - * @hdrlen: number of octets in @header - * @body: xdr_buf containing an RPC message (body.len is the message length) - * @body_offset: byte offset into @body to start checksumming - * @cksumout: OUT: a buffer to be filled in with the computed HMAC - * - * Usually expressed as H = HMAC(K, message)[1..h] . - * - * Caller provides the truncation length of the output token (h) in - * cksumout.len. - * - * Return values: - * %GSS_S_COMPLETE: Digest computed, @cksumout filled in - * %GSS_S_FAILURE: Call failed - */ -u32 -gss_krb5_checksum(struct crypto_ahash *tfm, char *header, int hdrlen, - const struct xdr_buf *body, int body_offset, - struct xdr_netobj *cksumout) -{ - struct ahash_request *req; - int err = -ENOMEM; - u8 *checksumdata; - - checksumdata = kmalloc(crypto_ahash_digestsize(tfm), GFP_KERNEL); - if (!checksumdata) - return GSS_S_FAILURE; - - req = ahash_request_alloc(tfm, GFP_KERNEL); - if (!req) - goto out_free_cksum; - ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL); - err = crypto_ahash_init(req); - if (err) - goto out_free_ahash; - - /* - * Per RFC 4121 Section 4.2.4, the checksum is performed over the - * data body first, then over the octets in "header". - */ - err = xdr_process_buf(body, body_offset, body->len - body_offset, - checksummer, req); - if (err) - goto out_free_ahash; - if (header) { - struct scatterlist sg[1]; - - sg_init_one(sg, header, hdrlen); - ahash_request_set_crypt(req, sg, NULL, hdrlen); - err = crypto_ahash_update(req); - if (err) - goto out_free_ahash; - } - - ahash_request_set_crypt(req, NULL, checksumdata, 0); - err = crypto_ahash_final(req); - if (err) - goto out_free_ahash; - - memcpy(cksumout->data, checksumdata, - min_t(int, cksumout->len, crypto_ahash_digestsize(tfm))); - -out_free_ahash: - ahash_request_free(req); -out_free_cksum: - kfree_sensitive(checksumdata); - return err ? GSS_S_FAILURE : GSS_S_COMPLETE; -} -EXPORT_SYMBOL_IF_KUNIT(gss_krb5_checksum); - -struct encryptor_desc { - u8 iv[GSS_KRB5_MAX_BLOCKSIZE]; - struct skcipher_request *req; - int pos; - struct xdr_buf *outbuf; - struct page **pages; - struct scatterlist infrags[4]; - struct scatterlist outfrags[4]; - int fragno; - int fraglen; -}; - -static int -encryptor(struct scatterlist *sg, void *data) -{ - struct encryptor_desc *desc = data; - struct xdr_buf *outbuf = desc->outbuf; - struct crypto_sync_skcipher *tfm = - crypto_sync_skcipher_reqtfm(desc->req); - struct page *in_page; - int thislen = desc->fraglen + sg->length; - int fraglen, ret; - int page_pos; - - /* Worst case is 4 fragments: head, end of page 1, start - * of page 2, tail. Anything more is a bug. */ - BUG_ON(desc->fragno > 3); - - page_pos = desc->pos - outbuf->head[0].iov_len; - if (page_pos >= 0 && page_pos < outbuf->page_len) { - /* pages are not in place: */ - int i = (page_pos + outbuf->page_base) >> PAGE_SHIFT; - in_page = desc->pages[i]; - } else { - in_page = sg_page(sg); - } - sg_set_page(&desc->infrags[desc->fragno], in_page, sg->length, - sg->offset); - sg_set_page(&desc->outfrags[desc->fragno], sg_page(sg), sg->length, - sg->offset); - desc->fragno++; - desc->fraglen += sg->length; - desc->pos += sg->length; - - fraglen = thislen & (crypto_sync_skcipher_blocksize(tfm) - 1); - thislen -= fraglen; - - if (thislen == 0) - return 0; - - sg_mark_end(&desc->infrags[desc->fragno - 1]); - sg_mark_end(&desc->outfrags[desc->fragno - 1]); - - skcipher_request_set_crypt(desc->req, desc->infrags, desc->outfrags, - thislen, desc->iv); - - ret = crypto_skcipher_encrypt(desc->req); - if (ret) - return ret; - - sg_init_table(desc->infrags, 4); - sg_init_table(desc->outfrags, 4); - - if (fraglen) { - sg_set_page(&desc->outfrags[0], sg_page(sg), fraglen, - sg->offset + sg->length - fraglen); - desc->infrags[0] = desc->outfrags[0]; - sg_assign_page(&desc->infrags[0], in_page); - desc->fragno = 1; - desc->fraglen = fraglen; - } else { - desc->fragno = 0; - desc->fraglen = 0; - } - return 0; -} - -struct decryptor_desc { - u8 iv[GSS_KRB5_MAX_BLOCKSIZE]; - struct skcipher_request *req; - struct scatterlist frags[4]; - int fragno; - int fraglen; -}; - -static int -decryptor(struct scatterlist *sg, void *data) -{ - struct decryptor_desc *desc = data; - int thislen = desc->fraglen + sg->length; - struct crypto_sync_skcipher *tfm = - crypto_sync_skcipher_reqtfm(desc->req); - int fraglen, ret; - - /* Worst case is 4 fragments: head, end of page 1, start - * of page 2, tail. Anything more is a bug. */ - BUG_ON(desc->fragno > 3); - sg_set_page(&desc->frags[desc->fragno], sg_page(sg), sg->length, - sg->offset); - desc->fragno++; - desc->fraglen += sg->length; - - fraglen = thislen & (crypto_sync_skcipher_blocksize(tfm) - 1); - thislen -= fraglen; - - if (thislen == 0) - return 0; - - sg_mark_end(&desc->frags[desc->fragno - 1]); - - skcipher_request_set_crypt(desc->req, desc->frags, desc->frags, - thislen, desc->iv); - - ret = crypto_skcipher_decrypt(desc->req); - if (ret) - return ret; - - sg_init_table(desc->frags, 4); - - if (fraglen) { - sg_set_page(&desc->frags[0], sg_page(sg), fraglen, - sg->offset + sg->length - fraglen); - desc->fragno = 1; - desc->fraglen = fraglen; - } else { - desc->fragno = 0; - desc->fraglen = 0; - } - return 0; -} /* * This function makes the assumption that it was ultimately called @@ -363,7 +61,7 @@ decryptor(struct scatterlist *sg, void *data) * * Even with that guarantee, this function may be called more than * once in the processing of gss_wrap(). The best we can do is - * verify at compile-time (see GSS_KRB5_SLACK_CHECK) that the + * verify at compile-time (see GSS_KRB5_MAX_SLACK_NEEDED) that the * largest expected shift will fit within RPC_MAX_AUTH_SIZE. * At run-time we can verify that a single invocation of this * function doesn't attempt to use more the RPC_MAX_AUTH_SIZE. @@ -389,263 +87,6 @@ xdr_extend_head(struct xdr_buf *buf, unsigned int base, unsigned int shiftlen) return 0; } -static u32 -gss_krb5_cts_crypt(struct crypto_sync_skcipher *cipher, struct xdr_buf *buf, - u32 offset, u8 *iv, struct page **pages, int encrypt) -{ - u32 ret; - struct scatterlist sg[1]; - SYNC_SKCIPHER_REQUEST_ON_STACK(req, cipher); - u8 *data; - struct page **save_pages; - u32 len = buf->len - offset; - - if (len > GSS_KRB5_MAX_BLOCKSIZE * 2) { - WARN_ON(0); - return -ENOMEM; - } - data = kmalloc(GSS_KRB5_MAX_BLOCKSIZE * 2, GFP_KERNEL); - if (!data) - return -ENOMEM; - - /* - * For encryption, we want to read from the cleartext - * page cache pages, and write the encrypted data to - * the supplied xdr_buf pages. - */ - save_pages = buf->pages; - if (encrypt) - buf->pages = pages; - - ret = read_bytes_from_xdr_buf(buf, offset, data, len); - buf->pages = save_pages; - if (ret) - goto out; - - sg_init_one(sg, data, len); - - skcipher_request_set_sync_tfm(req, cipher); - skcipher_request_set_callback(req, 0, NULL, NULL); - skcipher_request_set_crypt(req, sg, sg, len, iv); - - if (encrypt) - ret = crypto_skcipher_encrypt(req); - else - ret = crypto_skcipher_decrypt(req); - - skcipher_request_zero(req); - - if (ret) - goto out; - - ret = write_bytes_to_xdr_buf(buf, offset, data, len); - -#if IS_ENABLED(CONFIG_KUNIT) - /* - * CBC-CTS does not define an output IV but RFC 3962 defines it as the - * penultimate block of ciphertext, so copy that into the IV buffer - * before returning. - */ - if (encrypt) - memcpy(iv, data, crypto_sync_skcipher_ivsize(cipher)); -#endif - -out: - kfree(data); - return ret; -} - -/** - * krb5_cbc_cts_encrypt - encrypt in CBC mode with CTS - * @cts_tfm: CBC cipher with CTS - * @cbc_tfm: base CBC cipher - * @offset: starting byte offset for plaintext - * @buf: OUT: output buffer - * @pages: plaintext - * @iv: output CBC initialization vector, or NULL - * @ivsize: size of @iv, in octets - * - * To provide confidentiality, encrypt using cipher block chaining - * with ciphertext stealing. Message integrity is handled separately. - * - * Return values: - * %0: encryption successful - * negative errno: encryption could not be completed - */ -VISIBLE_IF_KUNIT -int krb5_cbc_cts_encrypt(struct crypto_sync_skcipher *cts_tfm, - struct crypto_sync_skcipher *cbc_tfm, - u32 offset, struct xdr_buf *buf, struct page **pages, - u8 *iv, unsigned int ivsize) -{ - u32 blocksize, nbytes, nblocks, cbcbytes; - struct encryptor_desc desc; - int err; - - blocksize = crypto_sync_skcipher_blocksize(cts_tfm); - nbytes = buf->len - offset; - nblocks = (nbytes + blocksize - 1) / blocksize; - cbcbytes = 0; - if (nblocks > 2) - cbcbytes = (nblocks - 2) * blocksize; - - memset(desc.iv, 0, sizeof(desc.iv)); - - /* Handle block-sized chunks of plaintext with CBC. */ - if (cbcbytes) { - SYNC_SKCIPHER_REQUEST_ON_STACK(req, cbc_tfm); - - desc.pos = offset; - desc.fragno = 0; - desc.fraglen = 0; - desc.pages = pages; - desc.outbuf = buf; - desc.req = req; - - skcipher_request_set_sync_tfm(req, cbc_tfm); - skcipher_request_set_callback(req, 0, NULL, NULL); - - sg_init_table(desc.infrags, 4); - sg_init_table(desc.outfrags, 4); - - err = xdr_process_buf(buf, offset, cbcbytes, encryptor, &desc); - skcipher_request_zero(req); - if (err) - return err; - } - - /* Remaining plaintext is handled with CBC-CTS. */ - err = gss_krb5_cts_crypt(cts_tfm, buf, offset + cbcbytes, - desc.iv, pages, 1); - if (err) - return err; - - if (unlikely(iv)) - memcpy(iv, desc.iv, ivsize); - return 0; -} -EXPORT_SYMBOL_IF_KUNIT(krb5_cbc_cts_encrypt); - -/** - * krb5_cbc_cts_decrypt - decrypt in CBC mode with CTS - * @cts_tfm: CBC cipher with CTS - * @cbc_tfm: base CBC cipher - * @offset: starting byte offset for plaintext - * @buf: OUT: output buffer - * - * Return values: - * %0: decryption successful - * negative errno: decryption could not be completed - */ -VISIBLE_IF_KUNIT -int krb5_cbc_cts_decrypt(struct crypto_sync_skcipher *cts_tfm, - struct crypto_sync_skcipher *cbc_tfm, - u32 offset, struct xdr_buf *buf) -{ - u32 blocksize, nblocks, cbcbytes; - struct decryptor_desc desc; - int err; - - blocksize = crypto_sync_skcipher_blocksize(cts_tfm); - nblocks = (buf->len + blocksize - 1) / blocksize; - cbcbytes = 0; - if (nblocks > 2) - cbcbytes = (nblocks - 2) * blocksize; - - memset(desc.iv, 0, sizeof(desc.iv)); - - /* Handle block-sized chunks of plaintext with CBC. */ - if (cbcbytes) { - SYNC_SKCIPHER_REQUEST_ON_STACK(req, cbc_tfm); - - desc.fragno = 0; - desc.fraglen = 0; - desc.req = req; - - skcipher_request_set_sync_tfm(req, cbc_tfm); - skcipher_request_set_callback(req, 0, NULL, NULL); - - sg_init_table(desc.frags, 4); - - err = xdr_process_buf(buf, 0, cbcbytes, decryptor, &desc); - skcipher_request_zero(req); - if (err) - return err; - } - - /* Remaining plaintext is handled with CBC-CTS. */ - return gss_krb5_cts_crypt(cts_tfm, buf, cbcbytes, desc.iv, NULL, 0); -} -EXPORT_SYMBOL_IF_KUNIT(krb5_cbc_cts_decrypt); - -/** - * krb5_etm_checksum - Compute a MAC for a GSS Wrap token - * @cipher: an initialized cipher transform - * @tfm: an initialized hash transform - * @body: xdr_buf containing an RPC message (body.len is the message length) - * @body_offset: byte offset into @body to start checksumming - * @cksumout: OUT: a buffer to be filled in with the computed HMAC - * - * Usually expressed as H = HMAC(K, IV | ciphertext)[1..h] . - * - * Caller provides the truncation length of the output token (h) in - * cksumout.len. - * - * Return values: - * %GSS_S_COMPLETE: Digest computed, @cksumout filled in - * %GSS_S_FAILURE: Call failed - */ -VISIBLE_IF_KUNIT -u32 krb5_etm_checksum(struct crypto_sync_skcipher *cipher, - struct crypto_ahash *tfm, const struct xdr_buf *body, - int body_offset, struct xdr_netobj *cksumout) -{ - unsigned int ivsize = crypto_sync_skcipher_ivsize(cipher); - struct ahash_request *req; - struct scatterlist sg[1]; - u8 *iv, *checksumdata; - int err = -ENOMEM; - - checksumdata = kmalloc(crypto_ahash_digestsize(tfm), GFP_KERNEL); - if (!checksumdata) - return GSS_S_FAILURE; - /* For RPCSEC, the "initial cipher state" is always all zeroes. */ - iv = kzalloc(ivsize, GFP_KERNEL); - if (!iv) - goto out_free_mem; - - req = ahash_request_alloc(tfm, GFP_KERNEL); - if (!req) - goto out_free_mem; - ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, NULL, NULL); - err = crypto_ahash_init(req); - if (err) - goto out_free_ahash; - - sg_init_one(sg, iv, ivsize); - ahash_request_set_crypt(req, sg, NULL, ivsize); - err = crypto_ahash_update(req); - if (err) - goto out_free_ahash; - err = xdr_process_buf(body, body_offset, body->len - body_offset, - checksummer, req); - if (err) - goto out_free_ahash; - - ahash_request_set_crypt(req, NULL, checksumdata, 0); - err = crypto_ahash_final(req); - if (err) - goto out_free_ahash; - memcpy(cksumout->data, checksumdata, cksumout->len); - -out_free_ahash: - ahash_request_free(req); -out_free_mem: - kfree(iv); - kfree_sensitive(checksumdata); - return err ? GSS_S_FAILURE : GSS_S_COMPLETE; -} -EXPORT_SYMBOL_IF_KUNIT(krb5_etm_checksum); /** * gss_krb5_aead_encrypt - Encrypt a wrap token using crypto/krb5 diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 6b08a7486e0b..208f9df9ea96 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -10,38 +10,8 @@ #include -/* - * The RFCs often specify payload lengths in bits. This helper - * converts a specified bit-length to the number of octets/bytes. - */ -#define BITS2OCTETS(x) ((x) / 8) - struct krb5_ctx; -struct gss_krb5_enctype { - const u32 etype; /* encryption (key) type */ - const u32 ctype; /* checksum type */ - const char *name; /* "friendly" name */ - const char *encrypt_name; /* crypto encrypt name */ - const char *aux_cipher; /* aux encrypt cipher name */ - const char *cksum_name; /* crypto checksum name */ - const u16 signalg; /* signing algorithm */ - const u16 sealalg; /* sealing algorithm */ - const u32 cksumlength; /* checksum length */ - const u32 keyed_cksum; /* is it a keyed cksum? */ - const u32 keybytes; /* raw key len, in bytes */ - const u32 keylength; /* protocol key length, in octets */ - const u32 Kc_length; /* checksum subkey length, in octets */ - const u32 Ke_length; /* encryption subkey length, in octets */ - const u32 Ki_length; /* integrity subkey length, in octets */ - - int (*derive_key)(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *in, - struct xdr_netobj *out, - const struct xdr_netobj *label, - gfp_t gfp_mask); -}; - /* krb5_ctx flags definitions */ #define KRB5_CTX_FLAG_INITIATOR 0x00000001 #define KRB5_CTX_FLAG_ACCEPTOR_SUBKEY 0x00000004 @@ -50,7 +20,6 @@ struct krb5_ctx { int initiate; /* 1 = initiating, 0 = accepting */ u32 enctype; u32 flags; - const struct gss_krb5_enctype *gk5e; /* enctype-specific info */ const struct krb5_enctype *krb5e; /* crypto/krb5 enctype */ struct crypto_aead *initiator_enc_aead; struct crypto_aead *acceptor_enc_aead; @@ -58,7 +27,6 @@ struct krb5_ctx { struct crypto_shash *acceptor_sign_shash; u8 Ksess[GSS_KRB5_MAX_KEYLEN]; /* session key */ u8 cksum[GSS_KRB5_MAX_KEYLEN]; - atomic_t seq_send; atomic64_t seq_send64; time64_t endtime; struct xdr_netobj mech_used; @@ -85,35 +53,6 @@ u32 gss_krb5_unwrap_v2(struct krb5_ctx *kctx, int offset, int len, * Implementation internal functions */ -/* Key Derivation Functions */ - -int krb5_derive_key_v2(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *inkey, - struct xdr_netobj *outkey, - const struct xdr_netobj *label, - gfp_t gfp_mask); - -int krb5_kdf_hmac_sha2(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *inkey, - struct xdr_netobj *outkey, - const struct xdr_netobj *in_constant, - gfp_t gfp_mask); - -int krb5_kdf_feedback_cmac(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *inkey, - struct xdr_netobj *outkey, - const struct xdr_netobj *in_constant, - gfp_t gfp_mask); - -void krb5_make_confounder(u8 *p, int conflen); - -u32 gss_krb5_checksum(struct crypto_ahash *tfm, char *header, int hdrlen, - const struct xdr_buf *body, int body_offset, - struct xdr_netobj *cksumout); - -u32 krb5_encrypt(struct crypto_sync_skcipher *key, void *iv, void *in, - void *out, int length); - int xdr_extend_head(struct xdr_buf *buf, unsigned int base, unsigned int shiftlen); @@ -130,19 +69,5 @@ u32 gss_krb5_aead_encrypt(struct krb5_ctx *kctx, u32 offset, u32 gss_krb5_aead_decrypt(struct krb5_ctx *kctx, u32 offset, u32 len, struct xdr_buf *buf, u32 *headskip, u32 *tailskip); -#if IS_ENABLED(CONFIG_KUNIT) -void krb5_nfold(u32 inbits, const u8 *in, u32 outbits, u8 *out); -const struct gss_krb5_enctype *gss_krb5_lookup_enctype(u32 etype); -int krb5_cbc_cts_encrypt(struct crypto_sync_skcipher *cts_tfm, - struct crypto_sync_skcipher *cbc_tfm, u32 offset, - struct xdr_buf *buf, struct page **pages, - u8 *iv, unsigned int ivsize); -int krb5_cbc_cts_decrypt(struct crypto_sync_skcipher *cts_tfm, - struct crypto_sync_skcipher *cbc_tfm, - u32 offset, struct xdr_buf *buf); -u32 krb5_etm_checksum(struct crypto_sync_skcipher *cipher, - struct crypto_ahash *tfm, const struct xdr_buf *body, - int body_offset, struct xdr_netobj *cksumout); -#endif #endif /* _NET_SUNRPC_AUTH_GSS_KRB5_INTERNAL_H */ diff --git a/net/sunrpc/auth_gss/gss_krb5_keys.c b/net/sunrpc/auth_gss/gss_krb5_keys.c deleted file mode 100644 index 4eb19c3a54c7..000000000000 --- a/net/sunrpc/auth_gss/gss_krb5_keys.c +++ /dev/null @@ -1,546 +0,0 @@ -/* - * COPYRIGHT (c) 2008 - * The Regents of the University of Michigan - * ALL RIGHTS RESERVED - * - * Permission is granted to use, copy, create derivative works - * and redistribute this software and such derivative works - * for any purpose, so long as the name of The University of - * Michigan is not used in any advertising or publicity - * pertaining to the use of distribution of this software - * without specific, written prior authorization. If the - * above copyright notice or any other identification of the - * University of Michigan is included in any copy of any - * portion of this software, then the disclaimer below must - * also be included. - * - * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION - * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY - * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF - * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING - * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE - * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE - * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR - * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING - * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN - * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGES. - */ - -/* - * Copyright (C) 1998 by the FundsXpress, INC. - * - * All rights reserved. - * - * Export of this software from the United States of America may require - * a specific license from the United States Government. It is the - * responsibility of any person or organization contemplating export to - * obtain such a license before exporting. - * - * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and - * distribute this software and its documentation for any purpose and - * without fee is hereby granted, provided that the above copyright - * notice appear in all copies and that both that copyright notice and - * this permission notice appear in supporting documentation, and that - * the name of FundsXpress. not be used in advertising or publicity pertaining - * to distribution of the software without specific, written prior - * permission. FundsXpress makes no representations about the suitability of - * this software for any purpose. It is provided "as is" without express - * or implied warranty. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gss_krb5_internal.h" - -#if IS_ENABLED(CONFIG_SUNRPC_DEBUG) -# define RPCDBG_FACILITY RPCDBG_AUTH -#endif - -/** - * krb5_nfold - n-fold function - * @inbits: number of bits in @in - * @in: buffer containing input to fold - * @outbits: number of bits in the output buffer - * @out: buffer to hold the result - * - * This is the n-fold function as described in rfc3961, sec 5.1 - * Taken from MIT Kerberos and modified. - */ -VISIBLE_IF_KUNIT -void krb5_nfold(u32 inbits, const u8 *in, u32 outbits, u8 *out) -{ - unsigned long ulcm; - int byte, i, msbit; - - /* the code below is more readable if I make these bytes - instead of bits */ - - inbits >>= 3; - outbits >>= 3; - - /* first compute lcm(n,k) */ - ulcm = lcm(inbits, outbits); - - /* now do the real work */ - - memset(out, 0, outbits); - byte = 0; - - /* this will end up cycling through k lcm(k,n)/k times, which - is correct */ - for (i = ulcm-1; i >= 0; i--) { - /* compute the msbit in k which gets added into this byte */ - msbit = ( - /* first, start with the msbit in the first, - * unrotated byte */ - ((inbits << 3) - 1) - /* then, for each byte, shift to the right - * for each repetition */ - + (((inbits << 3) + 13) * (i/inbits)) - /* last, pick out the correct byte within - * that shifted repetition */ - + ((inbits - (i % inbits)) << 3) - ) % (inbits << 3); - - /* pull out the byte value itself */ - byte += (((in[((inbits - 1) - (msbit >> 3)) % inbits] << 8)| - (in[((inbits) - (msbit >> 3)) % inbits])) - >> ((msbit & 7) + 1)) & 0xff; - - /* do the addition */ - byte += out[i % outbits]; - out[i % outbits] = byte & 0xff; - - /* keep around the carry bit, if any */ - byte >>= 8; - - } - - /* if there's a carry bit left over, add it back in */ - if (byte) { - for (i = outbits - 1; i >= 0; i--) { - /* do the addition */ - byte += out[i]; - out[i] = byte & 0xff; - - /* keep around the carry bit, if any */ - byte >>= 8; - } - } -} -EXPORT_SYMBOL_IF_KUNIT(krb5_nfold); - -/* - * This is the DK (derive_key) function as described in rfc3961, sec 5.1 - * Taken from MIT Kerberos and modified. - */ -static int krb5_DK(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *inkey, u8 *rawkey, - const struct xdr_netobj *in_constant, gfp_t gfp_mask) -{ - size_t blocksize, keybytes, keylength, n; - unsigned char *inblockdata, *outblockdata; - struct xdr_netobj inblock, outblock; - struct crypto_sync_skcipher *cipher; - int ret = -EINVAL; - - keybytes = gk5e->keybytes; - keylength = gk5e->keylength; - - if (inkey->len != keylength) - goto err_return; - - cipher = crypto_alloc_sync_skcipher(gk5e->encrypt_name, 0, 0); - if (IS_ERR(cipher)) - goto err_return; - blocksize = crypto_sync_skcipher_blocksize(cipher); - if (crypto_sync_skcipher_setkey(cipher, inkey->data, inkey->len)) - goto err_free_cipher; - - ret = -ENOMEM; - inblockdata = kmalloc(blocksize, gfp_mask); - if (inblockdata == NULL) - goto err_free_cipher; - - outblockdata = kmalloc(blocksize, gfp_mask); - if (outblockdata == NULL) - goto err_free_in; - - inblock.data = (char *) inblockdata; - inblock.len = blocksize; - - outblock.data = (char *) outblockdata; - outblock.len = blocksize; - - /* initialize the input block */ - - if (in_constant->len == inblock.len) { - memcpy(inblock.data, in_constant->data, inblock.len); - } else { - krb5_nfold(in_constant->len * 8, in_constant->data, - inblock.len * 8, inblock.data); - } - - /* loop encrypting the blocks until enough key bytes are generated */ - - n = 0; - while (n < keybytes) { - krb5_encrypt(cipher, NULL, inblock.data, outblock.data, - inblock.len); - - if ((keybytes - n) <= outblock.len) { - memcpy(rawkey + n, outblock.data, (keybytes - n)); - break; - } - - memcpy(rawkey + n, outblock.data, outblock.len); - memcpy(inblock.data, outblock.data, outblock.len); - n += outblock.len; - } - - ret = 0; - - kfree_sensitive(outblockdata); -err_free_in: - kfree_sensitive(inblockdata); -err_free_cipher: - crypto_free_sync_skcipher(cipher); -err_return: - return ret; -} - -/* - * This is the identity function, with some sanity checking. - */ -static int krb5_random_to_key_v2(const struct gss_krb5_enctype *gk5e, - struct xdr_netobj *randombits, - struct xdr_netobj *key) -{ - int ret = -EINVAL; - - if (key->len != 16 && key->len != 32) { - dprintk("%s: key->len is %d\n", __func__, key->len); - goto err_out; - } - if (randombits->len != 16 && randombits->len != 32) { - dprintk("%s: randombits->len is %d\n", - __func__, randombits->len); - goto err_out; - } - if (randombits->len != key->len) { - dprintk("%s: randombits->len is %d, key->len is %d\n", - __func__, randombits->len, key->len); - goto err_out; - } - memcpy(key->data, randombits->data, key->len); - ret = 0; -err_out: - return ret; -} - -/** - * krb5_derive_key_v2 - Derive a subkey for an RFC 3962 enctype - * @gk5e: Kerberos 5 enctype profile - * @inkey: base protocol key - * @outkey: OUT: derived key - * @label: subkey usage label - * @gfp_mask: memory allocation control flags - * - * Caller sets @outkey->len to the desired length of the derived key. - * - * On success, returns 0 and fills in @outkey. A negative errno value - * is returned on failure. - */ -int krb5_derive_key_v2(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *inkey, - struct xdr_netobj *outkey, - const struct xdr_netobj *label, - gfp_t gfp_mask) -{ - struct xdr_netobj inblock; - int ret; - - inblock.len = gk5e->keybytes; - inblock.data = kmalloc(inblock.len, gfp_mask); - if (!inblock.data) - return -ENOMEM; - - ret = krb5_DK(gk5e, inkey, inblock.data, label, gfp_mask); - if (!ret) - ret = krb5_random_to_key_v2(gk5e, &inblock, outkey); - - kfree_sensitive(inblock.data); - return ret; -} - -/* - * K(i) = CMAC(key, K(i-1) | i | constant | 0x00 | k) - * - * i: A block counter is used with a length of 4 bytes, represented - * in big-endian order. - * - * constant: The label input to the KDF is the usage constant supplied - * to the key derivation function - * - * k: The length of the output key in bits, represented as a 4-byte - * string in big-endian order. - * - * Caller fills in K(i-1) in @step, and receives the result K(i) - * in the same buffer. - */ -static int -krb5_cmac_Ki(struct crypto_shash *tfm, const struct xdr_netobj *constant, - u32 outlen, u32 count, struct xdr_netobj *step) -{ - __be32 k = cpu_to_be32(outlen * 8); - SHASH_DESC_ON_STACK(desc, tfm); - __be32 i = cpu_to_be32(count); - u8 zero = 0; - int ret; - - desc->tfm = tfm; - ret = crypto_shash_init(desc); - if (ret) - goto out_err; - - ret = crypto_shash_update(desc, step->data, step->len); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, (u8 *)&i, sizeof(i)); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, constant->data, constant->len); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, &zero, sizeof(zero)); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, (u8 *)&k, sizeof(k)); - if (ret) - goto out_err; - ret = crypto_shash_final(desc, step->data); - if (ret) - goto out_err; - -out_err: - shash_desc_zero(desc); - return ret; -} - -/** - * krb5_kdf_feedback_cmac - Derive a subkey for a Camellia/CMAC-based enctype - * @gk5e: Kerberos 5 enctype parameters - * @inkey: base protocol key - * @outkey: OUT: derived key - * @constant: subkey usage label - * @gfp_mask: memory allocation control flags - * - * RFC 6803 Section 3: - * - * "We use a key derivation function from the family specified in - * [SP800-108], Section 5.2, 'KDF in Feedback Mode'." - * - * n = ceiling(k / 128) - * K(0) = zeros - * K(i) = CMAC(key, K(i-1) | i | constant | 0x00 | k) - * DR(key, constant) = k-truncate(K(1) | K(2) | ... | K(n)) - * KDF-FEEDBACK-CMAC(key, constant) = random-to-key(DR(key, constant)) - * - * Caller sets @outkey->len to the desired length of the derived key (k). - * - * On success, returns 0 and fills in @outkey. A negative errno value - * is returned on failure. - */ -int -krb5_kdf_feedback_cmac(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *inkey, - struct xdr_netobj *outkey, - const struct xdr_netobj *constant, - gfp_t gfp_mask) -{ - struct xdr_netobj step = { .data = NULL }; - struct xdr_netobj DR = { .data = NULL }; - unsigned int blocksize, offset; - struct crypto_shash *tfm; - int n, count, ret; - - /* - * This implementation assumes the CMAC used for an enctype's - * key derivation is the same as the CMAC used for its - * checksumming. This happens to be true for enctypes that - * are currently supported by this implementation. - */ - tfm = crypto_alloc_shash(gk5e->cksum_name, 0, 0); - if (IS_ERR(tfm)) { - ret = PTR_ERR(tfm); - goto out; - } - ret = crypto_shash_setkey(tfm, inkey->data, inkey->len); - if (ret) - goto out_free_tfm; - - blocksize = crypto_shash_digestsize(tfm); - n = (outkey->len + blocksize - 1) / blocksize; - - /* K(0) is all zeroes */ - ret = -ENOMEM; - step.len = blocksize; - step.data = kzalloc(step.len, gfp_mask); - if (!step.data) - goto out_free_tfm; - - DR.len = blocksize * n; - DR.data = kmalloc(DR.len, gfp_mask); - if (!DR.data) - goto out_free_tfm; - - /* XXX: Does not handle partial-block key sizes */ - for (offset = 0, count = 1; count <= n; count++) { - ret = krb5_cmac_Ki(tfm, constant, outkey->len, count, &step); - if (ret) - goto out_free_tfm; - - memcpy(DR.data + offset, step.data, blocksize); - offset += blocksize; - } - - /* k-truncate and random-to-key */ - memcpy(outkey->data, DR.data, outkey->len); - ret = 0; - -out_free_tfm: - crypto_free_shash(tfm); -out: - kfree_sensitive(step.data); - kfree_sensitive(DR.data); - return ret; -} - -/* - * K1 = HMAC-SHA(key, 0x00000001 | label | 0x00 | k) - * - * key: The source of entropy from which subsequent keys are derived. - * - * label: An octet string describing the intended usage of the - * derived key. - * - * k: Length in bits of the key to be outputted, expressed in - * big-endian binary representation in 4 bytes. - */ -static int -krb5_hmac_K1(struct crypto_shash *tfm, const struct xdr_netobj *label, - u32 outlen, struct xdr_netobj *K1) -{ - __be32 k = cpu_to_be32(outlen * 8); - SHASH_DESC_ON_STACK(desc, tfm); - __be32 one = cpu_to_be32(1); - u8 zero = 0; - int ret; - - desc->tfm = tfm; - ret = crypto_shash_init(desc); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, (u8 *)&one, sizeof(one)); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, label->data, label->len); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, &zero, sizeof(zero)); - if (ret) - goto out_err; - ret = crypto_shash_update(desc, (u8 *)&k, sizeof(k)); - if (ret) - goto out_err; - ret = crypto_shash_final(desc, K1->data); - if (ret) - goto out_err; - -out_err: - shash_desc_zero(desc); - return ret; -} - -/** - * krb5_kdf_hmac_sha2 - Derive a subkey for an AES/SHA2-based enctype - * @gk5e: Kerberos 5 enctype policy parameters - * @inkey: base protocol key - * @outkey: OUT: derived key - * @label: subkey usage label - * @gfp_mask: memory allocation control flags - * - * RFC 8009 Section 3: - * - * "We use a key derivation function from Section 5.1 of [SP800-108], - * which uses the HMAC algorithm as the PRF." - * - * function KDF-HMAC-SHA2(key, label, [context,] k): - * k-truncate(K1) - * - * Caller sets @outkey->len to the desired length of the derived key. - * - * On success, returns 0 and fills in @outkey. A negative errno value - * is returned on failure. - */ -int -krb5_kdf_hmac_sha2(const struct gss_krb5_enctype *gk5e, - const struct xdr_netobj *inkey, - struct xdr_netobj *outkey, - const struct xdr_netobj *label, - gfp_t gfp_mask) -{ - struct crypto_shash *tfm; - struct xdr_netobj K1 = { - .data = NULL, - }; - int ret; - - /* - * This implementation assumes the HMAC used for an enctype's - * key derivation is the same as the HMAC used for its - * checksumming. This happens to be true for enctypes that - * are currently supported by this implementation. - */ - tfm = crypto_alloc_shash(gk5e->cksum_name, 0, 0); - if (IS_ERR(tfm)) { - ret = PTR_ERR(tfm); - goto out; - } - ret = crypto_shash_setkey(tfm, inkey->data, inkey->len); - if (ret) - goto out_free_tfm; - - K1.len = crypto_shash_digestsize(tfm); - K1.data = kmalloc(K1.len, gfp_mask); - if (!K1.data) { - ret = -ENOMEM; - goto out_free_tfm; - } - - ret = krb5_hmac_K1(tfm, label, outkey->len, &K1); - if (ret) - goto out_free_tfm; - - /* k-truncate and random-to-key */ - memcpy(outkey->data, K1.data, outkey->len); - -out_free_tfm: - kfree_sensitive(K1.data); - crypto_free_shash(tfm); -out: - return ret; -} diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index d8cb79fd2463..5a52fd84f946 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -17,7 +17,6 @@ #include #include #include -#include #include "auth_gss_internal.h" #include "gss_krb5_internal.h" @@ -28,141 +27,6 @@ static struct gss_api_mech gss_kerberos_mech; -static const struct gss_krb5_enctype supported_gss_krb5_enctypes[] = { -#if defined(CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1) - /* - * AES-128 with SHA-1 (RFC 3962) - */ - { - .etype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .ctype = CKSUMTYPE_HMAC_SHA1_96_AES128, - .name = "aes128-cts", - .encrypt_name = "cts(cbc(aes))", - .aux_cipher = "cbc(aes)", - .cksum_name = "hmac(sha1)", - .derive_key = krb5_derive_key_v2, - - .signalg = -1, - .sealalg = -1, - .keybytes = 16, - .keylength = BITS2OCTETS(128), - .Kc_length = BITS2OCTETS(128), - .Ke_length = BITS2OCTETS(128), - .Ki_length = BITS2OCTETS(128), - .cksumlength = BITS2OCTETS(96), - .keyed_cksum = 1, - }, - /* - * AES-256 with SHA-1 (RFC 3962) - */ - { - .etype = ENCTYPE_AES256_CTS_HMAC_SHA1_96, - .ctype = CKSUMTYPE_HMAC_SHA1_96_AES256, - .name = "aes256-cts", - .encrypt_name = "cts(cbc(aes))", - .aux_cipher = "cbc(aes)", - .cksum_name = "hmac(sha1)", - .derive_key = krb5_derive_key_v2, - - .signalg = -1, - .sealalg = -1, - .keybytes = 32, - .keylength = BITS2OCTETS(256), - .Kc_length = BITS2OCTETS(256), - .Ke_length = BITS2OCTETS(256), - .Ki_length = BITS2OCTETS(256), - .cksumlength = BITS2OCTETS(96), - .keyed_cksum = 1, - }, -#endif - -#if defined(CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA) - /* - * Camellia-128 with CMAC (RFC 6803) - */ - { - .etype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .ctype = CKSUMTYPE_CMAC_CAMELLIA128, - .name = "camellia128-cts-cmac", - .encrypt_name = "cts(cbc(camellia))", - .aux_cipher = "cbc(camellia)", - .cksum_name = "cmac(camellia)", - .cksumlength = BITS2OCTETS(128), - .keyed_cksum = 1, - .keylength = BITS2OCTETS(128), - .Kc_length = BITS2OCTETS(128), - .Ke_length = BITS2OCTETS(128), - .Ki_length = BITS2OCTETS(128), - - .derive_key = krb5_kdf_feedback_cmac, - - }, - /* - * Camellia-256 with CMAC (RFC 6803) - */ - { - .etype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .ctype = CKSUMTYPE_CMAC_CAMELLIA256, - .name = "camellia256-cts-cmac", - .encrypt_name = "cts(cbc(camellia))", - .aux_cipher = "cbc(camellia)", - .cksum_name = "cmac(camellia)", - .cksumlength = BITS2OCTETS(128), - .keyed_cksum = 1, - .keylength = BITS2OCTETS(256), - .Kc_length = BITS2OCTETS(256), - .Ke_length = BITS2OCTETS(256), - .Ki_length = BITS2OCTETS(256), - - .derive_key = krb5_kdf_feedback_cmac, - - }, -#endif - -#if defined(CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2) - /* - * AES-128 with SHA-256 (RFC 8009) - */ - { - .etype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .ctype = CKSUMTYPE_HMAC_SHA256_128_AES128, - .name = "aes128-cts-hmac-sha256-128", - .encrypt_name = "cts(cbc(aes))", - .aux_cipher = "cbc(aes)", - .cksum_name = "hmac(sha256)", - .cksumlength = BITS2OCTETS(128), - .keyed_cksum = 1, - .keylength = BITS2OCTETS(128), - .Kc_length = BITS2OCTETS(128), - .Ke_length = BITS2OCTETS(128), - .Ki_length = BITS2OCTETS(128), - - .derive_key = krb5_kdf_hmac_sha2, - - }, - /* - * AES-256 with SHA-384 (RFC 8009) - */ - { - .etype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .ctype = CKSUMTYPE_HMAC_SHA384_192_AES256, - .name = "aes256-cts-hmac-sha384-192", - .encrypt_name = "cts(cbc(aes))", - .aux_cipher = "cbc(aes)", - .cksum_name = "hmac(sha384)", - .cksumlength = BITS2OCTETS(192), - .keyed_cksum = 1, - .keylength = BITS2OCTETS(256), - .Kc_length = BITS2OCTETS(192), - .Ke_length = BITS2OCTETS(256), - .Ki_length = BITS2OCTETS(192), - - .derive_key = krb5_kdf_hmac_sha2, - - }, -#endif -}; - /* * The list of advertised enctypes is specified in order of most * preferred to least. @@ -204,30 +68,11 @@ static void gss_krb5_prepare_enctype_priority_list(void) } } -/** - * gss_krb5_lookup_enctype - Retrieve profile information for a given enctype - * @etype: ENCTYPE value - * - * Returns a pointer to a gss_krb5_enctype structure, or NULL if no - * matching etype is found. - */ -VISIBLE_IF_KUNIT -const struct gss_krb5_enctype *gss_krb5_lookup_enctype(u32 etype) -{ - size_t i; - - for (i = 0; i < ARRAY_SIZE(supported_gss_krb5_enctypes); i++) - if (supported_gss_krb5_enctypes[i].etype == etype) - return &supported_gss_krb5_enctypes[i]; - return NULL; -} -EXPORT_SYMBOL_IF_KUNIT(gss_krb5_lookup_enctype); - static int gss_krb5_import_ctx_v2(struct krb5_ctx *ctx, gfp_t gfp_mask) { struct krb5_buffer TK = { - .len = ctx->gk5e->keylength, + .len = ctx->krb5e->key_len, .data = ctx->Ksess, }; int ret; @@ -298,32 +143,17 @@ gss_import_v2_context(const void *p, const void *end, struct krb5_ctx *ctx, if (IS_ERR(p)) goto out_err; atomic64_set(&ctx->seq_send64, seq_send64); - /* set seq_send for use by "older" enctypes */ - atomic_set(&ctx->seq_send, seq_send64); - if (seq_send64 != atomic_read(&ctx->seq_send)) { - dprintk("%s: seq_send64 %llx, seq_send %x overflow?\n", __func__, - seq_send64, atomic_read(&ctx->seq_send)); - p = ERR_PTR(-EINVAL); - goto out_err; - } p = simple_get_bytes(p, end, &ctx->enctype, sizeof(ctx->enctype)); if (IS_ERR(p)) goto out_err; - ctx->gk5e = gss_krb5_lookup_enctype(ctx->enctype); - if (ctx->gk5e == NULL) { + ctx->krb5e = crypto_krb5_find_enctype(ctx->enctype); + if (!ctx->krb5e) { dprintk("gss_kerberos_mech: unsupported krb5 enctype %u\n", ctx->enctype); p = ERR_PTR(-EINVAL); goto out_err; } - ctx->krb5e = crypto_krb5_find_enctype(ctx->enctype); - if (!ctx->krb5e) { - dprintk("gss_kerberos_mech: crypto/krb5 missing enctype %u\n", - ctx->enctype); - p = ERR_PTR(-EINVAL); - goto out_err; - } - keylen = ctx->gk5e->keylength; + keylen = ctx->krb5e->key_len; p = simple_get_bytes(p, end, ctx->Ksess, keylen); if (IS_ERR(p)) diff --git a/net/sunrpc/auth_gss/gss_krb5_test.c b/net/sunrpc/auth_gss/gss_krb5_test.c deleted file mode 100644 index dde1ee934d0d..000000000000 --- a/net/sunrpc/auth_gss/gss_krb5_test.c +++ /dev/null @@ -1,1868 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Copyright (c) 2022 Oracle and/or its affiliates. - * - * KUnit test of SunRPC's GSS Kerberos mechanism. Subsystem - * name is "rpcsec_gss_krb5". - */ - -#include -#include - -#include -#include - -#include -#include - -#include "gss_krb5_internal.h" - -MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); - -struct gss_krb5_test_param { - const char *desc; - u32 enctype; - u32 nfold; - u32 constant; - const struct xdr_netobj *base_key; - const struct xdr_netobj *Ke; - const struct xdr_netobj *usage; - const struct xdr_netobj *plaintext; - const struct xdr_netobj *confounder; - const struct xdr_netobj *expected_result; - const struct xdr_netobj *expected_hmac; - const struct xdr_netobj *next_iv; -}; - -static inline void gss_krb5_get_desc(const struct gss_krb5_test_param *param, - char *desc) -{ - strscpy(desc, param->desc, KUNIT_PARAM_DESC_SIZE); -} - -static void kdf_case(struct kunit *test) -{ - const struct gss_krb5_test_param *param = test->param_value; - const struct gss_krb5_enctype *gk5e; - struct xdr_netobj derivedkey; - int err; - - /* Arrange */ - gk5e = gss_krb5_lookup_enctype(param->enctype); - if (!gk5e) - kunit_skip(test, "Encryption type is not available"); - - derivedkey.data = kunit_kzalloc(test, param->expected_result->len, - GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, derivedkey.data); - derivedkey.len = param->expected_result->len; - - /* Act */ - err = gk5e->derive_key(gk5e, param->base_key, &derivedkey, - param->usage, GFP_KERNEL); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Assert */ - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_result->data, - derivedkey.data, - derivedkey.len, - "key mismatch"); -} - -static void checksum_case(struct kunit *test) -{ - const struct gss_krb5_test_param *param = test->param_value; - struct xdr_buf buf = { - .head[0].iov_len = param->plaintext->len, - .len = param->plaintext->len, - }; - const struct gss_krb5_enctype *gk5e; - struct xdr_netobj Kc, checksum; - struct crypto_ahash *tfm; - int err; - - /* Arrange */ - gk5e = gss_krb5_lookup_enctype(param->enctype); - if (!gk5e) - kunit_skip(test, "Encryption type is not available"); - - Kc.len = gk5e->Kc_length; - Kc.data = kunit_kzalloc(test, Kc.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, Kc.data); - err = gk5e->derive_key(gk5e, param->base_key, &Kc, - param->usage, GFP_KERNEL); - KUNIT_ASSERT_EQ(test, err, 0); - - tfm = crypto_alloc_ahash(gk5e->cksum_name, 0, CRYPTO_ALG_ASYNC); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, tfm); - err = crypto_ahash_setkey(tfm, Kc.data, Kc.len); - KUNIT_ASSERT_EQ(test, err, 0); - - buf.head[0].iov_base = kunit_kzalloc(test, buf.head[0].iov_len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf.head[0].iov_base); - memcpy(buf.head[0].iov_base, param->plaintext->data, buf.head[0].iov_len); - - checksum.len = gk5e->cksumlength; - checksum.data = kunit_kzalloc(test, checksum.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, checksum.data); - - /* Act */ - err = gss_krb5_checksum(tfm, NULL, 0, &buf, 0, &checksum); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Assert */ - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_result->data, - checksum.data, - checksum.len, - "checksum mismatch"); - - crypto_free_ahash(tfm); -} - -#define DEFINE_HEX_XDR_NETOBJ(name, hex_array...) \ - static const u8 name ## _data[] = { hex_array }; \ - static const struct xdr_netobj name = { \ - .data = (u8 *)name##_data, \ - .len = sizeof(name##_data), \ - } - -#define DEFINE_STR_XDR_NETOBJ(name, string) \ - static const u8 name ## _str[] = string; \ - static const struct xdr_netobj name = { \ - .data = (u8 *)name##_str, \ - .len = sizeof(name##_str) - 1, \ - } - -/* - * RFC 3961 Appendix A.1. n-fold - * - * The n-fold function is defined in section 5.1 of RFC 3961. - * - * This test material is copyright (C) The Internet Society (2005). - */ - -DEFINE_HEX_XDR_NETOBJ(nfold_test1_plaintext, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test1_expected_result, - 0xbe, 0x07, 0x26, 0x31, 0x27, 0x6b, 0x19, 0x55 -); - -DEFINE_HEX_XDR_NETOBJ(nfold_test2_plaintext, - 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test2_expected_result, - 0x78, 0xa0, 0x7b, 0x6c, 0xaf, 0x85, 0xfa -); - -DEFINE_HEX_XDR_NETOBJ(nfold_test3_plaintext, - 0x52, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x43, 0x6f, - 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2c, - 0x20, 0x61, 0x6e, 0x64, 0x20, 0x52, 0x75, 0x6e, - 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x6f, 0x64, - 0x65 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test3_expected_result, - 0xbb, 0x6e, 0xd3, 0x08, 0x70, 0xb7, 0xf0, 0xe0 -); - -DEFINE_HEX_XDR_NETOBJ(nfold_test4_plaintext, - 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test4_expected_result, - 0x59, 0xe4, 0xa8, 0xca, 0x7c, 0x03, 0x85, 0xc3, - 0xc3, 0x7b, 0x3f, 0x6d, 0x20, 0x00, 0x24, 0x7c, - 0xb6, 0xe6, 0xbd, 0x5b, 0x3e -); - -DEFINE_HEX_XDR_NETOBJ(nfold_test5_plaintext, - 0x4d, 0x41, 0x53, 0x53, 0x41, 0x43, 0x48, 0x56, - 0x53, 0x45, 0x54, 0x54, 0x53, 0x20, 0x49, 0x4e, - 0x53, 0x54, 0x49, 0x54, 0x56, 0x54, 0x45, 0x20, - 0x4f, 0x46, 0x20, 0x54, 0x45, 0x43, 0x48, 0x4e, - 0x4f, 0x4c, 0x4f, 0x47, 0x59 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test5_expected_result, - 0xdb, 0x3b, 0x0d, 0x8f, 0x0b, 0x06, 0x1e, 0x60, - 0x32, 0x82, 0xb3, 0x08, 0xa5, 0x08, 0x41, 0x22, - 0x9a, 0xd7, 0x98, 0xfa, 0xb9, 0x54, 0x0c, 0x1b -); - -DEFINE_HEX_XDR_NETOBJ(nfold_test6_plaintext, - 0x51 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test6_expected_result, - 0x51, 0x8a, 0x54, 0xa2, 0x15, 0xa8, 0x45, 0x2a, - 0x51, 0x8a, 0x54, 0xa2, 0x15, 0xa8, 0x45, 0x2a, - 0x51, 0x8a, 0x54, 0xa2, 0x15 -); - -DEFINE_HEX_XDR_NETOBJ(nfold_test7_plaintext, - 0x62, 0x61 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test7_expected_result, - 0xfb, 0x25, 0xd5, 0x31, 0xae, 0x89, 0x74, 0x49, - 0x9f, 0x52, 0xfd, 0x92, 0xea, 0x98, 0x57, 0xc4, - 0xba, 0x24, 0xcf, 0x29, 0x7e -); - -DEFINE_HEX_XDR_NETOBJ(nfold_test_kerberos, - 0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test8_expected_result, - 0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test9_expected_result, - 0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73, - 0x7b, 0x9b, 0x5b, 0x2b, 0x93, 0x13, 0x2b, 0x93 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test10_expected_result, - 0x83, 0x72, 0xc2, 0x36, 0x34, 0x4e, 0x5f, 0x15, - 0x50, 0xcd, 0x07, 0x47, 0xe1, 0x5d, 0x62, 0xca, - 0x7a, 0x5a, 0x3b, 0xce, 0xa4 -); -DEFINE_HEX_XDR_NETOBJ(nfold_test11_expected_result, - 0x6b, 0x65, 0x72, 0x62, 0x65, 0x72, 0x6f, 0x73, - 0x7b, 0x9b, 0x5b, 0x2b, 0x93, 0x13, 0x2b, 0x93, - 0x5c, 0x9b, 0xdc, 0xda, 0xd9, 0x5c, 0x98, 0x99, - 0xc4, 0xca, 0xe4, 0xde, 0xe6, 0xd6, 0xca, 0xe4 -); - -static const struct gss_krb5_test_param rfc3961_nfold_test_params[] = { - { - .desc = "64-fold(\"012345\")", - .nfold = 64, - .plaintext = &nfold_test1_plaintext, - .expected_result = &nfold_test1_expected_result, - }, - { - .desc = "56-fold(\"password\")", - .nfold = 56, - .plaintext = &nfold_test2_plaintext, - .expected_result = &nfold_test2_expected_result, - }, - { - .desc = "64-fold(\"Rough Consensus, and Running Code\")", - .nfold = 64, - .plaintext = &nfold_test3_plaintext, - .expected_result = &nfold_test3_expected_result, - }, - { - .desc = "168-fold(\"password\")", - .nfold = 168, - .plaintext = &nfold_test4_plaintext, - .expected_result = &nfold_test4_expected_result, - }, - { - .desc = "192-fold(\"MASSACHVSETTS INSTITVTE OF TECHNOLOGY\")", - .nfold = 192, - .plaintext = &nfold_test5_plaintext, - .expected_result = &nfold_test5_expected_result, - }, - { - .desc = "168-fold(\"Q\")", - .nfold = 168, - .plaintext = &nfold_test6_plaintext, - .expected_result = &nfold_test6_expected_result, - }, - { - .desc = "168-fold(\"ba\")", - .nfold = 168, - .plaintext = &nfold_test7_plaintext, - .expected_result = &nfold_test7_expected_result, - }, - { - .desc = "64-fold(\"kerberos\")", - .nfold = 64, - .plaintext = &nfold_test_kerberos, - .expected_result = &nfold_test8_expected_result, - }, - { - .desc = "128-fold(\"kerberos\")", - .nfold = 128, - .plaintext = &nfold_test_kerberos, - .expected_result = &nfold_test9_expected_result, - }, - { - .desc = "168-fold(\"kerberos\")", - .nfold = 168, - .plaintext = &nfold_test_kerberos, - .expected_result = &nfold_test10_expected_result, - }, - { - .desc = "256-fold(\"kerberos\")", - .nfold = 256, - .plaintext = &nfold_test_kerberos, - .expected_result = &nfold_test11_expected_result, - }, -}; - -/* Creates the function rfc3961_nfold_gen_params */ -KUNIT_ARRAY_PARAM(rfc3961_nfold, rfc3961_nfold_test_params, gss_krb5_get_desc); - -static void rfc3961_nfold_case(struct kunit *test) -{ - const struct gss_krb5_test_param *param = test->param_value; - u8 *result; - - /* Arrange */ - result = kunit_kzalloc(test, 4096, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, result); - - /* Act */ - krb5_nfold(param->plaintext->len * 8, param->plaintext->data, - param->expected_result->len * 8, result); - - /* Assert */ - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_result->data, - result, - param->expected_result->len, - "result mismatch"); -} - -static struct kunit_case rfc3961_test_cases[] = { - { - .name = "RFC 3961 n-fold", - .run_case = rfc3961_nfold_case, - .generate_params = rfc3961_nfold_gen_params, - }, - {} -}; - -static struct kunit_suite rfc3961_suite = { - .name = "RFC 3961 tests", - .test_cases = rfc3961_test_cases, -}; - -/* - * From RFC 3962 Appendix B: Sample Test Vectors - * - * Some test vectors for CBC with ciphertext stealing, using an - * initial vector of all-zero. - * - * This test material is copyright (C) The Internet Society (2005). - */ - -DEFINE_HEX_XDR_NETOBJ(rfc3962_encryption_key, - 0x63, 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x20, - 0x74, 0x65, 0x72, 0x69, 0x79, 0x61, 0x6b, 0x69 -); - -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test1_plaintext, - 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, - 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test1_expected_result, - 0xc6, 0x35, 0x35, 0x68, 0xf2, 0xbf, 0x8c, 0xb4, - 0xd8, 0xa5, 0x80, 0x36, 0x2d, 0xa7, 0xff, 0x7f, - 0x97 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test1_next_iv, - 0xc6, 0x35, 0x35, 0x68, 0xf2, 0xbf, 0x8c, 0xb4, - 0xd8, 0xa5, 0x80, 0x36, 0x2d, 0xa7, 0xff, 0x7f -); - -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test2_plaintext, - 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, - 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x47, 0x61, 0x75, 0x27, 0x73, 0x20 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test2_expected_result, - 0xfc, 0x00, 0x78, 0x3e, 0x0e, 0xfd, 0xb2, 0xc1, - 0xd4, 0x45, 0xd4, 0xc8, 0xef, 0xf7, 0xed, 0x22, - 0x97, 0x68, 0x72, 0x68, 0xd6, 0xec, 0xcc, 0xc0, - 0xc0, 0x7b, 0x25, 0xe2, 0x5e, 0xcf, 0xe5 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test2_next_iv, - 0xfc, 0x00, 0x78, 0x3e, 0x0e, 0xfd, 0xb2, 0xc1, - 0xd4, 0x45, 0xd4, 0xc8, 0xef, 0xf7, 0xed, 0x22 -); - -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test3_plaintext, - 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, - 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x47, 0x61, 0x75, 0x27, 0x73, 0x20, 0x43 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test3_expected_result, - 0x39, 0x31, 0x25, 0x23, 0xa7, 0x86, 0x62, 0xd5, - 0xbe, 0x7f, 0xcb, 0xcc, 0x98, 0xeb, 0xf5, 0xa8, - 0x97, 0x68, 0x72, 0x68, 0xd6, 0xec, 0xcc, 0xc0, - 0xc0, 0x7b, 0x25, 0xe2, 0x5e, 0xcf, 0xe5, 0x84 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test3_next_iv, - 0x39, 0x31, 0x25, 0x23, 0xa7, 0x86, 0x62, 0xd5, - 0xbe, 0x7f, 0xcb, 0xcc, 0x98, 0xeb, 0xf5, 0xa8 -); - -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test4_plaintext, - 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, - 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x47, 0x61, 0x75, 0x27, 0x73, 0x20, 0x43, - 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x2c, 0x20, - 0x70, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2c -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test4_expected_result, - 0x97, 0x68, 0x72, 0x68, 0xd6, 0xec, 0xcc, 0xc0, - 0xc0, 0x7b, 0x25, 0xe2, 0x5e, 0xcf, 0xe5, 0x84, - 0xb3, 0xff, 0xfd, 0x94, 0x0c, 0x16, 0xa1, 0x8c, - 0x1b, 0x55, 0x49, 0xd2, 0xf8, 0x38, 0x02, 0x9e, - 0x39, 0x31, 0x25, 0x23, 0xa7, 0x86, 0x62, 0xd5, - 0xbe, 0x7f, 0xcb, 0xcc, 0x98, 0xeb, 0xf5 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test4_next_iv, - 0xb3, 0xff, 0xfd, 0x94, 0x0c, 0x16, 0xa1, 0x8c, - 0x1b, 0x55, 0x49, 0xd2, 0xf8, 0x38, 0x02, 0x9e -); - -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test5_plaintext, - 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, - 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x47, 0x61, 0x75, 0x27, 0x73, 0x20, 0x43, - 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x2c, 0x20, - 0x70, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2c, 0x20 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test5_expected_result, - 0x97, 0x68, 0x72, 0x68, 0xd6, 0xec, 0xcc, 0xc0, - 0xc0, 0x7b, 0x25, 0xe2, 0x5e, 0xcf, 0xe5, 0x84, - 0x9d, 0xad, 0x8b, 0xbb, 0x96, 0xc4, 0xcd, 0xc0, - 0x3b, 0xc1, 0x03, 0xe1, 0xa1, 0x94, 0xbb, 0xd8, - 0x39, 0x31, 0x25, 0x23, 0xa7, 0x86, 0x62, 0xd5, - 0xbe, 0x7f, 0xcb, 0xcc, 0x98, 0xeb, 0xf5, 0xa8 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test5_next_iv, - 0x9d, 0xad, 0x8b, 0xbb, 0x96, 0xc4, 0xcd, 0xc0, - 0x3b, 0xc1, 0x03, 0xe1, 0xa1, 0x94, 0xbb, 0xd8 -); - -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test6_plaintext, - 0x49, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, - 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x6c, - 0x20, 0x47, 0x61, 0x75, 0x27, 0x73, 0x20, 0x43, - 0x68, 0x69, 0x63, 0x6b, 0x65, 0x6e, 0x2c, 0x20, - 0x70, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2c, 0x20, - 0x61, 0x6e, 0x64, 0x20, 0x77, 0x6f, 0x6e, 0x74, - 0x6f, 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x70, 0x2e -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test6_expected_result, - 0x97, 0x68, 0x72, 0x68, 0xd6, 0xec, 0xcc, 0xc0, - 0xc0, 0x7b, 0x25, 0xe2, 0x5e, 0xcf, 0xe5, 0x84, - 0x39, 0x31, 0x25, 0x23, 0xa7, 0x86, 0x62, 0xd5, - 0xbe, 0x7f, 0xcb, 0xcc, 0x98, 0xeb, 0xf5, 0xa8, - 0x48, 0x07, 0xef, 0xe8, 0x36, 0xee, 0x89, 0xa5, - 0x26, 0x73, 0x0d, 0xbc, 0x2f, 0x7b, 0xc8, 0x40, - 0x9d, 0xad, 0x8b, 0xbb, 0x96, 0xc4, 0xcd, 0xc0, - 0x3b, 0xc1, 0x03, 0xe1, 0xa1, 0x94, 0xbb, 0xd8 -); -DEFINE_HEX_XDR_NETOBJ(rfc3962_enc_test6_next_iv, - 0x48, 0x07, 0xef, 0xe8, 0x36, 0xee, 0x89, 0xa5, - 0x26, 0x73, 0x0d, 0xbc, 0x2f, 0x7b, 0xc8, 0x40 -); - -static const struct gss_krb5_test_param rfc3962_encrypt_test_params[] = { - { - .desc = "Encrypt with aes128-cts-hmac-sha1-96 case 1", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &rfc3962_enc_test1_plaintext, - .expected_result = &rfc3962_enc_test1_expected_result, - .next_iv = &rfc3962_enc_test1_next_iv, - }, - { - .desc = "Encrypt with aes128-cts-hmac-sha1-96 case 2", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &rfc3962_enc_test2_plaintext, - .expected_result = &rfc3962_enc_test2_expected_result, - .next_iv = &rfc3962_enc_test2_next_iv, - }, - { - .desc = "Encrypt with aes128-cts-hmac-sha1-96 case 3", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &rfc3962_enc_test3_plaintext, - .expected_result = &rfc3962_enc_test3_expected_result, - .next_iv = &rfc3962_enc_test3_next_iv, - }, - { - .desc = "Encrypt with aes128-cts-hmac-sha1-96 case 4", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &rfc3962_enc_test4_plaintext, - .expected_result = &rfc3962_enc_test4_expected_result, - .next_iv = &rfc3962_enc_test4_next_iv, - }, - { - .desc = "Encrypt with aes128-cts-hmac-sha1-96 case 5", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &rfc3962_enc_test5_plaintext, - .expected_result = &rfc3962_enc_test5_expected_result, - .next_iv = &rfc3962_enc_test5_next_iv, - }, - { - .desc = "Encrypt with aes128-cts-hmac-sha1-96 case 6", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &rfc3962_enc_test6_plaintext, - .expected_result = &rfc3962_enc_test6_expected_result, - .next_iv = &rfc3962_enc_test6_next_iv, - }, -}; - -/* Creates the function rfc3962_encrypt_gen_params */ -KUNIT_ARRAY_PARAM(rfc3962_encrypt, rfc3962_encrypt_test_params, - gss_krb5_get_desc); - -/* - * This tests the implementation of the encryption part of the mechanism. - * It does not apply a confounder or test the result of HMAC over the - * plaintext. - */ -static void rfc3962_encrypt_case(struct kunit *test) -{ - const struct gss_krb5_test_param *param = test->param_value; - struct crypto_sync_skcipher *cts_tfm, *cbc_tfm; - const struct gss_krb5_enctype *gk5e; - struct xdr_buf buf; - void *iv, *text; - u32 err; - - /* Arrange */ - gk5e = gss_krb5_lookup_enctype(param->enctype); - if (!gk5e) - kunit_skip(test, "Encryption type is not available"); - - cbc_tfm = crypto_alloc_sync_skcipher(gk5e->aux_cipher, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cbc_tfm); - err = crypto_sync_skcipher_setkey(cbc_tfm, param->Ke->data, param->Ke->len); - KUNIT_ASSERT_EQ(test, err, 0); - - cts_tfm = crypto_alloc_sync_skcipher(gk5e->encrypt_name, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cts_tfm); - err = crypto_sync_skcipher_setkey(cts_tfm, param->Ke->data, param->Ke->len); - KUNIT_ASSERT_EQ(test, err, 0); - - iv = kunit_kzalloc(test, crypto_sync_skcipher_ivsize(cts_tfm), GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, iv); - - text = kunit_kzalloc(test, param->plaintext->len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, text); - - memcpy(text, param->plaintext->data, param->plaintext->len); - memset(&buf, 0, sizeof(buf)); - buf.head[0].iov_base = text; - buf.head[0].iov_len = param->plaintext->len; - buf.len = buf.head[0].iov_len; - - /* Act */ - err = krb5_cbc_cts_encrypt(cts_tfm, cbc_tfm, 0, &buf, NULL, - iv, crypto_sync_skcipher_ivsize(cts_tfm)); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Assert */ - KUNIT_EXPECT_EQ_MSG(test, - param->expected_result->len, buf.len, - "ciphertext length mismatch"); - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_result->data, - text, - param->expected_result->len, - "ciphertext mismatch"); - KUNIT_EXPECT_MEMEQ_MSG(test, - param->next_iv->data, - iv, - param->next_iv->len, - "IV mismatch"); - - crypto_free_sync_skcipher(cts_tfm); - crypto_free_sync_skcipher(cbc_tfm); -} - -static struct kunit_case rfc3962_test_cases[] = { - { - .name = "RFC 3962 encryption", - .run_case = rfc3962_encrypt_case, - .generate_params = rfc3962_encrypt_gen_params, - }, - {} -}; - -static struct kunit_suite rfc3962_suite = { - .name = "RFC 3962 suite", - .test_cases = rfc3962_test_cases, -}; - -/* - * From RFC 6803 Section 10. Test vectors - * - * Sample results for key derivation - * - * Copyright (c) 2012 IETF Trust and the persons identified as the - * document authors. All rights reserved. - */ - -DEFINE_HEX_XDR_NETOBJ(camellia128_cts_cmac_basekey, - 0x57, 0xd0, 0x29, 0x72, 0x98, 0xff, 0xd9, 0xd3, - 0x5d, 0xe5, 0xa4, 0x7f, 0xb4, 0xbd, 0xe2, 0x4b -); -DEFINE_HEX_XDR_NETOBJ(camellia128_cts_cmac_Kc, - 0xd1, 0x55, 0x77, 0x5a, 0x20, 0x9d, 0x05, 0xf0, - 0x2b, 0x38, 0xd4, 0x2a, 0x38, 0x9e, 0x5a, 0x56 -); -DEFINE_HEX_XDR_NETOBJ(camellia128_cts_cmac_Ke, - 0x64, 0xdf, 0x83, 0xf8, 0x5a, 0x53, 0x2f, 0x17, - 0x57, 0x7d, 0x8c, 0x37, 0x03, 0x57, 0x96, 0xab -); -DEFINE_HEX_XDR_NETOBJ(camellia128_cts_cmac_Ki, - 0x3e, 0x4f, 0xbd, 0xf3, 0x0f, 0xb8, 0x25, 0x9c, - 0x42, 0x5c, 0xb6, 0xc9, 0x6f, 0x1f, 0x46, 0x35 -); - -DEFINE_HEX_XDR_NETOBJ(camellia256_cts_cmac_basekey, - 0xb9, 0xd6, 0x82, 0x8b, 0x20, 0x56, 0xb7, 0xbe, - 0x65, 0x6d, 0x88, 0xa1, 0x23, 0xb1, 0xfa, 0xc6, - 0x82, 0x14, 0xac, 0x2b, 0x72, 0x7e, 0xcf, 0x5f, - 0x69, 0xaf, 0xe0, 0xc4, 0xdf, 0x2a, 0x6d, 0x2c -); -DEFINE_HEX_XDR_NETOBJ(camellia256_cts_cmac_Kc, - 0xe4, 0x67, 0xf9, 0xa9, 0x55, 0x2b, 0xc7, 0xd3, - 0x15, 0x5a, 0x62, 0x20, 0xaf, 0x9c, 0x19, 0x22, - 0x0e, 0xee, 0xd4, 0xff, 0x78, 0xb0, 0xd1, 0xe6, - 0xa1, 0x54, 0x49, 0x91, 0x46, 0x1a, 0x9e, 0x50 -); -DEFINE_HEX_XDR_NETOBJ(camellia256_cts_cmac_Ke, - 0x41, 0x2a, 0xef, 0xc3, 0x62, 0xa7, 0x28, 0x5f, - 0xc3, 0x96, 0x6c, 0x6a, 0x51, 0x81, 0xe7, 0x60, - 0x5a, 0xe6, 0x75, 0x23, 0x5b, 0x6d, 0x54, 0x9f, - 0xbf, 0xc9, 0xab, 0x66, 0x30, 0xa4, 0xc6, 0x04 -); -DEFINE_HEX_XDR_NETOBJ(camellia256_cts_cmac_Ki, - 0xfa, 0x62, 0x4f, 0xa0, 0xe5, 0x23, 0x99, 0x3f, - 0xa3, 0x88, 0xae, 0xfd, 0xc6, 0x7e, 0x67, 0xeb, - 0xcd, 0x8c, 0x08, 0xe8, 0xa0, 0x24, 0x6b, 0x1d, - 0x73, 0xb0, 0xd1, 0xdd, 0x9f, 0xc5, 0x82, 0xb0 -); - -DEFINE_HEX_XDR_NETOBJ(usage_checksum, - 0x00, 0x00, 0x00, 0x02, KEY_USAGE_SEED_CHECKSUM -); -DEFINE_HEX_XDR_NETOBJ(usage_encryption, - 0x00, 0x00, 0x00, 0x02, KEY_USAGE_SEED_ENCRYPTION -); -DEFINE_HEX_XDR_NETOBJ(usage_integrity, - 0x00, 0x00, 0x00, 0x02, KEY_USAGE_SEED_INTEGRITY -); - -static const struct gss_krb5_test_param rfc6803_kdf_test_params[] = { - { - .desc = "Derive Kc subkey for camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .base_key = &camellia128_cts_cmac_basekey, - .usage = &usage_checksum, - .expected_result = &camellia128_cts_cmac_Kc, - }, - { - .desc = "Derive Ke subkey for camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .base_key = &camellia128_cts_cmac_basekey, - .usage = &usage_encryption, - .expected_result = &camellia128_cts_cmac_Ke, - }, - { - .desc = "Derive Ki subkey for camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .base_key = &camellia128_cts_cmac_basekey, - .usage = &usage_integrity, - .expected_result = &camellia128_cts_cmac_Ki, - }, - { - .desc = "Derive Kc subkey for camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .base_key = &camellia256_cts_cmac_basekey, - .usage = &usage_checksum, - .expected_result = &camellia256_cts_cmac_Kc, - }, - { - .desc = "Derive Ke subkey for camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .base_key = &camellia256_cts_cmac_basekey, - .usage = &usage_encryption, - .expected_result = &camellia256_cts_cmac_Ke, - }, - { - .desc = "Derive Ki subkey for camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .base_key = &camellia256_cts_cmac_basekey, - .usage = &usage_integrity, - .expected_result = &camellia256_cts_cmac_Ki, - }, -}; - -/* Creates the function rfc6803_kdf_gen_params */ -KUNIT_ARRAY_PARAM(rfc6803_kdf, rfc6803_kdf_test_params, gss_krb5_get_desc); - -/* - * From RFC 6803 Section 10. Test vectors - * - * Sample checksums. - * - * Copyright (c) 2012 IETF Trust and the persons identified as the - * document authors. All rights reserved. - * - * XXX: These tests are likely to fail on EBCDIC or Unicode platforms. - */ -DEFINE_STR_XDR_NETOBJ(rfc6803_checksum_test1_plaintext, - "abcdefghijk"); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test1_basekey, - 0x1d, 0xc4, 0x6a, 0x8d, 0x76, 0x3f, 0x4f, 0x93, - 0x74, 0x2b, 0xcb, 0xa3, 0x38, 0x75, 0x76, 0xc3 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test1_usage, - 0x00, 0x00, 0x00, 0x07, KEY_USAGE_SEED_CHECKSUM -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test1_expected_result, - 0x11, 0x78, 0xe6, 0xc5, 0xc4, 0x7a, 0x8c, 0x1a, - 0xe0, 0xc4, 0xb9, 0xc7, 0xd4, 0xeb, 0x7b, 0x6b -); - -DEFINE_STR_XDR_NETOBJ(rfc6803_checksum_test2_plaintext, - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test2_basekey, - 0x50, 0x27, 0xbc, 0x23, 0x1d, 0x0f, 0x3a, 0x9d, - 0x23, 0x33, 0x3f, 0x1c, 0xa6, 0xfd, 0xbe, 0x7c -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test2_usage, - 0x00, 0x00, 0x00, 0x08, KEY_USAGE_SEED_CHECKSUM -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test2_expected_result, - 0xd1, 0xb3, 0x4f, 0x70, 0x04, 0xa7, 0x31, 0xf2, - 0x3a, 0x0c, 0x00, 0xbf, 0x6c, 0x3f, 0x75, 0x3a -); - -DEFINE_STR_XDR_NETOBJ(rfc6803_checksum_test3_plaintext, - "123456789"); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test3_basekey, - 0xb6, 0x1c, 0x86, 0xcc, 0x4e, 0x5d, 0x27, 0x57, - 0x54, 0x5a, 0xd4, 0x23, 0x39, 0x9f, 0xb7, 0x03, - 0x1e, 0xca, 0xb9, 0x13, 0xcb, 0xb9, 0x00, 0xbd, - 0x7a, 0x3c, 0x6d, 0xd8, 0xbf, 0x92, 0x01, 0x5b -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test3_usage, - 0x00, 0x00, 0x00, 0x09, KEY_USAGE_SEED_CHECKSUM -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test3_expected_result, - 0x87, 0xa1, 0x2c, 0xfd, 0x2b, 0x96, 0x21, 0x48, - 0x10, 0xf0, 0x1c, 0x82, 0x6e, 0x77, 0x44, 0xb1 -); - -DEFINE_STR_XDR_NETOBJ(rfc6803_checksum_test4_plaintext, - "!@#$%^&*()!@#$%^&*()!@#$%^&*()"); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test4_basekey, - 0x32, 0x16, 0x4c, 0x5b, 0x43, 0x4d, 0x1d, 0x15, - 0x38, 0xe4, 0xcf, 0xd9, 0xbe, 0x80, 0x40, 0xfe, - 0x8c, 0x4a, 0xc7, 0xac, 0xc4, 0xb9, 0x3d, 0x33, - 0x14, 0xd2, 0x13, 0x36, 0x68, 0x14, 0x7a, 0x05 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test4_usage, - 0x00, 0x00, 0x00, 0x0a, KEY_USAGE_SEED_CHECKSUM -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_checksum_test4_expected_result, - 0x3f, 0xa0, 0xb4, 0x23, 0x55, 0xe5, 0x2b, 0x18, - 0x91, 0x87, 0x29, 0x4a, 0xa2, 0x52, 0xab, 0x64 -); - -static const struct gss_krb5_test_param rfc6803_checksum_test_params[] = { - { - .desc = "camellia128-cts-cmac checksum test 1", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .base_key = &rfc6803_checksum_test1_basekey, - .usage = &rfc6803_checksum_test1_usage, - .plaintext = &rfc6803_checksum_test1_plaintext, - .expected_result = &rfc6803_checksum_test1_expected_result, - }, - { - .desc = "camellia128-cts-cmac checksum test 2", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .base_key = &rfc6803_checksum_test2_basekey, - .usage = &rfc6803_checksum_test2_usage, - .plaintext = &rfc6803_checksum_test2_plaintext, - .expected_result = &rfc6803_checksum_test2_expected_result, - }, - { - .desc = "camellia256-cts-cmac checksum test 3", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .base_key = &rfc6803_checksum_test3_basekey, - .usage = &rfc6803_checksum_test3_usage, - .plaintext = &rfc6803_checksum_test3_plaintext, - .expected_result = &rfc6803_checksum_test3_expected_result, - }, - { - .desc = "camellia256-cts-cmac checksum test 4", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .base_key = &rfc6803_checksum_test4_basekey, - .usage = &rfc6803_checksum_test4_usage, - .plaintext = &rfc6803_checksum_test4_plaintext, - .expected_result = &rfc6803_checksum_test4_expected_result, - }, -}; - -/* Creates the function rfc6803_checksum_gen_params */ -KUNIT_ARRAY_PARAM(rfc6803_checksum, rfc6803_checksum_test_params, - gss_krb5_get_desc); - -/* - * From RFC 6803 Section 10. Test vectors - * - * Sample encryptions (all using the default cipher state) - * - * Copyright (c) 2012 IETF Trust and the persons identified as the - * document authors. All rights reserved. - * - * Key usage values are from errata 4326 against RFC 6803. - */ - -static const struct xdr_netobj rfc6803_enc_empty_plaintext = { - .len = 0, -}; - -DEFINE_STR_XDR_NETOBJ(rfc6803_enc_1byte_plaintext, "1"); -DEFINE_STR_XDR_NETOBJ(rfc6803_enc_9byte_plaintext, "9 bytesss"); -DEFINE_STR_XDR_NETOBJ(rfc6803_enc_13byte_plaintext, "13 bytes byte"); -DEFINE_STR_XDR_NETOBJ(rfc6803_enc_30byte_plaintext, - "30 bytes bytes bytes bytes byt" -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test1_confounder, - 0xb6, 0x98, 0x22, 0xa1, 0x9a, 0x6b, 0x09, 0xc0, - 0xeb, 0xc8, 0x55, 0x7d, 0x1f, 0x1b, 0x6c, 0x0a -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test1_basekey, - 0x1d, 0xc4, 0x6a, 0x8d, 0x76, 0x3f, 0x4f, 0x93, - 0x74, 0x2b, 0xcb, 0xa3, 0x38, 0x75, 0x76, 0xc3 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test1_expected_result, - 0xc4, 0x66, 0xf1, 0x87, 0x10, 0x69, 0x92, 0x1e, - 0xdb, 0x7c, 0x6f, 0xde, 0x24, 0x4a, 0x52, 0xdb, - 0x0b, 0xa1, 0x0e, 0xdc, 0x19, 0x7b, 0xdb, 0x80, - 0x06, 0x65, 0x8c, 0xa3, 0xcc, 0xce, 0x6e, 0xb8 -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test2_confounder, - 0x6f, 0x2f, 0xc3, 0xc2, 0xa1, 0x66, 0xfd, 0x88, - 0x98, 0x96, 0x7a, 0x83, 0xde, 0x95, 0x96, 0xd9 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test2_basekey, - 0x50, 0x27, 0xbc, 0x23, 0x1d, 0x0f, 0x3a, 0x9d, - 0x23, 0x33, 0x3f, 0x1c, 0xa6, 0xfd, 0xbe, 0x7c -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test2_expected_result, - 0x84, 0x2d, 0x21, 0xfd, 0x95, 0x03, 0x11, 0xc0, - 0xdd, 0x46, 0x4a, 0x3f, 0x4b, 0xe8, 0xd6, 0xda, - 0x88, 0xa5, 0x6d, 0x55, 0x9c, 0x9b, 0x47, 0xd3, - 0xf9, 0xa8, 0x50, 0x67, 0xaf, 0x66, 0x15, 0x59, - 0xb8 -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test3_confounder, - 0xa5, 0xb4, 0xa7, 0x1e, 0x07, 0x7a, 0xee, 0xf9, - 0x3c, 0x87, 0x63, 0xc1, 0x8f, 0xdb, 0x1f, 0x10 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test3_basekey, - 0xa1, 0xbb, 0x61, 0xe8, 0x05, 0xf9, 0xba, 0x6d, - 0xde, 0x8f, 0xdb, 0xdd, 0xc0, 0x5c, 0xde, 0xa0 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test3_expected_result, - 0x61, 0x9f, 0xf0, 0x72, 0xe3, 0x62, 0x86, 0xff, - 0x0a, 0x28, 0xde, 0xb3, 0xa3, 0x52, 0xec, 0x0d, - 0x0e, 0xdf, 0x5c, 0x51, 0x60, 0xd6, 0x63, 0xc9, - 0x01, 0x75, 0x8c, 0xcf, 0x9d, 0x1e, 0xd3, 0x3d, - 0x71, 0xdb, 0x8f, 0x23, 0xaa, 0xbf, 0x83, 0x48, - 0xa0 -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test4_confounder, - 0x19, 0xfe, 0xe4, 0x0d, 0x81, 0x0c, 0x52, 0x4b, - 0x5b, 0x22, 0xf0, 0x18, 0x74, 0xc6, 0x93, 0xda -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test4_basekey, - 0x2c, 0xa2, 0x7a, 0x5f, 0xaf, 0x55, 0x32, 0x24, - 0x45, 0x06, 0x43, 0x4e, 0x1c, 0xef, 0x66, 0x76 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test4_expected_result, - 0xb8, 0xec, 0xa3, 0x16, 0x7a, 0xe6, 0x31, 0x55, - 0x12, 0xe5, 0x9f, 0x98, 0xa7, 0xc5, 0x00, 0x20, - 0x5e, 0x5f, 0x63, 0xff, 0x3b, 0xb3, 0x89, 0xaf, - 0x1c, 0x41, 0xa2, 0x1d, 0x64, 0x0d, 0x86, 0x15, - 0xc9, 0xed, 0x3f, 0xbe, 0xb0, 0x5a, 0xb6, 0xac, - 0xb6, 0x76, 0x89, 0xb5, 0xea -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test5_confounder, - 0xca, 0x7a, 0x7a, 0xb4, 0xbe, 0x19, 0x2d, 0xab, - 0xd6, 0x03, 0x50, 0x6d, 0xb1, 0x9c, 0x39, 0xe2 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test5_basekey, - 0x78, 0x24, 0xf8, 0xc1, 0x6f, 0x83, 0xff, 0x35, - 0x4c, 0x6b, 0xf7, 0x51, 0x5b, 0x97, 0x3f, 0x43 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test5_expected_result, - 0xa2, 0x6a, 0x39, 0x05, 0xa4, 0xff, 0xd5, 0x81, - 0x6b, 0x7b, 0x1e, 0x27, 0x38, 0x0d, 0x08, 0x09, - 0x0c, 0x8e, 0xc1, 0xf3, 0x04, 0x49, 0x6e, 0x1a, - 0xbd, 0xcd, 0x2b, 0xdc, 0xd1, 0xdf, 0xfc, 0x66, - 0x09, 0x89, 0xe1, 0x17, 0xa7, 0x13, 0xdd, 0xbb, - 0x57, 0xa4, 0x14, 0x6c, 0x15, 0x87, 0xcb, 0xa4, - 0x35, 0x66, 0x65, 0x59, 0x1d, 0x22, 0x40, 0x28, - 0x2f, 0x58, 0x42, 0xb1, 0x05, 0xa5 -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test6_confounder, - 0x3c, 0xbb, 0xd2, 0xb4, 0x59, 0x17, 0x94, 0x10, - 0x67, 0xf9, 0x65, 0x99, 0xbb, 0x98, 0x92, 0x6c -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test6_basekey, - 0xb6, 0x1c, 0x86, 0xcc, 0x4e, 0x5d, 0x27, 0x57, - 0x54, 0x5a, 0xd4, 0x23, 0x39, 0x9f, 0xb7, 0x03, - 0x1e, 0xca, 0xb9, 0x13, 0xcb, 0xb9, 0x00, 0xbd, - 0x7a, 0x3c, 0x6d, 0xd8, 0xbf, 0x92, 0x01, 0x5b -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test6_expected_result, - 0x03, 0x88, 0x6d, 0x03, 0x31, 0x0b, 0x47, 0xa6, - 0xd8, 0xf0, 0x6d, 0x7b, 0x94, 0xd1, 0xdd, 0x83, - 0x7e, 0xcc, 0xe3, 0x15, 0xef, 0x65, 0x2a, 0xff, - 0x62, 0x08, 0x59, 0xd9, 0x4a, 0x25, 0x92, 0x66 -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test7_confounder, - 0xde, 0xf4, 0x87, 0xfc, 0xeb, 0xe6, 0xde, 0x63, - 0x46, 0xd4, 0xda, 0x45, 0x21, 0xbb, 0xa2, 0xd2 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test7_basekey, - 0x1b, 0x97, 0xfe, 0x0a, 0x19, 0x0e, 0x20, 0x21, - 0xeb, 0x30, 0x75, 0x3e, 0x1b, 0x6e, 0x1e, 0x77, - 0xb0, 0x75, 0x4b, 0x1d, 0x68, 0x46, 0x10, 0x35, - 0x58, 0x64, 0x10, 0x49, 0x63, 0x46, 0x38, 0x33 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test7_expected_result, - 0x2c, 0x9c, 0x15, 0x70, 0x13, 0x3c, 0x99, 0xbf, - 0x6a, 0x34, 0xbc, 0x1b, 0x02, 0x12, 0x00, 0x2f, - 0xd1, 0x94, 0x33, 0x87, 0x49, 0xdb, 0x41, 0x35, - 0x49, 0x7a, 0x34, 0x7c, 0xfc, 0xd9, 0xd1, 0x8a, - 0x12 -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test8_confounder, - 0xad, 0x4f, 0xf9, 0x04, 0xd3, 0x4e, 0x55, 0x53, - 0x84, 0xb1, 0x41, 0x00, 0xfc, 0x46, 0x5f, 0x88 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test8_basekey, - 0x32, 0x16, 0x4c, 0x5b, 0x43, 0x4d, 0x1d, 0x15, - 0x38, 0xe4, 0xcf, 0xd9, 0xbe, 0x80, 0x40, 0xfe, - 0x8c, 0x4a, 0xc7, 0xac, 0xc4, 0xb9, 0x3d, 0x33, - 0x14, 0xd2, 0x13, 0x36, 0x68, 0x14, 0x7a, 0x05 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test8_expected_result, - 0x9c, 0x6d, 0xe7, 0x5f, 0x81, 0x2d, 0xe7, 0xed, - 0x0d, 0x28, 0xb2, 0x96, 0x35, 0x57, 0xa1, 0x15, - 0x64, 0x09, 0x98, 0x27, 0x5b, 0x0a, 0xf5, 0x15, - 0x27, 0x09, 0x91, 0x3f, 0xf5, 0x2a, 0x2a, 0x9c, - 0x8e, 0x63, 0xb8, 0x72, 0xf9, 0x2e, 0x64, 0xc8, - 0x39 -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test9_confounder, - 0xcf, 0x9b, 0xca, 0x6d, 0xf1, 0x14, 0x4e, 0x0c, - 0x0a, 0xf9, 0xb8, 0xf3, 0x4c, 0x90, 0xd5, 0x14 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test9_basekey, - 0xb0, 0x38, 0xb1, 0x32, 0xcd, 0x8e, 0x06, 0x61, - 0x22, 0x67, 0xfa, 0xb7, 0x17, 0x00, 0x66, 0xd8, - 0x8a, 0xec, 0xcb, 0xa0, 0xb7, 0x44, 0xbf, 0xc6, - 0x0d, 0xc8, 0x9b, 0xca, 0x18, 0x2d, 0x07, 0x15 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test9_expected_result, - 0xee, 0xec, 0x85, 0xa9, 0x81, 0x3c, 0xdc, 0x53, - 0x67, 0x72, 0xab, 0x9b, 0x42, 0xde, 0xfc, 0x57, - 0x06, 0xf7, 0x26, 0xe9, 0x75, 0xdd, 0xe0, 0x5a, - 0x87, 0xeb, 0x54, 0x06, 0xea, 0x32, 0x4c, 0xa1, - 0x85, 0xc9, 0x98, 0x6b, 0x42, 0xaa, 0xbe, 0x79, - 0x4b, 0x84, 0x82, 0x1b, 0xee -); - -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test10_confounder, - 0x64, 0x4d, 0xef, 0x38, 0xda, 0x35, 0x00, 0x72, - 0x75, 0x87, 0x8d, 0x21, 0x68, 0x55, 0xe2, 0x28 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test10_basekey, - 0xcc, 0xfc, 0xd3, 0x49, 0xbf, 0x4c, 0x66, 0x77, - 0xe8, 0x6e, 0x4b, 0x02, 0xb8, 0xea, 0xb9, 0x24, - 0xa5, 0x46, 0xac, 0x73, 0x1c, 0xf9, 0xbf, 0x69, - 0x89, 0xb9, 0x96, 0xe7, 0xd6, 0xbf, 0xbb, 0xa7 -); -DEFINE_HEX_XDR_NETOBJ(rfc6803_enc_test10_expected_result, - 0x0e, 0x44, 0x68, 0x09, 0x85, 0x85, 0x5f, 0x2d, - 0x1f, 0x18, 0x12, 0x52, 0x9c, 0xa8, 0x3b, 0xfd, - 0x8e, 0x34, 0x9d, 0xe6, 0xfd, 0x9a, 0xda, 0x0b, - 0xaa, 0xa0, 0x48, 0xd6, 0x8e, 0x26, 0x5f, 0xeb, - 0xf3, 0x4a, 0xd1, 0x25, 0x5a, 0x34, 0x49, 0x99, - 0xad, 0x37, 0x14, 0x68, 0x87, 0xa6, 0xc6, 0x84, - 0x57, 0x31, 0xac, 0x7f, 0x46, 0x37, 0x6a, 0x05, - 0x04, 0xcd, 0x06, 0x57, 0x14, 0x74 -); - -static const struct gss_krb5_test_param rfc6803_encrypt_test_params[] = { - { - .desc = "Encrypt empty plaintext with camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .constant = 0, - .base_key = &rfc6803_enc_test1_basekey, - .plaintext = &rfc6803_enc_empty_plaintext, - .confounder = &rfc6803_enc_test1_confounder, - .expected_result = &rfc6803_enc_test1_expected_result, - }, - { - .desc = "Encrypt 1 byte with camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .constant = 1, - .base_key = &rfc6803_enc_test2_basekey, - .plaintext = &rfc6803_enc_1byte_plaintext, - .confounder = &rfc6803_enc_test2_confounder, - .expected_result = &rfc6803_enc_test2_expected_result, - }, - { - .desc = "Encrypt 9 bytes with camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .constant = 2, - .base_key = &rfc6803_enc_test3_basekey, - .plaintext = &rfc6803_enc_9byte_plaintext, - .confounder = &rfc6803_enc_test3_confounder, - .expected_result = &rfc6803_enc_test3_expected_result, - }, - { - .desc = "Encrypt 13 bytes with camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .constant = 3, - .base_key = &rfc6803_enc_test4_basekey, - .plaintext = &rfc6803_enc_13byte_plaintext, - .confounder = &rfc6803_enc_test4_confounder, - .expected_result = &rfc6803_enc_test4_expected_result, - }, - { - .desc = "Encrypt 30 bytes with camellia128-cts-cmac", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .constant = 4, - .base_key = &rfc6803_enc_test5_basekey, - .plaintext = &rfc6803_enc_30byte_plaintext, - .confounder = &rfc6803_enc_test5_confounder, - .expected_result = &rfc6803_enc_test5_expected_result, - }, - { - .desc = "Encrypt empty plaintext with camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .constant = 0, - .base_key = &rfc6803_enc_test6_basekey, - .plaintext = &rfc6803_enc_empty_plaintext, - .confounder = &rfc6803_enc_test6_confounder, - .expected_result = &rfc6803_enc_test6_expected_result, - }, - { - .desc = "Encrypt 1 byte with camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .constant = 1, - .base_key = &rfc6803_enc_test7_basekey, - .plaintext = &rfc6803_enc_1byte_plaintext, - .confounder = &rfc6803_enc_test7_confounder, - .expected_result = &rfc6803_enc_test7_expected_result, - }, - { - .desc = "Encrypt 9 bytes with camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .constant = 2, - .base_key = &rfc6803_enc_test8_basekey, - .plaintext = &rfc6803_enc_9byte_plaintext, - .confounder = &rfc6803_enc_test8_confounder, - .expected_result = &rfc6803_enc_test8_expected_result, - }, - { - .desc = "Encrypt 13 bytes with camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .constant = 3, - .base_key = &rfc6803_enc_test9_basekey, - .plaintext = &rfc6803_enc_13byte_plaintext, - .confounder = &rfc6803_enc_test9_confounder, - .expected_result = &rfc6803_enc_test9_expected_result, - }, - { - .desc = "Encrypt 30 bytes with camellia256-cts-cmac", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .constant = 4, - .base_key = &rfc6803_enc_test10_basekey, - .plaintext = &rfc6803_enc_30byte_plaintext, - .confounder = &rfc6803_enc_test10_confounder, - .expected_result = &rfc6803_enc_test10_expected_result, - }, -}; - -/* Creates the function rfc6803_encrypt_gen_params */ -KUNIT_ARRAY_PARAM(rfc6803_encrypt, rfc6803_encrypt_test_params, - gss_krb5_get_desc); - -static void rfc6803_encrypt_case(struct kunit *test) -{ - const struct gss_krb5_test_param *param = test->param_value; - struct crypto_sync_skcipher *cts_tfm, *cbc_tfm; - const struct gss_krb5_enctype *gk5e; - struct xdr_netobj Ke, Ki, checksum; - u8 usage_data[GSS_KRB5_K5CLENGTH]; - struct xdr_netobj usage = { - .data = usage_data, - .len = sizeof(usage_data), - }; - struct crypto_ahash *ahash_tfm; - unsigned int blocksize; - struct xdr_buf buf; - void *text; - size_t len; - u32 err; - - /* Arrange */ - gk5e = gss_krb5_lookup_enctype(param->enctype); - if (!gk5e) - kunit_skip(test, "Encryption type is not available"); - - memset(usage_data, 0, sizeof(usage_data)); - usage.data[3] = param->constant; - - Ke.len = gk5e->Ke_length; - Ke.data = kunit_kzalloc(test, Ke.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, Ke.data); - usage.data[4] = KEY_USAGE_SEED_ENCRYPTION; - err = gk5e->derive_key(gk5e, param->base_key, &Ke, &usage, GFP_KERNEL); - KUNIT_ASSERT_EQ(test, err, 0); - - cbc_tfm = crypto_alloc_sync_skcipher(gk5e->aux_cipher, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cbc_tfm); - err = crypto_sync_skcipher_setkey(cbc_tfm, Ke.data, Ke.len); - KUNIT_ASSERT_EQ(test, err, 0); - - cts_tfm = crypto_alloc_sync_skcipher(gk5e->encrypt_name, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cts_tfm); - err = crypto_sync_skcipher_setkey(cts_tfm, Ke.data, Ke.len); - KUNIT_ASSERT_EQ(test, err, 0); - blocksize = crypto_sync_skcipher_blocksize(cts_tfm); - - len = param->confounder->len + param->plaintext->len + blocksize; - text = kunit_kzalloc(test, len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, text); - memcpy(text, param->confounder->data, param->confounder->len); - memcpy(text + param->confounder->len, param->plaintext->data, - param->plaintext->len); - - memset(&buf, 0, sizeof(buf)); - buf.head[0].iov_base = text; - buf.head[0].iov_len = param->confounder->len + param->plaintext->len; - buf.len = buf.head[0].iov_len; - - checksum.len = gk5e->cksumlength; - checksum.data = kunit_kzalloc(test, checksum.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, checksum.data); - - Ki.len = gk5e->Ki_length; - Ki.data = kunit_kzalloc(test, Ki.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, Ki.data); - usage.data[4] = KEY_USAGE_SEED_INTEGRITY; - err = gk5e->derive_key(gk5e, param->base_key, &Ki, - &usage, GFP_KERNEL); - KUNIT_ASSERT_EQ(test, err, 0); - ahash_tfm = crypto_alloc_ahash(gk5e->cksum_name, 0, CRYPTO_ALG_ASYNC); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ahash_tfm); - err = crypto_ahash_setkey(ahash_tfm, Ki.data, Ki.len); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Act */ - err = gss_krb5_checksum(ahash_tfm, NULL, 0, &buf, 0, &checksum); - KUNIT_ASSERT_EQ(test, err, 0); - - err = krb5_cbc_cts_encrypt(cts_tfm, cbc_tfm, 0, &buf, NULL, NULL, 0); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Assert */ - KUNIT_EXPECT_EQ_MSG(test, param->expected_result->len, - buf.len + checksum.len, - "ciphertext length mismatch"); - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_result->data, - buf.head[0].iov_base, - buf.len, - "encrypted result mismatch"); - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_result->data + - (param->expected_result->len - checksum.len), - checksum.data, - checksum.len, - "HMAC mismatch"); - - crypto_free_ahash(ahash_tfm); - crypto_free_sync_skcipher(cts_tfm); - crypto_free_sync_skcipher(cbc_tfm); -} - -static struct kunit_case rfc6803_test_cases[] = { - { - .name = "RFC 6803 key derivation", - .run_case = kdf_case, - .generate_params = rfc6803_kdf_gen_params, - }, - { - .name = "RFC 6803 checksum", - .run_case = checksum_case, - .generate_params = rfc6803_checksum_gen_params, - }, - { - .name = "RFC 6803 encryption", - .run_case = rfc6803_encrypt_case, - .generate_params = rfc6803_encrypt_gen_params, - }, - {} -}; - -static struct kunit_suite rfc6803_suite = { - .name = "RFC 6803 suite", - .test_cases = rfc6803_test_cases, -}; - -/* - * From RFC 8009 Appendix A. Test Vectors - * - * Sample results for SHA-2 enctype key derivation - * - * This test material is copyright (c) 2016 IETF Trust and the - * persons identified as the document authors. All rights reserved. - */ - -DEFINE_HEX_XDR_NETOBJ(aes128_cts_hmac_sha256_128_basekey, - 0x37, 0x05, 0xd9, 0x60, 0x80, 0xc1, 0x77, 0x28, - 0xa0, 0xe8, 0x00, 0xea, 0xb6, 0xe0, 0xd2, 0x3c -); -DEFINE_HEX_XDR_NETOBJ(aes128_cts_hmac_sha256_128_Kc, - 0xb3, 0x1a, 0x01, 0x8a, 0x48, 0xf5, 0x47, 0x76, - 0xf4, 0x03, 0xe9, 0xa3, 0x96, 0x32, 0x5d, 0xc3 -); -DEFINE_HEX_XDR_NETOBJ(aes128_cts_hmac_sha256_128_Ke, - 0x9b, 0x19, 0x7d, 0xd1, 0xe8, 0xc5, 0x60, 0x9d, - 0x6e, 0x67, 0xc3, 0xe3, 0x7c, 0x62, 0xc7, 0x2e -); -DEFINE_HEX_XDR_NETOBJ(aes128_cts_hmac_sha256_128_Ki, - 0x9f, 0xda, 0x0e, 0x56, 0xab, 0x2d, 0x85, 0xe1, - 0x56, 0x9a, 0x68, 0x86, 0x96, 0xc2, 0x6a, 0x6c -); - -DEFINE_HEX_XDR_NETOBJ(aes256_cts_hmac_sha384_192_basekey, - 0x6d, 0x40, 0x4d, 0x37, 0xfa, 0xf7, 0x9f, 0x9d, - 0xf0, 0xd3, 0x35, 0x68, 0xd3, 0x20, 0x66, 0x98, - 0x00, 0xeb, 0x48, 0x36, 0x47, 0x2e, 0xa8, 0xa0, - 0x26, 0xd1, 0x6b, 0x71, 0x82, 0x46, 0x0c, 0x52 -); -DEFINE_HEX_XDR_NETOBJ(aes256_cts_hmac_sha384_192_Kc, - 0xef, 0x57, 0x18, 0xbe, 0x86, 0xcc, 0x84, 0x96, - 0x3d, 0x8b, 0xbb, 0x50, 0x31, 0xe9, 0xf5, 0xc4, - 0xba, 0x41, 0xf2, 0x8f, 0xaf, 0x69, 0xe7, 0x3d -); -DEFINE_HEX_XDR_NETOBJ(aes256_cts_hmac_sha384_192_Ke, - 0x56, 0xab, 0x22, 0xbe, 0xe6, 0x3d, 0x82, 0xd7, - 0xbc, 0x52, 0x27, 0xf6, 0x77, 0x3f, 0x8e, 0xa7, - 0xa5, 0xeb, 0x1c, 0x82, 0x51, 0x60, 0xc3, 0x83, - 0x12, 0x98, 0x0c, 0x44, 0x2e, 0x5c, 0x7e, 0x49 -); -DEFINE_HEX_XDR_NETOBJ(aes256_cts_hmac_sha384_192_Ki, - 0x69, 0xb1, 0x65, 0x14, 0xe3, 0xcd, 0x8e, 0x56, - 0xb8, 0x20, 0x10, 0xd5, 0xc7, 0x30, 0x12, 0xb6, - 0x22, 0xc4, 0xd0, 0x0f, 0xfc, 0x23, 0xed, 0x1f -); - -static const struct gss_krb5_test_param rfc8009_kdf_test_params[] = { - { - .desc = "Derive Kc subkey for aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .usage = &usage_checksum, - .expected_result = &aes128_cts_hmac_sha256_128_Kc, - }, - { - .desc = "Derive Ke subkey for aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .usage = &usage_encryption, - .expected_result = &aes128_cts_hmac_sha256_128_Ke, - }, - { - .desc = "Derive Ki subkey for aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .usage = &usage_integrity, - .expected_result = &aes128_cts_hmac_sha256_128_Ki, - }, - { - .desc = "Derive Kc subkey for aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .usage = &usage_checksum, - .expected_result = &aes256_cts_hmac_sha384_192_Kc, - }, - { - .desc = "Derive Ke subkey for aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .usage = &usage_encryption, - .expected_result = &aes256_cts_hmac_sha384_192_Ke, - }, - { - .desc = "Derive Ki subkey for aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .usage = &usage_integrity, - .expected_result = &aes256_cts_hmac_sha384_192_Ki, - }, -}; - -/* Creates the function rfc8009_kdf_gen_params */ -KUNIT_ARRAY_PARAM(rfc8009_kdf, rfc8009_kdf_test_params, gss_krb5_get_desc); - -/* - * From RFC 8009 Appendix A. Test Vectors - * - * These sample checksums use the above sample key derivation results, - * including use of the same base-key and key usage values. - * - * This test material is copyright (c) 2016 IETF Trust and the - * persons identified as the document authors. All rights reserved. - */ - -DEFINE_HEX_XDR_NETOBJ(rfc8009_checksum_plaintext, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_checksum_test1_expected_result, - 0xd7, 0x83, 0x67, 0x18, 0x66, 0x43, 0xd6, 0x7b, - 0x41, 0x1c, 0xba, 0x91, 0x39, 0xfc, 0x1d, 0xee -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_checksum_test2_expected_result, - 0x45, 0xee, 0x79, 0x15, 0x67, 0xee, 0xfc, 0xa3, - 0x7f, 0x4a, 0xc1, 0xe0, 0x22, 0x2d, 0xe8, 0x0d, - 0x43, 0xc3, 0xbf, 0xa0, 0x66, 0x99, 0x67, 0x2a -); - -static const struct gss_krb5_test_param rfc8009_checksum_test_params[] = { - { - .desc = "Checksum with aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .usage = &usage_checksum, - .plaintext = &rfc8009_checksum_plaintext, - .expected_result = &rfc8009_checksum_test1_expected_result, - }, - { - .desc = "Checksum with aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .usage = &usage_checksum, - .plaintext = &rfc8009_checksum_plaintext, - .expected_result = &rfc8009_checksum_test2_expected_result, - }, -}; - -/* Creates the function rfc8009_checksum_gen_params */ -KUNIT_ARRAY_PARAM(rfc8009_checksum, rfc8009_checksum_test_params, - gss_krb5_get_desc); - -/* - * From RFC 8009 Appendix A. Test Vectors - * - * Sample encryptions (all using the default cipher state): - * -------------------------------------------------------- - * - * These sample encryptions use the above sample key derivation results, - * including use of the same base-key and key usage values. - * - * This test material is copyright (c) 2016 IETF Trust and the - * persons identified as the document authors. All rights reserved. - */ - -static const struct xdr_netobj rfc8009_enc_empty_plaintext = { - .len = 0, -}; -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_short_plaintext, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_block_plaintext, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_long_plaintext, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14 -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test1_confounder, - 0x7e, 0x58, 0x95, 0xea, 0xf2, 0x67, 0x24, 0x35, - 0xba, 0xd8, 0x17, 0xf5, 0x45, 0xa3, 0x71, 0x48 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test1_expected_result, - 0xef, 0x85, 0xfb, 0x89, 0x0b, 0xb8, 0x47, 0x2f, - 0x4d, 0xab, 0x20, 0x39, 0x4d, 0xca, 0x78, 0x1d -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test1_expected_hmac, - 0xad, 0x87, 0x7e, 0xda, 0x39, 0xd5, 0x0c, 0x87, - 0x0c, 0x0d, 0x5a, 0x0a, 0x8e, 0x48, 0xc7, 0x18 -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test2_confounder, - 0x7b, 0xca, 0x28, 0x5e, 0x2f, 0xd4, 0x13, 0x0f, - 0xb5, 0x5b, 0x1a, 0x5c, 0x83, 0xbc, 0x5b, 0x24 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test2_expected_result, - 0x84, 0xd7, 0xf3, 0x07, 0x54, 0xed, 0x98, 0x7b, - 0xab, 0x0b, 0xf3, 0x50, 0x6b, 0xeb, 0x09, 0xcf, - 0xb5, 0x54, 0x02, 0xce, 0xf7, 0xe6 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test2_expected_hmac, - 0x87, 0x7c, 0xe9, 0x9e, 0x24, 0x7e, 0x52, 0xd1, - 0x6e, 0xd4, 0x42, 0x1d, 0xfd, 0xf8, 0x97, 0x6c -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test3_confounder, - 0x56, 0xab, 0x21, 0x71, 0x3f, 0xf6, 0x2c, 0x0a, - 0x14, 0x57, 0x20, 0x0f, 0x6f, 0xa9, 0x94, 0x8f -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test3_expected_result, - 0x35, 0x17, 0xd6, 0x40, 0xf5, 0x0d, 0xdc, 0x8a, - 0xd3, 0x62, 0x87, 0x22, 0xb3, 0x56, 0x9d, 0x2a, - 0xe0, 0x74, 0x93, 0xfa, 0x82, 0x63, 0x25, 0x40, - 0x80, 0xea, 0x65, 0xc1, 0x00, 0x8e, 0x8f, 0xc2 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test3_expected_hmac, - 0x95, 0xfb, 0x48, 0x52, 0xe7, 0xd8, 0x3e, 0x1e, - 0x7c, 0x48, 0xc3, 0x7e, 0xeb, 0xe6, 0xb0, 0xd3 -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test4_confounder, - 0xa7, 0xa4, 0xe2, 0x9a, 0x47, 0x28, 0xce, 0x10, - 0x66, 0x4f, 0xb6, 0x4e, 0x49, 0xad, 0x3f, 0xac -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test4_expected_result, - 0x72, 0x0f, 0x73, 0xb1, 0x8d, 0x98, 0x59, 0xcd, - 0x6c, 0xcb, 0x43, 0x46, 0x11, 0x5c, 0xd3, 0x36, - 0xc7, 0x0f, 0x58, 0xed, 0xc0, 0xc4, 0x43, 0x7c, - 0x55, 0x73, 0x54, 0x4c, 0x31, 0xc8, 0x13, 0xbc, - 0xe1, 0xe6, 0xd0, 0x72, 0xc1 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test4_expected_hmac, - 0x86, 0xb3, 0x9a, 0x41, 0x3c, 0x2f, 0x92, 0xca, - 0x9b, 0x83, 0x34, 0xa2, 0x87, 0xff, 0xcb, 0xfc -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test5_confounder, - 0xf7, 0x64, 0xe9, 0xfa, 0x15, 0xc2, 0x76, 0x47, - 0x8b, 0x2c, 0x7d, 0x0c, 0x4e, 0x5f, 0x58, 0xe4 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test5_expected_result, - 0x41, 0xf5, 0x3f, 0xa5, 0xbf, 0xe7, 0x02, 0x6d, - 0x91, 0xfa, 0xf9, 0xbe, 0x95, 0x91, 0x95, 0xa0 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test5_expected_hmac, - 0x58, 0x70, 0x72, 0x73, 0xa9, 0x6a, 0x40, 0xf0, - 0xa0, 0x19, 0x60, 0x62, 0x1a, 0xc6, 0x12, 0x74, - 0x8b, 0x9b, 0xbf, 0xbe, 0x7e, 0xb4, 0xce, 0x3c -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test6_confounder, - 0xb8, 0x0d, 0x32, 0x51, 0xc1, 0xf6, 0x47, 0x14, - 0x94, 0x25, 0x6f, 0xfe, 0x71, 0x2d, 0x0b, 0x9a -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test6_expected_result, - 0x4e, 0xd7, 0xb3, 0x7c, 0x2b, 0xca, 0xc8, 0xf7, - 0x4f, 0x23, 0xc1, 0xcf, 0x07, 0xe6, 0x2b, 0xc7, - 0xb7, 0x5f, 0xb3, 0xf6, 0x37, 0xb9 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test6_expected_hmac, - 0xf5, 0x59, 0xc7, 0xf6, 0x64, 0xf6, 0x9e, 0xab, - 0x7b, 0x60, 0x92, 0x23, 0x75, 0x26, 0xea, 0x0d, - 0x1f, 0x61, 0xcb, 0x20, 0xd6, 0x9d, 0x10, 0xf2 -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test7_confounder, - 0x53, 0xbf, 0x8a, 0x0d, 0x10, 0x52, 0x65, 0xd4, - 0xe2, 0x76, 0x42, 0x86, 0x24, 0xce, 0x5e, 0x63 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test7_expected_result, - 0xbc, 0x47, 0xff, 0xec, 0x79, 0x98, 0xeb, 0x91, - 0xe8, 0x11, 0x5c, 0xf8, 0xd1, 0x9d, 0xac, 0x4b, - 0xbb, 0xe2, 0xe1, 0x63, 0xe8, 0x7d, 0xd3, 0x7f, - 0x49, 0xbe, 0xca, 0x92, 0x02, 0x77, 0x64, 0xf6 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test7_expected_hmac, - 0x8c, 0xf5, 0x1f, 0x14, 0xd7, 0x98, 0xc2, 0x27, - 0x3f, 0x35, 0xdf, 0x57, 0x4d, 0x1f, 0x93, 0x2e, - 0x40, 0xc4, 0xff, 0x25, 0x5b, 0x36, 0xa2, 0x66 -); - -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test8_confounder, - 0x76, 0x3e, 0x65, 0x36, 0x7e, 0x86, 0x4f, 0x02, - 0xf5, 0x51, 0x53, 0xc7, 0xe3, 0xb5, 0x8a, 0xf1 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test8_expected_result, - 0x40, 0x01, 0x3e, 0x2d, 0xf5, 0x8e, 0x87, 0x51, - 0x95, 0x7d, 0x28, 0x78, 0xbc, 0xd2, 0xd6, 0xfe, - 0x10, 0x1c, 0xcf, 0xd5, 0x56, 0xcb, 0x1e, 0xae, - 0x79, 0xdb, 0x3c, 0x3e, 0xe8, 0x64, 0x29, 0xf2, - 0xb2, 0xa6, 0x02, 0xac, 0x86 -); -DEFINE_HEX_XDR_NETOBJ(rfc8009_enc_test8_expected_hmac, - 0xfe, 0xf6, 0xec, 0xb6, 0x47, 0xd6, 0x29, 0x5f, - 0xae, 0x07, 0x7a, 0x1f, 0xeb, 0x51, 0x75, 0x08, - 0xd2, 0xc1, 0x6b, 0x41, 0x92, 0xe0, 0x1f, 0x62 -); - -static const struct gss_krb5_test_param rfc8009_encrypt_test_params[] = { - { - .desc = "Encrypt empty plaintext with aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .plaintext = &rfc8009_enc_empty_plaintext, - .confounder = &rfc8009_enc_test1_confounder, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .expected_result = &rfc8009_enc_test1_expected_result, - .expected_hmac = &rfc8009_enc_test1_expected_hmac, - }, - { - .desc = "Encrypt short plaintext with aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .plaintext = &rfc8009_enc_short_plaintext, - .confounder = &rfc8009_enc_test2_confounder, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .expected_result = &rfc8009_enc_test2_expected_result, - .expected_hmac = &rfc8009_enc_test2_expected_hmac, - }, - { - .desc = "Encrypt block plaintext with aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .plaintext = &rfc8009_enc_block_plaintext, - .confounder = &rfc8009_enc_test3_confounder, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .expected_result = &rfc8009_enc_test3_expected_result, - .expected_hmac = &rfc8009_enc_test3_expected_hmac, - }, - { - .desc = "Encrypt long plaintext with aes128-cts-hmac-sha256-128", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .plaintext = &rfc8009_enc_long_plaintext, - .confounder = &rfc8009_enc_test4_confounder, - .base_key = &aes128_cts_hmac_sha256_128_basekey, - .expected_result = &rfc8009_enc_test4_expected_result, - .expected_hmac = &rfc8009_enc_test4_expected_hmac, - }, - { - .desc = "Encrypt empty plaintext with aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .plaintext = &rfc8009_enc_empty_plaintext, - .confounder = &rfc8009_enc_test5_confounder, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .expected_result = &rfc8009_enc_test5_expected_result, - .expected_hmac = &rfc8009_enc_test5_expected_hmac, - }, - { - .desc = "Encrypt short plaintext with aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .plaintext = &rfc8009_enc_short_plaintext, - .confounder = &rfc8009_enc_test6_confounder, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .expected_result = &rfc8009_enc_test6_expected_result, - .expected_hmac = &rfc8009_enc_test6_expected_hmac, - }, - { - .desc = "Encrypt block plaintext with aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .plaintext = &rfc8009_enc_block_plaintext, - .confounder = &rfc8009_enc_test7_confounder, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .expected_result = &rfc8009_enc_test7_expected_result, - .expected_hmac = &rfc8009_enc_test7_expected_hmac, - }, - { - .desc = "Encrypt long plaintext with aes256-cts-hmac-sha384-192", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .plaintext = &rfc8009_enc_long_plaintext, - .confounder = &rfc8009_enc_test8_confounder, - .base_key = &aes256_cts_hmac_sha384_192_basekey, - .expected_result = &rfc8009_enc_test8_expected_result, - .expected_hmac = &rfc8009_enc_test8_expected_hmac, - }, -}; - -/* Creates the function rfc8009_encrypt_gen_params */ -KUNIT_ARRAY_PARAM(rfc8009_encrypt, rfc8009_encrypt_test_params, - gss_krb5_get_desc); - -static void rfc8009_encrypt_case(struct kunit *test) -{ - const struct gss_krb5_test_param *param = test->param_value; - struct crypto_sync_skcipher *cts_tfm, *cbc_tfm; - const struct gss_krb5_enctype *gk5e; - struct xdr_netobj Ke, Ki, checksum; - u8 usage_data[GSS_KRB5_K5CLENGTH]; - struct xdr_netobj usage = { - .data = usage_data, - .len = sizeof(usage_data), - }; - struct crypto_ahash *ahash_tfm; - struct xdr_buf buf; - void *text; - size_t len; - u32 err; - - /* Arrange */ - gk5e = gss_krb5_lookup_enctype(param->enctype); - if (!gk5e) - kunit_skip(test, "Encryption type is not available"); - - *(__be32 *)usage.data = cpu_to_be32(2); - - Ke.len = gk5e->Ke_length; - Ke.data = kunit_kzalloc(test, Ke.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, Ke.data); - usage.data[4] = KEY_USAGE_SEED_ENCRYPTION; - err = gk5e->derive_key(gk5e, param->base_key, &Ke, - &usage, GFP_KERNEL); - KUNIT_ASSERT_EQ(test, err, 0); - - cbc_tfm = crypto_alloc_sync_skcipher(gk5e->aux_cipher, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cbc_tfm); - err = crypto_sync_skcipher_setkey(cbc_tfm, Ke.data, Ke.len); - KUNIT_ASSERT_EQ(test, err, 0); - - cts_tfm = crypto_alloc_sync_skcipher(gk5e->encrypt_name, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cts_tfm); - err = crypto_sync_skcipher_setkey(cts_tfm, Ke.data, Ke.len); - KUNIT_ASSERT_EQ(test, err, 0); - - len = param->confounder->len + param->plaintext->len; - text = kunit_kzalloc(test, len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, text); - memcpy(text, param->confounder->data, param->confounder->len); - memcpy(text + param->confounder->len, param->plaintext->data, - param->plaintext->len); - - memset(&buf, 0, sizeof(buf)); - buf.head[0].iov_base = text; - buf.head[0].iov_len = param->confounder->len + param->plaintext->len; - buf.len = buf.head[0].iov_len; - - checksum.len = gk5e->cksumlength; - checksum.data = kunit_kzalloc(test, checksum.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, checksum.data); - - Ki.len = gk5e->Ki_length; - Ki.data = kunit_kzalloc(test, Ki.len, GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, Ki.data); - usage.data[4] = KEY_USAGE_SEED_INTEGRITY; - err = gk5e->derive_key(gk5e, param->base_key, &Ki, - &usage, GFP_KERNEL); - KUNIT_ASSERT_EQ(test, err, 0); - - ahash_tfm = crypto_alloc_ahash(gk5e->cksum_name, 0, CRYPTO_ALG_ASYNC); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ahash_tfm); - err = crypto_ahash_setkey(ahash_tfm, Ki.data, Ki.len); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Act */ - err = krb5_cbc_cts_encrypt(cts_tfm, cbc_tfm, 0, &buf, NULL, NULL, 0); - KUNIT_ASSERT_EQ(test, err, 0); - err = krb5_etm_checksum(cts_tfm, ahash_tfm, &buf, 0, &checksum); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Assert */ - KUNIT_EXPECT_EQ_MSG(test, - param->expected_result->len, buf.len, - "ciphertext length mismatch"); - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_result->data, - buf.head[0].iov_base, - param->expected_result->len, - "ciphertext mismatch"); - KUNIT_EXPECT_MEMEQ_MSG(test, - param->expected_hmac->data, - checksum.data, - checksum.len, - "HMAC mismatch"); - - crypto_free_ahash(ahash_tfm); - crypto_free_sync_skcipher(cts_tfm); - crypto_free_sync_skcipher(cbc_tfm); -} - -static struct kunit_case rfc8009_test_cases[] = { - { - .name = "RFC 8009 key derivation", - .run_case = kdf_case, - .generate_params = rfc8009_kdf_gen_params, - }, - { - .name = "RFC 8009 checksum", - .run_case = checksum_case, - .generate_params = rfc8009_checksum_gen_params, - }, - { - .name = "RFC 8009 encryption", - .run_case = rfc8009_encrypt_case, - .generate_params = rfc8009_encrypt_gen_params, - }, - {} -}; - -static struct kunit_suite rfc8009_suite = { - .name = "RFC 8009 suite", - .test_cases = rfc8009_test_cases, -}; - -/* - * Encryption self-tests - */ - -DEFINE_STR_XDR_NETOBJ(encrypt_selftest_plaintext, - "This is the plaintext for the encryption self-test."); - -static const struct gss_krb5_test_param encrypt_selftest_params[] = { - { - .desc = "aes128-cts-hmac-sha1-96 encryption self-test", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &encrypt_selftest_plaintext, - }, - { - .desc = "aes256-cts-hmac-sha1-96 encryption self-test", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA1_96, - .Ke = &rfc3962_encryption_key, - .plaintext = &encrypt_selftest_plaintext, - }, - { - .desc = "camellia128-cts-cmac encryption self-test", - .enctype = ENCTYPE_CAMELLIA128_CTS_CMAC, - .Ke = &camellia128_cts_cmac_Ke, - .plaintext = &encrypt_selftest_plaintext, - }, - { - .desc = "camellia256-cts-cmac encryption self-test", - .enctype = ENCTYPE_CAMELLIA256_CTS_CMAC, - .Ke = &camellia256_cts_cmac_Ke, - .plaintext = &encrypt_selftest_plaintext, - }, - { - .desc = "aes128-cts-hmac-sha256-128 encryption self-test", - .enctype = ENCTYPE_AES128_CTS_HMAC_SHA256_128, - .Ke = &aes128_cts_hmac_sha256_128_Ke, - .plaintext = &encrypt_selftest_plaintext, - }, - { - .desc = "aes256-cts-hmac-sha384-192 encryption self-test", - .enctype = ENCTYPE_AES256_CTS_HMAC_SHA384_192, - .Ke = &aes256_cts_hmac_sha384_192_Ke, - .plaintext = &encrypt_selftest_plaintext, - }, -}; - -/* Creates the function encrypt_selftest_gen_params */ -KUNIT_ARRAY_PARAM(encrypt_selftest, encrypt_selftest_params, - gss_krb5_get_desc); - -/* - * Encrypt and decrypt plaintext, and ensure the input plaintext - * matches the output plaintext. A confounder is not added in this - * case. - */ -static void encrypt_selftest_case(struct kunit *test) -{ - const struct gss_krb5_test_param *param = test->param_value; - struct crypto_sync_skcipher *cts_tfm, *cbc_tfm; - const struct gss_krb5_enctype *gk5e; - struct xdr_buf buf; - void *text; - int err; - - /* Arrange */ - gk5e = gss_krb5_lookup_enctype(param->enctype); - if (!gk5e) - kunit_skip(test, "Encryption type is not available"); - - cbc_tfm = crypto_alloc_sync_skcipher(gk5e->aux_cipher, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cbc_tfm); - err = crypto_sync_skcipher_setkey(cbc_tfm, param->Ke->data, param->Ke->len); - KUNIT_ASSERT_EQ(test, err, 0); - - cts_tfm = crypto_alloc_sync_skcipher(gk5e->encrypt_name, 0, 0); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cts_tfm); - err = crypto_sync_skcipher_setkey(cts_tfm, param->Ke->data, param->Ke->len); - KUNIT_ASSERT_EQ(test, err, 0); - - text = kunit_kzalloc(test, roundup(param->plaintext->len, - crypto_sync_skcipher_blocksize(cbc_tfm)), - GFP_KERNEL); - KUNIT_ASSERT_NOT_ERR_OR_NULL(test, text); - - memcpy(text, param->plaintext->data, param->plaintext->len); - memset(&buf, 0, sizeof(buf)); - buf.head[0].iov_base = text; - buf.head[0].iov_len = param->plaintext->len; - buf.len = buf.head[0].iov_len; - - /* Act */ - err = krb5_cbc_cts_encrypt(cts_tfm, cbc_tfm, 0, &buf, NULL, NULL, 0); - KUNIT_ASSERT_EQ(test, err, 0); - err = krb5_cbc_cts_decrypt(cts_tfm, cbc_tfm, 0, &buf); - KUNIT_ASSERT_EQ(test, err, 0); - - /* Assert */ - KUNIT_EXPECT_EQ_MSG(test, - param->plaintext->len, buf.len, - "length mismatch"); - KUNIT_EXPECT_MEMEQ_MSG(test, - param->plaintext->data, - buf.head[0].iov_base, - buf.len, - "plaintext mismatch"); - - crypto_free_sync_skcipher(cts_tfm); - crypto_free_sync_skcipher(cbc_tfm); -} - -static struct kunit_case encryption_test_cases[] = { - { - .name = "Encryption self-tests", - .run_case = encrypt_selftest_case, - .generate_params = encrypt_selftest_gen_params, - }, - {} -}; - -static struct kunit_suite encryption_test_suite = { - .name = "Encryption test suite", - .test_cases = encryption_test_cases, -}; - -kunit_test_suites(&rfc3961_suite, - &rfc3962_suite, - &rfc6803_suite, - &rfc8009_suite, - &encryption_test_suite); - -MODULE_DESCRIPTION("Test RPCSEC GSS Kerberos 5 functions"); -MODULE_LICENSE("GPL"); diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index 516833b4c114..6bd588dfbfc0 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -2348,73 +2348,6 @@ int xdr_encode_array2(const struct xdr_buf *buf, unsigned int base, } EXPORT_SYMBOL_GPL(xdr_encode_array2); -int xdr_process_buf(const struct xdr_buf *buf, unsigned int offset, - unsigned int len, - int (*actor)(struct scatterlist *, void *), void *data) -{ - int i, ret = 0; - unsigned int page_len, thislen, page_offset; - struct scatterlist sg[1]; - - sg_init_table(sg, 1); - - if (offset >= buf->head[0].iov_len) { - offset -= buf->head[0].iov_len; - } else { - thislen = buf->head[0].iov_len - offset; - if (thislen > len) - thislen = len; - sg_set_buf(sg, buf->head[0].iov_base + offset, thislen); - ret = actor(sg, data); - if (ret) - goto out; - offset = 0; - len -= thislen; - } - if (len == 0) - goto out; - - if (offset >= buf->page_len) { - offset -= buf->page_len; - } else { - page_len = buf->page_len - offset; - if (page_len > len) - page_len = len; - len -= page_len; - page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1); - i = (offset + buf->page_base) >> PAGE_SHIFT; - thislen = PAGE_SIZE - page_offset; - do { - if (thislen > page_len) - thislen = page_len; - sg_set_page(sg, buf->pages[i], thislen, page_offset); - ret = actor(sg, data); - if (ret) - goto out; - page_len -= thislen; - i++; - page_offset = 0; - thislen = PAGE_SIZE; - } while (page_len != 0); - offset = 0; - } - if (len == 0) - goto out; - if (offset < buf->tail[0].iov_len) { - thislen = buf->tail[0].iov_len - offset; - if (thislen > len) - thislen = len; - sg_set_buf(sg, buf->tail[0].iov_base + offset, thislen); - ret = actor(sg, data); - len -= thislen; - } - if (len != 0) - ret = -EINVAL; -out: - return ret; -} -EXPORT_SYMBOL_GPL(xdr_process_buf); - /** * xdr_stream_decode_string_dup - Decode and duplicate variable length string * @xdr: pointer to xdr_stream From c331c8eaa20211d99181f1a1cf8d5a83b119dcb6 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:51:00 -0400 Subject: [PATCH 044/106] SUNRPC: Remove per-enctype Kconfig options The RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1, RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA, and RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2 Kconfig options originally gated both algorithm availability and the advertised enctype list. Now that per-message crypto operations are routed through crypto/krb5, these options control only which enctype numbers appear in the gssd upcall string; the underlying algorithms are always present. Remove the per-enctype Kconfig options and replace the ifdef-gated enctype table with a candidate list looked up in the crypto/krb5 enctype table at module init time. Each enctype is included in the advertised list only if crypto_krb5_find_enctype() finds it in the library's enctype table. When a new enctype is added to crypto/krb5, adding its constant to the candidate array is sufficient to begin advertising it. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/Kconfig | 38 ----------------------------- net/sunrpc/auth_gss/gss_krb5_mech.c | 30 +++++++++++------------ 2 files changed, 14 insertions(+), 54 deletions(-) diff --git a/net/sunrpc/Kconfig b/net/sunrpc/Kconfig index 1c2e1fe9d365..305c55cdbd45 100644 --- a/net/sunrpc/Kconfig +++ b/net/sunrpc/Kconfig @@ -35,44 +35,6 @@ config RPCSEC_GSS_KRB5 If unsure, say Y. -config RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1 - bool "Enable Kerberos enctypes based on AES and SHA-1" - depends on RPCSEC_GSS_KRB5 - depends on CRYPTO_CBC && CRYPTO_CTS - depends on CRYPTO_HMAC && CRYPTO_SHA1 - depends on CRYPTO_AES - default y - help - Choose Y to enable the use of Kerberos 5 encryption types - that utilize Advanced Encryption Standard (AES) ciphers and - SHA-1 digests. These include aes128-cts-hmac-sha1-96 and - aes256-cts-hmac-sha1-96. - -config RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA - bool "Enable Kerberos encryption types based on Camellia and CMAC" - depends on RPCSEC_GSS_KRB5 - depends on CRYPTO_CBC && CRYPTO_CTS && CRYPTO_CAMELLIA - depends on CRYPTO_CMAC - default n - help - Choose Y to enable the use of Kerberos 5 encryption types - that utilize Camellia ciphers (RFC 3713) and CMAC digests - (NIST Special Publication 800-38B). These include - camellia128-cts-cmac and camellia256-cts-cmac. - -config RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2 - bool "Enable Kerberos enctypes based on AES and SHA-2" - depends on RPCSEC_GSS_KRB5 - depends on CRYPTO_CBC && CRYPTO_CTS - depends on CRYPTO_HMAC && CRYPTO_SHA256 && CRYPTO_SHA512 - depends on CRYPTO_AES - default n - help - Choose Y to enable the use of Kerberos 5 encryption types - that utilize Advanced Encryption Standard (AES) ciphers and - SHA-2 digests. These include aes128-cts-hmac-sha256-128 and - aes256-cts-hmac-sha384-192. - config SUNRPC_DEBUG bool "RPC: Enable dprintk debugging" depends on SUNRPC && SYSCTL diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 5a52fd84f946..996e452b9b3c 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -28,27 +28,23 @@ static struct gss_api_mech gss_kerberos_mech; /* - * The list of advertised enctypes is specified in order of most - * preferred to least. + * Candidate enctypes in order of most preferred to least. + * Each is probed against crypto/krb5 at module init; only + * enctypes that crypto/krb5 supports are advertised. */ +static const u32 gss_krb5_enctypes[] = { + ENCTYPE_AES256_CTS_HMAC_SHA384_192, + ENCTYPE_AES128_CTS_HMAC_SHA256_128, + ENCTYPE_CAMELLIA256_CTS_CMAC, + ENCTYPE_CAMELLIA128_CTS_CMAC, + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + ENCTYPE_AES128_CTS_HMAC_SHA1_96, +}; + static char gss_krb5_enctype_priority_list[64]; static void gss_krb5_prepare_enctype_priority_list(void) { - static const u32 gss_krb5_enctypes[] = { -#if defined(CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA2) - ENCTYPE_AES256_CTS_HMAC_SHA384_192, - ENCTYPE_AES128_CTS_HMAC_SHA256_128, -#endif -#if defined(CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_CAMELLIA) - ENCTYPE_CAMELLIA256_CTS_CMAC, - ENCTYPE_CAMELLIA128_CTS_CMAC, -#endif -#if defined(CONFIG_RPCSEC_GSS_KRB5_ENCTYPES_AES_SHA1) - ENCTYPE_AES256_CTS_HMAC_SHA1_96, - ENCTYPE_AES128_CTS_HMAC_SHA1_96, -#endif - }; size_t total, i; char buf[16]; char *sep; @@ -57,6 +53,8 @@ static void gss_krb5_prepare_enctype_priority_list(void) sep = ""; gss_krb5_enctype_priority_list[0] = '\0'; for (total = 0, i = 0; i < ARRAY_SIZE(gss_krb5_enctypes); i++) { + if (!crypto_krb5_find_enctype(gss_krb5_enctypes[i])) + continue; n = sprintf(buf, "%s%u", sep, gss_krb5_enctypes[i]); if (n < 0) break; From bb9f4c49500306793eb83e4435107332db6925f0 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:51:01 -0400 Subject: [PATCH 045/106] SUNRPC: Remove redundant crypto Kconfig dependencies With all per-message crypto operations now routed through crypto/krb5, rpcsec_gss_krb5 no longer calls individual crypto algorithms directly. The CRYPTO_KRB5 symbol already selects CRYPTO_SKCIPHER and CRYPTO_HASH (the latter transitively via CRYPTO_HMAC). Drop the top-level select CRYPTO_SKCIPHER and select CRYPTO_HASH from RPCSEC_GSS_KRB5, as these are redundant with CRYPTO_KRB5's own dependencies. Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- net/sunrpc/Kconfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/net/sunrpc/Kconfig b/net/sunrpc/Kconfig index 305c55cdbd45..e7808e5714dc 100644 --- a/net/sunrpc/Kconfig +++ b/net/sunrpc/Kconfig @@ -22,8 +22,6 @@ config RPCSEC_GSS_KRB5 default y select SUNRPC_GSS select CRYPTO_KRB5 - select CRYPTO_SKCIPHER - select CRYPTO_HASH help Choose Y here to enable Secure RPC using the Kerberos version 5 GSS-API mechanism (RFC 1964). From 2804a16b9e51f803a32ac2e9a014f5851a7cc3f4 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Mon, 27 Apr 2026 09:51:02 -0400 Subject: [PATCH 046/106] SUNRPC: Remove dead rpcsec_gss_krb5 definitions The migration to crypto/krb5 eliminated the per-enctype function dispatch and direct crypto API usage, leaving behind a number of orphaned definitions. Remove the following from gss_krb5.h: - GSS_KRB5_K5CLENGTH, used only by removed key derivation - KG_TOK_MIC_MSG and KG_TOK_WRAP_MSG (Kerberos v1 token types; v1 support was dropped earlier) - KG2_TOK_INITIAL and KG2_TOK_RESPONSE (context establishment token types; no remaining users) - KG2_RESP_FLAG_ERROR and KG2_RESP_FLAG_DELEG_OK - enum sgn_alg and enum seal_alg (v1 algorithm constants) - All CKSUMTYPE_* definitions, now duplicated by KRB5_CKSUMTYPE_* in - The KG_ error constants from gssapi_err_krb5.h, which have no remaining users - The ENCTYPE_* constant block, replaced by KRB5_ENCTYPE_* from - KG_USAGE_SEAL/SIGN/SEQ (3DES usage constants) - KEY_USAGE_SEED_CHECKSUM/ENCRYPTION/INTEGRITY, duplicated by - #include , no longer needed Remove the cksum[] field from struct krb5_ctx in gss_krb5_internal.h; no code reads or writes it after the key derivation removal. Switch gss_krb5_enctypes[] in gss_krb5_mech.c to the canonical KRB5_ENCTYPE_* names from . Remove stale #include directives: - from gss_krb5_wrap.c - and from gss_krb5_seal.c Assisted-by: Claude:claude-opus-4-6 Reviewed-by: Jeff Layton Acked-by: Anna Schumaker Signed-off-by: Chuck Lever --- include/linux/sunrpc/gss_krb5.h | 105 ------------------------ net/sunrpc/auth_gss/gss_krb5_internal.h | 1 - net/sunrpc/auth_gss/gss_krb5_mech.c | 12 +-- net/sunrpc/auth_gss/gss_krb5_seal.c | 2 - net/sunrpc/auth_gss/gss_krb5_wrap.c | 1 - 5 files changed, 6 insertions(+), 115 deletions(-) diff --git a/include/linux/sunrpc/gss_krb5.h b/include/linux/sunrpc/gss_krb5.h index 43950b5237c8..1cd452ed1db5 100644 --- a/include/linux/sunrpc/gss_krb5.h +++ b/include/linux/sunrpc/gss_krb5.h @@ -37,13 +37,9 @@ #ifndef _LINUX_SUNRPC_GSS_KRB5_H #define _LINUX_SUNRPC_GSS_KRB5_H -#include #include #include -/* Length of constant used in key derivation */ -#define GSS_KRB5_K5CLENGTH (5) - /* Maximum key length (in bytes) for the supported crypto algorithms */ #define GSS_KRB5_MAX_KEYLEN (32) @@ -56,11 +52,6 @@ /* The length of the Kerberos GSS token header */ #define GSS_KRB5_TOK_HDR_LEN (16) -#define KG_TOK_MIC_MSG 0x0101 -#define KG_TOK_WRAP_MSG 0x0201 - -#define KG2_TOK_INITIAL 0x0101 -#define KG2_TOK_RESPONSE 0x0202 #define KG2_TOK_MIC 0x0404 #define KG2_TOK_WRAP 0x0504 @@ -68,102 +59,6 @@ #define KG2_TOKEN_FLAG_SEALED 0x02 #define KG2_TOKEN_FLAG_ACCEPTORSUBKEY 0x04 -#define KG2_RESP_FLAG_ERROR 0x0001 -#define KG2_RESP_FLAG_DELEG_OK 0x0002 - -enum sgn_alg { - SGN_ALG_DES_MAC_MD5 = 0x0000, - SGN_ALG_MD2_5 = 0x0001, - SGN_ALG_DES_MAC = 0x0002, - SGN_ALG_3 = 0x0003, /* not published */ - SGN_ALG_HMAC_SHA1_DES3_KD = 0x0004 -}; -enum seal_alg { - SEAL_ALG_NONE = 0xffff, - SEAL_ALG_DES = 0x0000, - SEAL_ALG_1 = 0x0001, /* not published */ - SEAL_ALG_DES3KD = 0x0002 -}; - -/* - * These values are assigned by IANA and published via the - * subregistry at the link below: - * - * https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-2 - */ -#define CKSUMTYPE_CRC32 0x0001 -#define CKSUMTYPE_RSA_MD4 0x0002 -#define CKSUMTYPE_RSA_MD4_DES 0x0003 -#define CKSUMTYPE_DESCBC 0x0004 -#define CKSUMTYPE_RSA_MD5 0x0007 -#define CKSUMTYPE_RSA_MD5_DES 0x0008 -#define CKSUMTYPE_NIST_SHA 0x0009 -#define CKSUMTYPE_HMAC_SHA1_DES3 0x000c -#define CKSUMTYPE_HMAC_SHA1_96_AES128 0x000f -#define CKSUMTYPE_HMAC_SHA1_96_AES256 0x0010 -#define CKSUMTYPE_CMAC_CAMELLIA128 0x0011 -#define CKSUMTYPE_CMAC_CAMELLIA256 0x0012 -#define CKSUMTYPE_HMAC_SHA256_128_AES128 0x0013 -#define CKSUMTYPE_HMAC_SHA384_192_AES256 0x0014 -#define CKSUMTYPE_HMAC_MD5_ARCFOUR -138 /* Microsoft md5 hmac cksumtype */ - -/* from gssapi_err_krb5.h */ -#define KG_CCACHE_NOMATCH (39756032L) -#define KG_KEYTAB_NOMATCH (39756033L) -#define KG_TGT_MISSING (39756034L) -#define KG_NO_SUBKEY (39756035L) -#define KG_CONTEXT_ESTABLISHED (39756036L) -#define KG_BAD_SIGN_TYPE (39756037L) -#define KG_BAD_LENGTH (39756038L) -#define KG_CTX_INCOMPLETE (39756039L) -#define KG_CONTEXT (39756040L) -#define KG_CRED (39756041L) -#define KG_ENC_DESC (39756042L) -#define KG_BAD_SEQ (39756043L) -#define KG_EMPTY_CCACHE (39756044L) -#define KG_NO_CTYPES (39756045L) - -/* per Kerberos v5 protocol spec crypto types from the wire. - * these get mapped to linux kernel crypto routines. - * - * These values are assigned by IANA and published via the - * subregistry at the link below: - * - * https://www.iana.org/assignments/kerberos-parameters/kerberos-parameters.xhtml#kerberos-parameters-1 - */ -#define ENCTYPE_NULL 0x0000 -#define ENCTYPE_DES_CBC_CRC 0x0001 /* DES cbc mode with CRC-32 */ -#define ENCTYPE_DES_CBC_MD4 0x0002 /* DES cbc mode with RSA-MD4 */ -#define ENCTYPE_DES_CBC_MD5 0x0003 /* DES cbc mode with RSA-MD5 */ -#define ENCTYPE_DES_CBC_RAW 0x0004 /* DES cbc mode raw */ -/* XXX deprecated? */ -#define ENCTYPE_DES3_CBC_SHA 0x0005 /* DES-3 cbc mode with NIST-SHA */ -#define ENCTYPE_DES3_CBC_RAW 0x0006 /* DES-3 cbc mode raw */ -#define ENCTYPE_DES_HMAC_SHA1 0x0008 -#define ENCTYPE_DES3_CBC_SHA1 0x0010 -#define ENCTYPE_AES128_CTS_HMAC_SHA1_96 0x0011 -#define ENCTYPE_AES256_CTS_HMAC_SHA1_96 0x0012 -#define ENCTYPE_AES128_CTS_HMAC_SHA256_128 0x0013 -#define ENCTYPE_AES256_CTS_HMAC_SHA384_192 0x0014 -#define ENCTYPE_ARCFOUR_HMAC 0x0017 -#define ENCTYPE_ARCFOUR_HMAC_EXP 0x0018 -#define ENCTYPE_CAMELLIA128_CTS_CMAC 0x0019 -#define ENCTYPE_CAMELLIA256_CTS_CMAC 0x001A -#define ENCTYPE_UNKNOWN 0x01ff - -/* - * Constants used for key derivation - */ -/* for 3DES */ -#define KG_USAGE_SEAL (22) -#define KG_USAGE_SIGN (23) -#define KG_USAGE_SEQ (24) - -/* from rfc3961 */ -#define KEY_USAGE_SEED_CHECKSUM (0x99) -#define KEY_USAGE_SEED_ENCRYPTION (0xAA) -#define KEY_USAGE_SEED_INTEGRITY (0x55) - /* from rfc4121 */ #define KG_USAGE_ACCEPTOR_SEAL (22) #define KG_USAGE_ACCEPTOR_SIGN (23) diff --git a/net/sunrpc/auth_gss/gss_krb5_internal.h b/net/sunrpc/auth_gss/gss_krb5_internal.h index 208f9df9ea96..3b392e96f25d 100644 --- a/net/sunrpc/auth_gss/gss_krb5_internal.h +++ b/net/sunrpc/auth_gss/gss_krb5_internal.h @@ -26,7 +26,6 @@ struct krb5_ctx { struct crypto_shash *initiator_sign_shash; struct crypto_shash *acceptor_sign_shash; u8 Ksess[GSS_KRB5_MAX_KEYLEN]; /* session key */ - u8 cksum[GSS_KRB5_MAX_KEYLEN]; atomic64_t seq_send64; time64_t endtime; struct xdr_netobj mech_used; diff --git a/net/sunrpc/auth_gss/gss_krb5_mech.c b/net/sunrpc/auth_gss/gss_krb5_mech.c index 996e452b9b3c..c41b5f3e1789 100644 --- a/net/sunrpc/auth_gss/gss_krb5_mech.c +++ b/net/sunrpc/auth_gss/gss_krb5_mech.c @@ -33,12 +33,12 @@ static struct gss_api_mech gss_kerberos_mech; * enctypes that crypto/krb5 supports are advertised. */ static const u32 gss_krb5_enctypes[] = { - ENCTYPE_AES256_CTS_HMAC_SHA384_192, - ENCTYPE_AES128_CTS_HMAC_SHA256_128, - ENCTYPE_CAMELLIA256_CTS_CMAC, - ENCTYPE_CAMELLIA128_CTS_CMAC, - ENCTYPE_AES256_CTS_HMAC_SHA1_96, - ENCTYPE_AES128_CTS_HMAC_SHA1_96, + KRB5_ENCTYPE_AES256_CTS_HMAC_SHA384_192, + KRB5_ENCTYPE_AES128_CTS_HMAC_SHA256_128, + KRB5_ENCTYPE_CAMELLIA256_CTS_CMAC, + KRB5_ENCTYPE_CAMELLIA128_CTS_CMAC, + KRB5_ENCTYPE_AES256_CTS_HMAC_SHA1_96, + KRB5_ENCTYPE_AES128_CTS_HMAC_SHA1_96, }; static char gss_krb5_enctype_priority_list[64]; diff --git a/net/sunrpc/auth_gss/gss_krb5_seal.c b/net/sunrpc/auth_gss/gss_krb5_seal.c index 66c179337029..cfe066e89f23 100644 --- a/net/sunrpc/auth_gss/gss_krb5_seal.c +++ b/net/sunrpc/auth_gss/gss_krb5_seal.c @@ -61,8 +61,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/net/sunrpc/auth_gss/gss_krb5_wrap.c b/net/sunrpc/auth_gss/gss_krb5_wrap.c index 93aa7500d032..ac4b32df42b9 100644 --- a/net/sunrpc/auth_gss/gss_krb5_wrap.c +++ b/net/sunrpc/auth_gss/gss_krb5_wrap.c @@ -28,7 +28,6 @@ * SUCH DAMAGES. */ -#include #include #include #include From 9545262f7e58d67de413d5a47ea2a3f2e59ba9f6 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 6 May 2026 11:26:50 -0400 Subject: [PATCH 047/106] svcrdma: Release write chunk resources without re-queuing Each RDMA Send completion triggers a cascade of work items on the svcrdma_wq unbound workqueue: ib_cq_poll_work (on ib_comp_wq, per-CPU) -> svc_rdma_send_ctxt_put -> queue_work [work item 1] -> svc_rdma_write_info_free -> queue_work [work item 2] Every transition through queue_work contends on the unbound pool's spinlock. Profiling an 8KB NFSv3 read/write workload over RDMA shows about 4% of total CPU cycles spent on this lock, with the cascading re-queue of write_info release contributing roughly 1%. The initial queue_work in svc_rdma_send_ctxt_put is needed to move release work off the CQ completion context (which runs on a per-CPU bound workqueue). However, once executing on svcrdma_wq, there is no need to re-queue for each write_info structure. svc_rdma_reply_chunk_release already calls svc_rdma_cc_release inline from the same svcrdma_wq context, and svc_rdma_recv_ctxt_put does the same from nfsd thread context. Release write chunk resources inline in svc_rdma_write_info_free, removing the intermediate svc_rdma_write_info_free_async work item and the wi_work field from struct svc_rdma_write_info. Reviewed-by: Mike Snitzer Tested-by: Jonathan Flynn Signed-off-by: Chuck Lever --- include/linux/sunrpc/svc_rdma.h | 1 - net/sunrpc/xprtrdma/svc_rdma_rw.c | 13 ++----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/include/linux/sunrpc/svc_rdma.h b/include/linux/sunrpc/svc_rdma.h index df6e08aaad57..14eb9d52742e 100644 --- a/include/linux/sunrpc/svc_rdma.h +++ b/include/linux/sunrpc/svc_rdma.h @@ -230,7 +230,6 @@ struct svc_rdma_write_info { unsigned int wi_next_off; struct svc_rdma_chunk_ctxt wi_cc; - struct work_struct wi_work; }; struct svc_rdma_send_ctxt { diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index 402e2ceca4ff..cca8ec973de4 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -236,19 +236,10 @@ svc_rdma_write_info_alloc(struct svcxprt_rdma *rdma, return info; } -static void svc_rdma_write_info_free_async(struct work_struct *work) -{ - struct svc_rdma_write_info *info; - - info = container_of(work, struct svc_rdma_write_info, wi_work); - svc_rdma_cc_release(info->wi_rdma, &info->wi_cc, DMA_TO_DEVICE); - kfree(info); -} - static void svc_rdma_write_info_free(struct svc_rdma_write_info *info) { - INIT_WORK(&info->wi_work, svc_rdma_write_info_free_async); - queue_work(svcrdma_wq, &info->wi_work); + svc_rdma_cc_release(info->wi_rdma, &info->wi_cc, DMA_TO_DEVICE); + kfree(info); } /** From 58202c29de9360a2b255458892e08a252d4406be Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Wed, 6 May 2026 11:26:51 -0400 Subject: [PATCH 048/106] svcrdma: Defer send context release to xpo_release_ctxt Send completion currently queues a work item to an unbound workqueue for each completed send context. Under load, the Send Completion handlers contend for the shared workqueue pool lock. Replace the workqueue with a per-transport lock-free list (llist). The Send completion handler appends the send_ctxt to sc_send_release_list and does no further teardown. The nfsd thread drains the list in xpo_release_ctxt between RPCs, performing DMA unmapping, chunk I/O resource release, and page release in a batch. This eliminates both the workqueue pool lock and the DMA unmap cost from the Send completion path. DMA unmapping can be expensive when an IOMMU is present in strict mode, as each unmap triggers a synchronous hardware IOTLB invalidation. Moving it to the nfsd thread, where that latency is harmless, avoids penalizing completion handler throughput. The nfsd threads absorb the release cost at a point where the client is no longer waiting on a reply, and natural batching amortizes the overhead when completions arrive faster than RPCs complete. A self-enqueue backstops drain on a quiescing transport. When svc_rdma_send_ctxt_put() observes that its llist_add() transitions sc_send_release_list from empty to non-empty, it sets XPT_DATA and calls svc_xprt_enqueue() so that svc_xprt_ready() schedules an nfsd thread. The thread enters svc_rdma_recvfrom(), finds no pending receive, clears XPT_DATA, and returns 0; svc_xprt_release() then runs xpo_release_ctxt and drains the list. Under steady load the foreground drain keeps the list non-empty between adds and no enqueue fires; only the trailing edge of a burst pays for a wakeup. Without this path, a Send completion arriving after the last xpo_release_ctxt on an idle connection would leave the send_ctxt's DMA mappings and reply pages pinned until the next RPC, send-context exhaustion, or transport close. Signed-off-by: Chuck Lever --- include/linux/sunrpc/svc_rdma.h | 5 +- net/sunrpc/xprtrdma/svc_rdma.c | 18 +---- net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 9 +++ net/sunrpc/xprtrdma/svc_rdma_sendto.c | 91 +++++++++++++++++------- net/sunrpc/xprtrdma/svc_rdma_transport.c | 3 +- 5 files changed, 82 insertions(+), 44 deletions(-) diff --git a/include/linux/sunrpc/svc_rdma.h b/include/linux/sunrpc/svc_rdma.h index 14eb9d52742e..4ba39f07371d 100644 --- a/include/linux/sunrpc/svc_rdma.h +++ b/include/linux/sunrpc/svc_rdma.h @@ -66,7 +66,6 @@ extern unsigned int svcrdma_ord; extern unsigned int svcrdma_max_requests; extern unsigned int svcrdma_max_bc_requests; extern unsigned int svcrdma_max_req_size; -extern struct workqueue_struct *svcrdma_wq; extern struct percpu_counter svcrdma_stat_read; extern struct percpu_counter svcrdma_stat_recv; @@ -117,6 +116,8 @@ struct svcxprt_rdma { struct llist_head sc_recv_ctxts; + struct llist_head sc_send_release_list; + atomic_t sc_completion_ids; }; /* sc_flags */ @@ -235,7 +236,6 @@ struct svc_rdma_write_info { struct svc_rdma_send_ctxt { struct llist_node sc_node; struct rpc_rdma_cid sc_cid; - struct work_struct sc_work; struct svcxprt_rdma *sc_rdma; struct ib_send_wr sc_send_wr; @@ -299,6 +299,7 @@ extern int svc_rdma_process_read_list(struct svcxprt_rdma *rdma, /* svc_rdma_sendto.c */ extern void svc_rdma_send_ctxts_destroy(struct svcxprt_rdma *rdma); +extern void svc_rdma_send_ctxts_drain(struct svcxprt_rdma *rdma); extern struct svc_rdma_send_ctxt * svc_rdma_send_ctxt_get(struct svcxprt_rdma *rdma); extern void svc_rdma_send_ctxt_put(struct svcxprt_rdma *rdma, diff --git a/net/sunrpc/xprtrdma/svc_rdma.c b/net/sunrpc/xprtrdma/svc_rdma.c index 415c0310101f..f67f0612b1a9 100644 --- a/net/sunrpc/xprtrdma/svc_rdma.c +++ b/net/sunrpc/xprtrdma/svc_rdma.c @@ -264,38 +264,22 @@ static int svc_rdma_proc_init(void) return rc; } -struct workqueue_struct *svcrdma_wq; - void svc_rdma_cleanup(void) { svc_unreg_xprt_class(&svc_rdma_class); svc_rdma_proc_cleanup(); - if (svcrdma_wq) { - struct workqueue_struct *wq = svcrdma_wq; - - svcrdma_wq = NULL; - destroy_workqueue(wq); - } dprintk("SVCRDMA Module Removed, deregister RPC RDMA transport\n"); } int svc_rdma_init(void) { - struct workqueue_struct *wq; int rc; - wq = alloc_workqueue("svcrdma", WQ_UNBOUND, 0); - if (!wq) - return -ENOMEM; - rc = svc_rdma_proc_init(); - if (rc) { - destroy_workqueue(wq); + if (rc) return rc; - } - svcrdma_wq = wq; svc_reg_xprt_class(&svc_rdma_class); dprintk("SVCRDMA Module Init, register RPC RDMA transport\n"); diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c index f8a0638eb095..19503a12d0a2 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c +++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c @@ -242,6 +242,10 @@ void svc_rdma_recv_ctxt_put(struct svcxprt_rdma *rdma, * Ensure that the recv_ctxt is released whether or not a Reply * was sent. For example, the client could close the connection, * or svc_process could drop an RPC, before the Reply is sent. + * + * Also drain any send_ctxts queued for deferred release so that + * DMA unmap and page release run in nfsd thread context between + * RPCs rather than on the Send completion path. */ void svc_rdma_release_ctxt(struct svc_xprt *xprt, void *vctxt) { @@ -251,6 +255,8 @@ void svc_rdma_release_ctxt(struct svc_xprt *xprt, void *vctxt) if (ctxt) svc_rdma_recv_ctxt_put(rdma, ctxt); + + svc_rdma_send_ctxts_drain(rdma); } static bool svc_rdma_refresh_recvs(struct svcxprt_rdma *rdma, @@ -384,6 +390,9 @@ static void svc_rdma_wc_receive(struct ib_cq *cq, struct ib_wc *wc) * svc_rdma_flush_recv_queues - Drain pending Receive work * @rdma: svcxprt_rdma being shut down * + * Caller must guarantee that @rdma's Send and Recv Completion + * Queues are empty (e.g., via ib_drain_qp()), so that no completion + * handlers can still produce work on the queues being drained. */ void svc_rdma_flush_recv_queues(struct svcxprt_rdma *rdma) { diff --git a/net/sunrpc/xprtrdma/svc_rdma_sendto.c b/net/sunrpc/xprtrdma/svc_rdma_sendto.c index 8b3f0c8c14b2..eceefd21bec8 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_sendto.c +++ b/net/sunrpc/xprtrdma/svc_rdma_sendto.c @@ -79,21 +79,21 @@ * The ownership of all of the Reply's pages are transferred into that * ctxt, the Send WR is posted, and sendto returns. * - * The svc_rdma_send_ctxt is presented when the Send WR completes. The - * Send completion handler finally releases the Reply's pages. - * - * This mechanism also assumes that completions on the transport's Send - * Completion Queue do not run in parallel. Otherwise a Write completion - * and Send completion running at the same time could release pages that - * are still DMA-mapped. + * The svc_rdma_send_ctxt is presented when the Send WR completes. + * The Send completion handler queues the send_ctxt onto the + * per-transport sc_send_release_list (a lock-free llist). The + * nfsd thread drains sc_send_release_list in xpo_release_ctxt + * between RPCs, DMA-unmapping SGEs, releasing chunk I/O + * resources and pages, and returning send_ctxts to the free + * list in a batch. * * Error Handling * * - If the Send WR is posted successfully, it will either complete * successfully, or get flushed. Either way, the Send completion - * handler releases the Reply's pages. - * - If the Send WR cannot be not posted, the forward path releases - * the Reply's pages. + * handler queues the send_ctxt for deferred release. + * - If the Send WR cannot be posted, the forward path releases the + * Reply's pages. * * This handles the case, without the use of page reference counting, * where two different Write segments send portions of the same page. @@ -226,14 +226,25 @@ struct svc_rdma_send_ctxt *svc_rdma_send_ctxt_get(struct svcxprt_rdma *rdma) return ctxt; out_empty: + svc_rdma_send_ctxts_drain(rdma); + + spin_lock(&rdma->sc_send_lock); + node = llist_del_first(&rdma->sc_send_ctxts); + spin_unlock(&rdma->sc_send_lock); + if (node) { + ctxt = llist_entry(node, struct svc_rdma_send_ctxt, sc_node); + goto out; + } + ctxt = svc_rdma_send_ctxt_alloc(rdma); if (!ctxt) return NULL; goto out; } -static void svc_rdma_send_ctxt_release(struct svcxprt_rdma *rdma, - struct svc_rdma_send_ctxt *ctxt) +/* Release chunk I/O resources and DMA-unmap SGEs. */ +static void svc_rdma_send_ctxt_unmap(struct svcxprt_rdma *rdma, + struct svc_rdma_send_ctxt *ctxt) { struct ib_device *device = rdma->sc_cm_id->device; unsigned int i; @@ -241,9 +252,6 @@ static void svc_rdma_send_ctxt_release(struct svcxprt_rdma *rdma, svc_rdma_write_chunk_release(rdma, ctxt); svc_rdma_reply_chunk_release(rdma, ctxt); - if (ctxt->sc_page_count) - release_pages(ctxt->sc_pages, ctxt->sc_page_count); - /* The first SGE contains the transport header, which * remains mapped until @ctxt is destroyed. */ @@ -256,30 +264,56 @@ static void svc_rdma_send_ctxt_release(struct svcxprt_rdma *rdma, ctxt->sc_sges[i].length, DMA_TO_DEVICE); } +} + +/* Unmap, release pages, and return send_ctxt to the free list. */ +static void svc_rdma_send_ctxt_release(struct svcxprt_rdma *rdma, + struct svc_rdma_send_ctxt *ctxt) +{ + svc_rdma_send_ctxt_unmap(rdma, ctxt); + + if (ctxt->sc_page_count) + release_pages(ctxt->sc_pages, ctxt->sc_page_count); llist_add(&ctxt->sc_node, &rdma->sc_send_ctxts); } -static void svc_rdma_send_ctxt_put_async(struct work_struct *work) +/** + * svc_rdma_send_ctxts_drain - Release completed send_ctxts + * @rdma: controlling svcxprt_rdma + */ +void svc_rdma_send_ctxts_drain(struct svcxprt_rdma *rdma) { - struct svc_rdma_send_ctxt *ctxt; + struct svc_rdma_send_ctxt *ctxt, *next; + struct llist_node *node; - ctxt = container_of(work, struct svc_rdma_send_ctxt, sc_work); - svc_rdma_send_ctxt_release(ctxt->sc_rdma, ctxt); + node = llist_del_all(&rdma->sc_send_release_list); + llist_for_each_entry_safe(ctxt, next, node, sc_node) + svc_rdma_send_ctxt_release(rdma, ctxt); } /** - * svc_rdma_send_ctxt_put - Return send_ctxt to free list + * svc_rdma_send_ctxt_put - Queue send_ctxt for deferred release * @rdma: controlling svcxprt_rdma - * @ctxt: object to return to the free list + * @ctxt: send_ctxt to queue for deferred release * - * Pages left in sc_pages are DMA unmapped and released. + * Queues @ctxt onto sc_send_release_list. DMA unmap and + * page release run later in svc_rdma_send_ctxts_drain(), + * typically from xpo_release_ctxt. + * + * On the empty-to-non-empty transition, set XPT_DATA and + * enqueue the transport. Without this self-trigger, a Send + * completion arriving after the last xpo_release_ctxt on an + * idle connection would leave the send_ctxt's DMA mappings + * and reply pages pinned until another drain occurred. */ void svc_rdma_send_ctxt_put(struct svcxprt_rdma *rdma, struct svc_rdma_send_ctxt *ctxt) { - INIT_WORK(&ctxt->sc_work, svc_rdma_send_ctxt_put_async); - queue_work(svcrdma_wq, &ctxt->sc_work); + if (llist_add(&ctxt->sc_node, &rdma->sc_send_release_list)) { + set_bit(XPT_DATA, &rdma->sc_xprt.xpt_flags); + svc_xprt_enqueue(&rdma->sc_xprt); + } } /** @@ -367,6 +401,15 @@ int svc_rdma_sq_wait(struct svcxprt_rdma *rdma, atomic_inc(&rdma->sc_sq_ticket_tail); wake_up(&rdma->sc_sq_ticket_wait); trace_svcrdma_sq_retry(rdma, cid); + + /* + * While this thread sat on sc_send_wait or sc_sq_ticket_wait, + * Send completions that tried to enqueue this transport for a + * release-list drain were rejected: svc_rdma_has_wspace returns + * 0 while either waitqueue is active, and svc_xprt_ready + * rejects the enqueue. Drain the release list now. + */ + svc_rdma_send_ctxts_drain(rdma); return 0; out_close: diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index f18bc60d9f4f..f99cd6177504 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -178,6 +178,7 @@ static struct svcxprt_rdma *svc_rdma_create_xprt(struct svc_serv *serv, init_llist_head(&cma_xprt->sc_send_ctxts); init_llist_head(&cma_xprt->sc_recv_ctxts); init_llist_head(&cma_xprt->sc_rw_ctxts); + init_llist_head(&cma_xprt->sc_send_release_list); init_waitqueue_head(&cma_xprt->sc_send_wait); init_waitqueue_head(&cma_xprt->sc_sq_ticket_wait); @@ -614,7 +615,7 @@ static void svc_rdma_free(struct svc_xprt *xprt) /* This blocks until the Completion Queues are empty */ if (rdma->sc_qp && !IS_ERR(rdma->sc_qp)) ib_drain_qp(rdma->sc_qp); - flush_workqueue(svcrdma_wq); + svc_rdma_send_ctxts_drain(rdma); svc_rdma_flush_recv_queues(rdma); From 5cca6056f2bae9be14566e0f7f6e351103a6aef3 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:36 -0400 Subject: [PATCH 049/106] lockd: Stop warning on nlm__int__drop_reply in !V4 cast_status cast_status folds internal lock-daemon sentinels into NLMv1/v3 wire status codes. The !CONFIG_LOCKD_V4 variant warns when an unrecognized status falls into the internal-sentinel range, gated by be32_to_cpu(status) >= 30000. nlm__int__drop_reply is defined as cpu_to_be32(30000), so it sits at the lower edge of that range and trips pr_warn_once ("lockd: unhandled internal status %u"). The status is returned unchanged so the reply is still dropped, but every dropped reply on a !CONFIG_LOCKD_V4 build emits a spurious warning. Compare against nlm__int__drop_reply directly so the warning still catches the genuinely unexpected sentinels deadlock, stale_fh, and failed (30001 through 30003) but excludes the legitimate dropped-reply marker. Fixes: d343fce148a4 ("[PATCH] knfsd: Allow lockd to drop replies as appropriate") Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index c0a3487719e2..110e186802b6 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -49,7 +49,7 @@ static inline __be32 cast_status(__be32 status) status = nlm_lck_denied_nolocks; break; default: - if (be32_to_cpu(status) >= 30000) + if (be32_to_cpu(status) > be32_to_cpu(nlm__int__drop_reply)) pr_warn_once("lockd: unhandled internal status %u\n", be32_to_cpu(status)); break; From 5412049208e669925f7b08bbfabe3cd28a598c5b Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:37 -0400 Subject: [PATCH 050/106] lockd: Correct kernel-doc status descriptions for NLMv4 GRANTED NLM_GRANTED is a server-to-client callback; the local node responds in the role of the client. The kernel-doc for nlm4svc_proc_granted attributes NLM4_DENIED and NLM4_DENIED_GRACE_PERIOD to "the server", but per the Open Group XNFS specification the responder for this procedure is the client host, and NLM4_DENIED_GRACE_PERIOD identifies the client's own grace period after a reboot, not the server's. Rewrite the descriptions to match the spec: NLM4_DENIED reflects the generic internal-resource-constraint failure, and NLM4_DENIED_GRACE_PERIOD attributes the grace period to the client host that received the callback. Fixes: 7a9f7c8f934e ("lockd: Use xdrgen XDR functions for the NLMv4 GRANTED procedure") Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svc4proc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 41cab858de57..fc9ed4abb7ca 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -513,12 +513,12 @@ nlm4svc_proc_unlock(struct svc_rqst *rqstp) * nlm4_res NLMPROC4_GRANTED(nlm4_testargs) = 5; * * Permissible procedure status codes: - * %NLM4_GRANTED: The requested lock was granted. - * %NLM4_DENIED: The server could not allocate the resources - * needed to process the request. - * %NLM4_DENIED_GRACE_PERIOD: The server has recently restarted and is - * re-establishing existing locks, and is not - * yet ready to accept normal service requests. + * %NLM4_GRANTED: The granted lock was accepted. + * %NLM4_DENIED: The procedure failed, possibly due to + * internal resource constraints. + * %NLM4_DENIED_GRACE_PERIOD: The client host recently restarted and + * its NLM is re-establishing existing locks, + * so it is not yet ready to accept callbacks. */ static __be32 nlm4svc_proc_granted(struct svc_rqst *rqstp) From 6d4c0dfeba728f2d06f634f02eb7606b82d87fbd Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:38 -0400 Subject: [PATCH 051/106] lockd: Drop locks_init_lock() from nlm4_lock_to_lockd_lock() The NLMv4 GRANTED helper passes the wrapper's lock to nlmclnt_grant(), which compares only fl_start, fl_end, svid, and fh, and the shared nlmclnt_lock_event tracepoint now sources its byte-range fields from fl_start and fl_end as well. Both fl_start and fl_end are set unconditionally by lockd_set_file_lock_range4() on the line below, so the locks_init_lock() call left no observable effect: every other field of struct file_lock is unread on the GRANTED path. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svc4proc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index fc9ed4abb7ca..2bd71bc2b481 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -119,7 +119,6 @@ nlm4_lock_to_nlm_lock(struct nlm_lock *lock, struct nlm4_lock *alock) lock->oh.len = alock->oh.len; lock->oh.data = alock->oh.data; lock->svid = alock->svid; - locks_init_lock(&lock->fl); lockd_set_file_lock_range4(&lock->fl, alock->l_offset, alock->l_len); return nlm_granted; } From 2175ca75882e346c9d8741a2196693d70f635340 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:39 -0400 Subject: [PATCH 052/106] lockd: Translate nlm__int__deadlock in __nlm4svc_proc_lock_msg() When nlmsvc_lock() detects a deadlock it returns the internal sentinel nlm__int__deadlock (30001), which version-specific handlers must translate to a wire-valid status before the reply is encoded. The xdrgen LOCK_MSG handler stores the sentinel unmodified in resp->status; the LOCK_RES callback then places 30001 on the v4 wire, where the client rejects the reply. Commit 9e0d0c619407 ("lockd: Introduce nlm__int__deadlock") established the translation boundary and updated the synchronous v4 path nlm4svc_do_lock(), but the xdrgen LOCK_MSG handler added later in commit b2be4e28c23a ("lockd: Use xdrgen XDR functions for the NLMv4 LOCK_MSG procedure") missed the corresponding remap. Apply the same translation in __nlm4svc_proc_lock_msg() so deadlock results are reported as nlm4_deadlock on LOCK_RES. Fixes: b2be4e28c23a ("lockd: Use xdrgen XDR functions for the NLMv4 LOCK_MSG procedure") Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svc4proc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 2bd71bc2b481..886b56317e5f 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -668,6 +668,8 @@ __nlm4svc_proc_lock_msg(struct svc_rqst *rqstp, struct nlm_res *resp) resp->status = nlmsvc_lock(rqstp, file, host, &argp->lock, argp->xdrgen.block, &resp->cookie, argp->xdrgen.reclaim); + if (resp->status == nlm__int__deadlock) + resp->status = nlm4_deadlock; nlmsvc_release_lockowner(&argp->lock); out: From b3d200166a35305ac77941845dcab99bb6badd76 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:40 -0400 Subject: [PATCH 053/106] lockd: Do not monitor when looking up the LOCK_MSG callback host A LOCK_MSG handler that fails to obtain a host returns rpc_system_err, which causes the dispatcher to send an RPC-level error rather than an NLM LOCK_RES denial. Before the xdrgen conversion, the outer host lookup was unmonitored, so an NSM upcall failure was reported back to the client through LOCK_RES with status nlm_lck_denied_nolocks generated by the inner helper. The xdrgen conversion replaced the unmonitored lookup with nlm4svc_lookup_host(..., true). When nsm_monitor() fails, the outer lookup now returns NULL, so the procedure short-circuits to rpc_system_err and __nlm4svc_proc_lock_msg() never runs. The client therefore receives no LOCK_RES, regressing the legacy behavior. The inner helper still performs a monitored lookup while building the LOCK_RES, so the outer call only needs an unmonitored host reference for the callback path. Pass false here to restore the previous semantics. Fixes: b2be4e28c23a ("lockd: Use xdrgen XDR functions for the NLMv4 LOCK_MSG procedure") Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svc4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 886b56317e5f..e3a6d69c1fa6 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -698,7 +698,7 @@ static __be32 nlm4svc_proc_lock_msg(struct svc_rqst *rqstp) struct nlm4_lockargs_wrapper *argp = rqstp->rq_argp; struct nlm_host *host; - host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, true); + host = nlm4svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); if (!host) return rpc_system_err; From 4b386d8ddb3633e300218325e2d81f8cf159fb51 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:41 -0400 Subject: [PATCH 054/106] Documentation: Add the RPC language description of NLM version 3 In order to generate source code to encode and decode NLMv3 protocol elements, include a copy of the RPC language description of NLMv3 for xdrgen to process. The language description is derived from the Open Group's XNFS specification: https://pubs.opengroup.org/onlinepubs/9629799/chap10.htm#tagcjh_11_03 The C code committed here was generated from the new nlm3.x file using tools/net/sunrpc/xdrgen/xdrgen. The goals of replacing hand-written XDR functions with ones that are tool-generated are to improve memory safety and make XDR encoding and decoding less brittle to maintain. Parts of the NFSv4 protocol are still being extended actively. Tool-generated XDR code reduces the time it takes to get a working implementation of new protocol elements. The xdrgen utility derives both the type definitions and the encode/decode functions directly from protocol specifications, using names and symbols familiar to anyone who knows those specs. Unlike hand-written code that can inadvertently diverge from the specification, xdrgen guarantees that the generated code matches the specification exactly. We would eventually like xdrgen to generate Rust code as well, making the conversion of the kernel's NFS stacks to use Rust just a little easier for us. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- Documentation/sunrpc/xdr/nlm3.x | 168 +++++++ fs/lockd/Makefile | 19 +- fs/lockd/nlm3xdr_gen.c | 714 +++++++++++++++++++++++++++++ fs/lockd/nlm3xdr_gen.h | 32 ++ include/linux/sunrpc/xdrgen/nlm3.h | 210 +++++++++ 5 files changed, 1139 insertions(+), 4 deletions(-) create mode 100644 Documentation/sunrpc/xdr/nlm3.x create mode 100644 fs/lockd/nlm3xdr_gen.c create mode 100644 fs/lockd/nlm3xdr_gen.h create mode 100644 include/linux/sunrpc/xdrgen/nlm3.h diff --git a/Documentation/sunrpc/xdr/nlm3.x b/Documentation/sunrpc/xdr/nlm3.x new file mode 100644 index 000000000000..b2e704f7b864 --- /dev/null +++ b/Documentation/sunrpc/xdr/nlm3.x @@ -0,0 +1,168 @@ +/* + * This file was extracted by hand from + * https://pubs.opengroup.org/onlinepubs/9629799/chap10.htm#tagcjh_11_03 + */ + +/* + * The NLMv3 protocol + */ + +pragma header nlm3; + +const LM_MAXSTRLEN = 1024; + +const LM_MAXNAMELEN = 1025; + +const MAXNETOBJ_SZ = 1024; + +typedef opaque netobj; + +enum nlm_stats { + LCK_GRANTED = 0, + LCK_DENIED = 1, + LCK_DENIED_NOLOCKS = 2, + LCK_BLOCKED = 3, + LCK_DENIED_GRACE_PERIOD = 4 +}; + +pragma big_endian nlm_stats; + +struct nlm_stat { + nlm_stats stat; +}; + +struct nlm_res { + netobj cookie; + nlm_stat stat; +}; + +struct nlm_holder { + bool exclusive; + int uppid; + netobj oh; + unsigned int l_offset; + unsigned int l_len; +}; + +union nlm_testrply switch (nlm_stats stat) { + case LCK_DENIED: + nlm_holder holder; + default: + void; +}; + +struct nlm_testres { + netobj cookie; + nlm_testrply test_stat; +}; + +struct nlm_lock { + string caller_name; + netobj fh; + netobj oh; + int uppid; + unsigned int l_offset; + unsigned int l_len; +}; + +struct nlm_lockargs { + netobj cookie; + bool block; + bool exclusive; + nlm_lock alock; + bool reclaim; + int state; +}; + +struct nlm_cancargs { + netobj cookie; + bool block; + bool exclusive; + nlm_lock alock; +}; + +struct nlm_testargs { + netobj cookie; + bool exclusive; + nlm_lock alock; +}; + +struct nlm_unlockargs { + netobj cookie; + nlm_lock alock; +}; + +enum fsh_mode { + fsm_DN = 0, + fsm_DR = 1, + fsm_DW = 2, + fsm_DRW = 3 +}; + +enum fsh_access { + fsa_NONE = 0, + fsa_R = 1, + fsa_W = 2, + fsa_RW = 3 +}; + +struct nlm_share { + string caller_name; + netobj fh; + netobj oh; + fsh_mode mode; + fsh_access access; +}; + +struct nlm_shareargs { + netobj cookie; + nlm_share share; + bool reclaim; +}; + +struct nlm_shareres { + netobj cookie; + nlm_stats stat; + int sequence; +}; + +struct nlm_notify { + string name; + long state; +}; + +/* + * Argument for the Linux-private SM_NOTIFY procedure + */ +const SM_PRIV_SIZE = 16; + +struct nlm_notifyargs { + nlm_notify notify; + opaque private[SM_PRIV_SIZE]; +}; + +program NLM_PROG { + version NLM_VERS { + void NLM_NULL(void) = 0; + nlm_testres NLM_TEST(nlm_testargs) = 1; + nlm_res NLM_LOCK(nlm_lockargs) = 2; + nlm_res NLM_CANCEL(nlm_cancargs) = 3; + nlm_res NLM_UNLOCK(nlm_unlockargs) = 4; + nlm_res NLM_GRANTED(nlm_testargs) = 5; + void NLM_TEST_MSG(nlm_testargs) = 6; + void NLM_LOCK_MSG(nlm_lockargs) = 7; + void NLM_CANCEL_MSG(nlm_cancargs) = 8; + void NLM_UNLOCK_MSG(nlm_unlockargs) = 9; + void NLM_GRANTED_MSG(nlm_testargs) = 10; + void NLM_TEST_RES(nlm_testres) = 11; + void NLM_LOCK_RES(nlm_res) = 12; + void NLM_CANCEL_RES(nlm_res) = 13; + void NLM_UNLOCK_RES(nlm_res) = 14; + void NLM_GRANTED_RES(nlm_res) = 15; + void NLM_SM_NOTIFY(nlm_notifyargs) = 16; + nlm_shareres NLM_SHARE(nlm_shareargs) = 20; + nlm_shareres NLM_UNSHARE(nlm_shareargs) = 21; + nlm_res NLM_NM_LOCK(nlm_lockargs) = 22; + void NLM_FREE_ALL(nlm_notify) = 23; + } = 3; +} = 100021; diff --git a/fs/lockd/Makefile b/fs/lockd/Makefile index 808f0f2a7be1..951a74e4847a 100644 --- a/fs/lockd/Makefile +++ b/fs/lockd/Makefile @@ -8,7 +8,8 @@ ccflags-y += -I$(src) # needed for trace events obj-$(CONFIG_LOCKD) += lockd.o lockd-y := clntlock.o clntproc.o clntxdr.o host.o svc.o svclock.o \ - svcshare.o svcproc.o svcsubs.o mon.o trace.o xdr.o netlink.o + svcshare.o svcproc.o svcsubs.o mon.o trace.o xdr.o netlink.o \ + nlm3xdr_gen.o lockd-$(CONFIG_LOCKD_V4) += clnt4xdr.o svc4proc.o nlm4xdr_gen.o lockd-$(CONFIG_PROC_FS) += procfs.o @@ -25,17 +26,27 @@ lockd-$(CONFIG_PROC_FS) += procfs.o XDRGEN = ../../tools/net/sunrpc/xdrgen/xdrgen -XDRGEN_DEFINITIONS = ../../include/linux/sunrpc/xdrgen/nlm4.h -XDRGEN_DECLARATIONS = nlm4xdr_gen.h -XDRGEN_SOURCE = nlm4xdr_gen.c +XDRGEN_DEFINITIONS = ../../include/linux/sunrpc/xdrgen/nlm4.h \ + ../../include/linux/sunrpc/xdrgen/nlm3.h +XDRGEN_DECLARATIONS = nlm4xdr_gen.h nlm3xdr_gen.h +XDRGEN_SOURCE = nlm4xdr_gen.c nlm3xdr_gen.c xdrgen: $(XDRGEN_DEFINITIONS) $(XDRGEN_DECLARATIONS) $(XDRGEN_SOURCE) ../../include/linux/sunrpc/xdrgen/nlm4.h: ../../Documentation/sunrpc/xdr/nlm4.x $(XDRGEN) definitions $< > $@ +../../include/linux/sunrpc/xdrgen/nlm3.h: ../../Documentation/sunrpc/xdr/nlm3.x + $(XDRGEN) definitions $< > $@ + nlm4xdr_gen.h: ../../Documentation/sunrpc/xdr/nlm4.x $(XDRGEN) declarations $< > $@ +nlm3xdr_gen.h: ../../Documentation/sunrpc/xdr/nlm3.x + $(XDRGEN) declarations $< > $@ + nlm4xdr_gen.c: ../../Documentation/sunrpc/xdr/nlm4.x $(XDRGEN) source --peer server $< > $@ + +nlm3xdr_gen.c: ../../Documentation/sunrpc/xdr/nlm3.x + $(XDRGEN) source --peer server $< > $@ diff --git a/fs/lockd/nlm3xdr_gen.c b/fs/lockd/nlm3xdr_gen.c new file mode 100644 index 000000000000..9ed5a41b5daf --- /dev/null +++ b/fs/lockd/nlm3xdr_gen.c @@ -0,0 +1,714 @@ +// SPDX-License-Identifier: GPL-2.0 +// Generated by xdrgen. Manual edits will be lost. +// XDR specification file: ../../Documentation/sunrpc/xdr/nlm3.x +// XDR specification modification time: Thu Apr 23 10:56:34 2026 + +#include + +#include "nlm3xdr_gen.h" + +static bool __maybe_unused +xdrgen_decode_netobj(struct xdr_stream *xdr, netobj *ptr) +{ + return xdrgen_decode_opaque(xdr, ptr, MAXNETOBJ_SZ); +} + +static bool __maybe_unused +xdrgen_decode_nlm_stats(struct xdr_stream *xdr, nlm_stats *ptr) +{ + __be32 raw; + u32 val; + + if (xdr_stream_decode_be32(xdr, &raw) < 0) + return false; + val = be32_to_cpu(raw); + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case LCK_GRANTED: + case LCK_DENIED: + case LCK_DENIED_NOLOCKS: + case LCK_BLOCKED: + case LCK_DENIED_GRACE_PERIOD: + break; + default: + return false; + } + *ptr = raw; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_stat(struct xdr_stream *xdr, struct nlm_stat *ptr) +{ + if (!xdrgen_decode_nlm_stats(xdr, &ptr->stat)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_res(struct xdr_stream *xdr, struct nlm_res *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_nlm_stat(xdr, &ptr->stat)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_holder(struct xdr_stream *xdr, struct nlm_holder *ptr) +{ + if (!xdrgen_decode_bool(xdr, &ptr->exclusive)) + return false; + if (!xdrgen_decode_int(xdr, &ptr->uppid)) + return false; + if (!xdrgen_decode_netobj(xdr, &ptr->oh)) + return false; + if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_offset)) + return false; + if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_len)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_testrply(struct xdr_stream *xdr, struct nlm_testrply *ptr) +{ + if (!xdrgen_decode_nlm_stats(xdr, &ptr->stat)) + return false; + switch (ptr->stat) { + case __constant_cpu_to_be32(LCK_DENIED): + if (!xdrgen_decode_nlm_holder(xdr, &ptr->u.holder)) + return false; + break; + default: + break; + } + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_testres(struct xdr_stream *xdr, struct nlm_testres *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_nlm_testrply(xdr, &ptr->test_stat)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_lock(struct xdr_stream *xdr, struct nlm_lock *ptr) +{ + if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXSTRLEN)) + return false; + if (!xdrgen_decode_netobj(xdr, &ptr->fh)) + return false; + if (!xdrgen_decode_netobj(xdr, &ptr->oh)) + return false; + if (!xdrgen_decode_int(xdr, &ptr->uppid)) + return false; + if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_offset)) + return false; + if (!xdrgen_decode_unsigned_int(xdr, &ptr->l_len)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_lockargs(struct xdr_stream *xdr, struct nlm_lockargs *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->block)) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->exclusive)) + return false; + if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock)) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->reclaim)) + return false; + if (!xdrgen_decode_int(xdr, &ptr->state)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_cancargs(struct xdr_stream *xdr, struct nlm_cancargs *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->block)) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->exclusive)) + return false; + if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_testargs(struct xdr_stream *xdr, struct nlm_testargs *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->exclusive)) + return false; + if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_unlockargs(struct xdr_stream *xdr, struct nlm_unlockargs *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_nlm_lock(xdr, &ptr->alock)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_fsh_mode(struct xdr_stream *xdr, fsh_mode *ptr) +{ + u32 val; + + if (xdr_stream_decode_u32(xdr, &val) < 0) + return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case fsm_DN: + case fsm_DR: + case fsm_DW: + case fsm_DRW: + break; + default: + return false; + } + *ptr = val; + return true; +} + +static bool __maybe_unused +xdrgen_decode_fsh_access(struct xdr_stream *xdr, fsh_access *ptr) +{ + u32 val; + + if (xdr_stream_decode_u32(xdr, &val) < 0) + return false; + /* Compiler may optimize to a range check for dense enums */ + switch (val) { + case fsa_NONE: + case fsa_R: + case fsa_W: + case fsa_RW: + break; + default: + return false; + } + *ptr = val; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_share(struct xdr_stream *xdr, struct nlm_share *ptr) +{ + if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXSTRLEN)) + return false; + if (!xdrgen_decode_netobj(xdr, &ptr->fh)) + return false; + if (!xdrgen_decode_netobj(xdr, &ptr->oh)) + return false; + if (!xdrgen_decode_fsh_mode(xdr, &ptr->mode)) + return false; + if (!xdrgen_decode_fsh_access(xdr, &ptr->access)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_shareargs(struct xdr_stream *xdr, struct nlm_shareargs *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_nlm_share(xdr, &ptr->share)) + return false; + if (!xdrgen_decode_bool(xdr, &ptr->reclaim)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_shareres(struct xdr_stream *xdr, struct nlm_shareres *ptr) +{ + if (!xdrgen_decode_netobj(xdr, &ptr->cookie)) + return false; + if (!xdrgen_decode_nlm_stats(xdr, &ptr->stat)) + return false; + if (!xdrgen_decode_int(xdr, &ptr->sequence)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_notify(struct xdr_stream *xdr, struct nlm_notify *ptr) +{ + if (!xdrgen_decode_string(xdr, (string *)ptr, LM_MAXNAMELEN)) + return false; + if (!xdrgen_decode_long(xdr, &ptr->state)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_decode_nlm_notifyargs(struct xdr_stream *xdr, struct nlm_notifyargs *ptr) +{ + if (!xdrgen_decode_nlm_notify(xdr, &ptr->notify)) + return false; + if (xdr_stream_decode_opaque_fixed(xdr, ptr->private, SM_PRIV_SIZE) < 0) + return false; + return true; +} + +/** + * nlm_svc_decode_void - Decode a void argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + return xdrgen_decode_void(xdr); +} + +/** + * nlm_svc_decode_nlm_testargs - Decode a nlm_testargs argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_testargs *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_testargs(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_lockargs - Decode a nlm_lockargs argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_lockargs *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_lockargs(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_cancargs - Decode a nlm_cancargs argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_cancargs *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_cancargs(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_unlockargs - Decode a nlm_unlockargs argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_unlockargs *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_unlockargs(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_testres - Decode a nlm_testres argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_testres *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_testres(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_res - Decode a nlm_res argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_res *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_res(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_notifyargs - Decode a nlm_notifyargs argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_notifyargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_notifyargs *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_notifyargs(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_shareargs - Decode a nlm_shareargs argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_shareargs *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_shareargs(xdr, argp); +} + +/** + * nlm_svc_decode_nlm_notify - Decode a nlm_notify argument + * @rqstp: RPC transaction context + * @xdr: source XDR data stream + * + * Return values: + * %true: procedure arguments decoded successfully + * %false: decode failed + */ +bool nlm_svc_decode_nlm_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_notify *argp = rqstp->rq_argp; + + return xdrgen_decode_nlm_notify(xdr, argp); +} + +static bool __maybe_unused +xdrgen_encode_netobj(struct xdr_stream *xdr, const netobj value) +{ + return xdr_stream_encode_opaque(xdr, value.data, value.len) >= 0; +} + +static bool __maybe_unused +xdrgen_encode_nlm_stats(struct xdr_stream *xdr, nlm_stats value) +{ + return xdr_stream_encode_be32(xdr, value) == XDR_UNIT; +} + +static bool __maybe_unused +xdrgen_encode_nlm_stat(struct xdr_stream *xdr, const struct nlm_stat *value) +{ + if (!xdrgen_encode_nlm_stats(xdr, value->stat)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_res(struct xdr_stream *xdr, const struct nlm_res *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_nlm_stat(xdr, &value->stat)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_holder(struct xdr_stream *xdr, const struct nlm_holder *value) +{ + if (!xdrgen_encode_bool(xdr, value->exclusive)) + return false; + if (!xdrgen_encode_int(xdr, value->uppid)) + return false; + if (!xdrgen_encode_netobj(xdr, value->oh)) + return false; + if (!xdrgen_encode_unsigned_int(xdr, value->l_offset)) + return false; + if (!xdrgen_encode_unsigned_int(xdr, value->l_len)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_testrply(struct xdr_stream *xdr, const struct nlm_testrply *ptr) +{ + if (!xdrgen_encode_nlm_stats(xdr, ptr->stat)) + return false; + switch (ptr->stat) { + case __constant_cpu_to_be32(LCK_DENIED): + if (!xdrgen_encode_nlm_holder(xdr, &ptr->u.holder)) + return false; + break; + default: + break; + } + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_testres(struct xdr_stream *xdr, const struct nlm_testres *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_nlm_testrply(xdr, &value->test_stat)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_lock(struct xdr_stream *xdr, const struct nlm_lock *value) +{ + if (value->caller_name.len > LM_MAXSTRLEN) + return false; + if (xdr_stream_encode_opaque(xdr, value->caller_name.data, value->caller_name.len) < 0) + return false; + if (!xdrgen_encode_netobj(xdr, value->fh)) + return false; + if (!xdrgen_encode_netobj(xdr, value->oh)) + return false; + if (!xdrgen_encode_int(xdr, value->uppid)) + return false; + if (!xdrgen_encode_unsigned_int(xdr, value->l_offset)) + return false; + if (!xdrgen_encode_unsigned_int(xdr, value->l_len)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_lockargs(struct xdr_stream *xdr, const struct nlm_lockargs *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_bool(xdr, value->block)) + return false; + if (!xdrgen_encode_bool(xdr, value->exclusive)) + return false; + if (!xdrgen_encode_nlm_lock(xdr, &value->alock)) + return false; + if (!xdrgen_encode_bool(xdr, value->reclaim)) + return false; + if (!xdrgen_encode_int(xdr, value->state)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_cancargs(struct xdr_stream *xdr, const struct nlm_cancargs *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_bool(xdr, value->block)) + return false; + if (!xdrgen_encode_bool(xdr, value->exclusive)) + return false; + if (!xdrgen_encode_nlm_lock(xdr, &value->alock)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_testargs(struct xdr_stream *xdr, const struct nlm_testargs *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_bool(xdr, value->exclusive)) + return false; + if (!xdrgen_encode_nlm_lock(xdr, &value->alock)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_unlockargs(struct xdr_stream *xdr, const struct nlm_unlockargs *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_nlm_lock(xdr, &value->alock)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_fsh_mode(struct xdr_stream *xdr, fsh_mode value) +{ + return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; +} + +static bool __maybe_unused +xdrgen_encode_fsh_access(struct xdr_stream *xdr, fsh_access value) +{ + return xdr_stream_encode_u32(xdr, value) == XDR_UNIT; +} + +static bool __maybe_unused +xdrgen_encode_nlm_share(struct xdr_stream *xdr, const struct nlm_share *value) +{ + if (value->caller_name.len > LM_MAXSTRLEN) + return false; + if (xdr_stream_encode_opaque(xdr, value->caller_name.data, value->caller_name.len) < 0) + return false; + if (!xdrgen_encode_netobj(xdr, value->fh)) + return false; + if (!xdrgen_encode_netobj(xdr, value->oh)) + return false; + if (!xdrgen_encode_fsh_mode(xdr, value->mode)) + return false; + if (!xdrgen_encode_fsh_access(xdr, value->access)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_shareargs(struct xdr_stream *xdr, const struct nlm_shareargs *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_nlm_share(xdr, &value->share)) + return false; + if (!xdrgen_encode_bool(xdr, value->reclaim)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_shareres(struct xdr_stream *xdr, const struct nlm_shareres *value) +{ + if (!xdrgen_encode_netobj(xdr, value->cookie)) + return false; + if (!xdrgen_encode_nlm_stats(xdr, value->stat)) + return false; + if (!xdrgen_encode_int(xdr, value->sequence)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_notify(struct xdr_stream *xdr, const struct nlm_notify *value) +{ + if (value->name.len > LM_MAXNAMELEN) + return false; + if (xdr_stream_encode_opaque(xdr, value->name.data, value->name.len) < 0) + return false; + if (!xdrgen_encode_long(xdr, value->state)) + return false; + return true; +} + +static bool __maybe_unused +xdrgen_encode_nlm_notifyargs(struct xdr_stream *xdr, const struct nlm_notifyargs *value) +{ + if (!xdrgen_encode_nlm_notify(xdr, &value->notify)) + return false; + if (xdr_stream_encode_opaque_fixed(xdr, value->private, SM_PRIV_SIZE) < 0) + return false; + return true; +} + +/** + * nlm_svc_encode_void - Encode a void result + * @rqstp: RPC transaction context + * @xdr: target XDR data stream + * + * Return values: + * %true: procedure results encoded successfully + * %false: encode failed + */ +bool nlm_svc_encode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + return xdrgen_encode_void(xdr); +} + +/** + * nlm_svc_encode_nlm_testres - Encode a nlm_testres result + * @rqstp: RPC transaction context + * @xdr: target XDR data stream + * + * Return values: + * %true: procedure results encoded successfully + * %false: encode failed + */ +bool nlm_svc_encode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_testres *resp = rqstp->rq_resp; + + return xdrgen_encode_nlm_testres(xdr, resp); +} + +/** + * nlm_svc_encode_nlm_res - Encode a nlm_res result + * @rqstp: RPC transaction context + * @xdr: target XDR data stream + * + * Return values: + * %true: procedure results encoded successfully + * %false: encode failed + */ +bool nlm_svc_encode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_res *resp = rqstp->rq_resp; + + return xdrgen_encode_nlm_res(xdr, resp); +} + +/** + * nlm_svc_encode_nlm_shareres - Encode a nlm_shareres result + * @rqstp: RPC transaction context + * @xdr: target XDR data stream + * + * Return values: + * %true: procedure results encoded successfully + * %false: encode failed + */ +bool nlm_svc_encode_nlm_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr) +{ + struct nlm_shareres *resp = rqstp->rq_resp; + + return xdrgen_encode_nlm_shareres(xdr, resp); +} diff --git a/fs/lockd/nlm3xdr_gen.h b/fs/lockd/nlm3xdr_gen.h new file mode 100644 index 000000000000..c99038e99805 --- /dev/null +++ b/fs/lockd/nlm3xdr_gen.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Generated by xdrgen. Manual edits will be lost. */ +/* XDR specification file: ../../Documentation/sunrpc/xdr/nlm3.x */ +/* XDR specification modification time: Thu Apr 23 10:56:34 2026 */ + +#ifndef _LINUX_XDRGEN_NLM3_DECL_H +#define _LINUX_XDRGEN_NLM3_DECL_H + +#include + +#include +#include +#include +#include + +bool nlm_svc_decode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_notifyargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_decode_nlm_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr); + +bool nlm_svc_encode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_encode_nlm_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_encode_nlm_res(struct svc_rqst *rqstp, struct xdr_stream *xdr); +bool nlm_svc_encode_nlm_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr); + +#endif /* _LINUX_XDRGEN_NLM3_DECL_H */ diff --git a/include/linux/sunrpc/xdrgen/nlm3.h b/include/linux/sunrpc/xdrgen/nlm3.h new file mode 100644 index 000000000000..897e7d91807c --- /dev/null +++ b/include/linux/sunrpc/xdrgen/nlm3.h @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Generated by xdrgen. Manual edits will be lost. */ +/* XDR specification file: ../../Documentation/sunrpc/xdr/nlm3.x */ +/* XDR specification modification time: Thu Apr 23 10:56:34 2026 */ + +#ifndef _LINUX_XDRGEN_NLM3_DEF_H +#define _LINUX_XDRGEN_NLM3_DEF_H + +#include +#include + +enum { LM_MAXSTRLEN = 1024 }; + +enum { LM_MAXNAMELEN = 1025 }; + +enum { MAXNETOBJ_SZ = 1024 }; + +typedef opaque netobj; + +enum nlm_stats { + LCK_GRANTED = 0, + LCK_DENIED = 1, + LCK_DENIED_NOLOCKS = 2, + LCK_BLOCKED = 3, + LCK_DENIED_GRACE_PERIOD = 4, +}; + +typedef __be32 nlm_stats; + +struct nlm_stat { + nlm_stats stat; +}; + +struct nlm_res { + netobj cookie; + struct nlm_stat stat; +}; + +struct nlm_holder { + bool exclusive; + s32 uppid; + netobj oh; + u32 l_offset; + u32 l_len; +}; + +struct nlm_testrply { + nlm_stats stat; + union { + struct nlm_holder holder; + } u; +}; + +struct nlm_testres { + netobj cookie; + struct nlm_testrply test_stat; +}; + +struct nlm_lock { + string caller_name; + netobj fh; + netobj oh; + s32 uppid; + u32 l_offset; + u32 l_len; +}; + +struct nlm_lockargs { + netobj cookie; + bool block; + bool exclusive; + struct nlm_lock alock; + bool reclaim; + s32 state; +}; + +struct nlm_cancargs { + netobj cookie; + bool block; + bool exclusive; + struct nlm_lock alock; +}; + +struct nlm_testargs { + netobj cookie; + bool exclusive; + struct nlm_lock alock; +}; + +struct nlm_unlockargs { + netobj cookie; + struct nlm_lock alock; +}; + +enum fsh_mode { + fsm_DN = 0, + fsm_DR = 1, + fsm_DW = 2, + fsm_DRW = 3, +}; + +typedef enum fsh_mode fsh_mode; + +enum fsh_access { + fsa_NONE = 0, + fsa_R = 1, + fsa_W = 2, + fsa_RW = 3, +}; + +typedef enum fsh_access fsh_access; + +struct nlm_share { + string caller_name; + netobj fh; + netobj oh; + fsh_mode mode; + fsh_access access; +}; + +struct nlm_shareargs { + netobj cookie; + struct nlm_share share; + bool reclaim; +}; + +struct nlm_shareres { + netobj cookie; + nlm_stats stat; + s32 sequence; +}; + +struct nlm_notify { + string name; + s32 state; +}; + +enum { SM_PRIV_SIZE = 16 }; + +struct nlm_notifyargs { + struct nlm_notify notify; + u8 private[SM_PRIV_SIZE]; +}; + +enum { + NLM_NULL = 0, + NLM_TEST = 1, + NLM_LOCK = 2, + NLM_CANCEL = 3, + NLM_UNLOCK = 4, + NLM_GRANTED = 5, + NLM_TEST_MSG = 6, + NLM_LOCK_MSG = 7, + NLM_CANCEL_MSG = 8, + NLM_UNLOCK_MSG = 9, + NLM_GRANTED_MSG = 10, + NLM_TEST_RES = 11, + NLM_LOCK_RES = 12, + NLM_CANCEL_RES = 13, + NLM_UNLOCK_RES = 14, + NLM_GRANTED_RES = 15, + NLM_SM_NOTIFY = 16, + NLM_SHARE = 20, + NLM_UNSHARE = 21, + NLM_NM_LOCK = 22, + NLM_FREE_ALL = 23, +}; + +#ifndef NLM_PROG +#define NLM_PROG (100021) +#endif + +#define NLM3_netobj_sz (XDR_unsigned_int + XDR_QUADLEN(MAXNETOBJ_SZ)) +#define NLM3_nlm_stats_sz (XDR_int) +#define NLM3_nlm_stat_sz \ + (NLM3_nlm_stats_sz) +#define NLM3_nlm_res_sz \ + (NLM3_netobj_sz + NLM3_nlm_stat_sz) +#define NLM3_nlm_holder_sz \ + (XDR_bool + XDR_int + NLM3_netobj_sz + XDR_unsigned_int + XDR_unsigned_int) +#define NLM3_nlm_testrply_sz \ + (NLM3_nlm_stats_sz + NLM3_nlm_holder_sz) +#define NLM3_nlm_testres_sz \ + (NLM3_netobj_sz + NLM3_nlm_testrply_sz) +#define NLM3_nlm_lock_sz \ + (XDR_unsigned_int + XDR_QUADLEN(LM_MAXSTRLEN) + NLM3_netobj_sz + NLM3_netobj_sz + XDR_int + XDR_unsigned_int + XDR_unsigned_int) +#define NLM3_nlm_lockargs_sz \ + (NLM3_netobj_sz + XDR_bool + XDR_bool + NLM3_nlm_lock_sz + XDR_bool + XDR_int) +#define NLM3_nlm_cancargs_sz \ + (NLM3_netobj_sz + XDR_bool + XDR_bool + NLM3_nlm_lock_sz) +#define NLM3_nlm_testargs_sz \ + (NLM3_netobj_sz + XDR_bool + NLM3_nlm_lock_sz) +#define NLM3_nlm_unlockargs_sz \ + (NLM3_netobj_sz + NLM3_nlm_lock_sz) +#define NLM3_fsh_mode_sz (XDR_int) +#define NLM3_fsh_access_sz (XDR_int) +#define NLM3_nlm_share_sz \ + (XDR_unsigned_int + XDR_QUADLEN(LM_MAXSTRLEN) + NLM3_netobj_sz + NLM3_netobj_sz + NLM3_fsh_mode_sz + NLM3_fsh_access_sz) +#define NLM3_nlm_shareargs_sz \ + (NLM3_netobj_sz + NLM3_nlm_share_sz + XDR_bool) +#define NLM3_nlm_shareres_sz \ + (NLM3_netobj_sz + NLM3_nlm_stats_sz + XDR_int) +#define NLM3_nlm_notify_sz \ + (XDR_unsigned_int + XDR_QUADLEN(LM_MAXNAMELEN) + XDR_long) +#define NLM3_nlm_notifyargs_sz \ + (NLM3_nlm_notify_sz + XDR_QUADLEN(SM_PRIV_SIZE)) +#define NLM3_MAX_ARGS_SZ \ + (NLM3_nlm_lockargs_sz) + +#endif /* _LINUX_XDRGEN_NLM3_DEF_H */ From b3ef4d4688ebb78ed46d9c3adc6f2b82f1e05721 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:42 -0400 Subject: [PATCH 055/106] lockd: Rename struct nlm_cookie to lockd_cookie Machine-generated XDR types derived from the NLM specification use names that match the protocol. Internal lockd types with identical names cause compilation failures when machine-generated encoders replace hand-coded ones. Rename the internal struct nlm_cookie type to lockd_cookie to prevent such collisions. The "lockd_" prefix distinguishes implementation-specific types from specified NLM protocol types. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/clnt4xdr.c | 4 ++-- fs/lockd/clntproc.c | 2 +- fs/lockd/clntxdr.c | 4 ++-- fs/lockd/lockd.h | 6 +++--- fs/lockd/svc4proc.c | 6 +++--- fs/lockd/svclock.c | 16 ++++++++-------- fs/lockd/svcxdr.h | 4 ++-- fs/lockd/xdr.h | 7 +++---- 8 files changed, 24 insertions(+), 25 deletions(-) diff --git a/fs/lockd/clnt4xdr.c b/fs/lockd/clnt4xdr.c index 2058733eacf8..6d881f9702a9 100644 --- a/fs/lockd/clnt4xdr.c +++ b/fs/lockd/clnt4xdr.c @@ -132,13 +132,13 @@ static int decode_netobj(struct xdr_stream *xdr, * netobj cookie; */ static void encode_cookie(struct xdr_stream *xdr, - const struct nlm_cookie *cookie) + const struct lockd_cookie *cookie) { encode_netobj(xdr, (u8 *)&cookie->data, cookie->len); } static int decode_cookie(struct xdr_stream *xdr, - struct nlm_cookie *cookie) + struct lockd_cookie *cookie) { u32 length; __be32 *p; diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index 7f211008a5d2..50cfab2f31c7 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c @@ -42,7 +42,7 @@ static const struct rpc_call_ops nlmclnt_cancel_ops; */ static atomic_t nlm_cookie = ATOMIC_INIT(0x1234); -void nlmclnt_next_cookie(struct nlm_cookie *c) +void nlmclnt_next_cookie(struct lockd_cookie *c) { u32 cookie = atomic_inc_return(&nlm_cookie); diff --git a/fs/lockd/clntxdr.c b/fs/lockd/clntxdr.c index 65555f5224b1..2a4d28847254 100644 --- a/fs/lockd/clntxdr.c +++ b/fs/lockd/clntxdr.c @@ -130,13 +130,13 @@ static int decode_netobj(struct xdr_stream *xdr, * netobj cookie; */ static void encode_cookie(struct xdr_stream *xdr, - const struct nlm_cookie *cookie) + const struct lockd_cookie *cookie) { encode_netobj(xdr, (u8 *)&cookie->data, cookie->len); } static int decode_cookie(struct xdr_stream *xdr, - struct nlm_cookie *cookie) + struct lockd_cookie *cookie) { u32 length; __be32 *p; diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index 1db6cb352542..119818568507 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -257,7 +257,7 @@ __be32 nlmclnt_grant(const struct sockaddr *addr, void nlmclnt_recovery(struct nlm_host *); int nlmclnt_reclaim(struct nlm_host *, struct file_lock *, struct nlm_rqst *); -void nlmclnt_next_cookie(struct nlm_cookie *); +void nlmclnt_next_cookie(struct lockd_cookie *); #ifdef CONFIG_LOCKD_V4 extern const struct rpc_version nlm_version4; @@ -314,7 +314,7 @@ typedef int (*nlm_host_match_fn_t)(void *cur, struct nlm_host *ref); int lock_to_openmode(struct file_lock *); __be32 nlmsvc_lock(struct svc_rqst *, struct nlm_file *, struct nlm_host *, struct nlm_lock *, int, - struct nlm_cookie *, int); + struct lockd_cookie *, int); __be32 nlmsvc_unlock(struct net *net, struct nlm_file *, struct nlm_lock *); __be32 nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, struct nlm_host *host, struct nlm_lock *lock, @@ -323,7 +323,7 @@ __be32 nlmsvc_cancel_blocked(struct net *net, struct nlm_file *, struct nlm_l void nlmsvc_retry_blocked(struct svc_rqst *rqstp); void nlmsvc_traverse_blocks(struct nlm_host *, struct nlm_file *, nlm_host_match_fn_t match); -void nlmsvc_grant_reply(struct nlm_cookie *, __be32); +void nlmsvc_grant_reply(struct lockd_cookie *, __be32); void nlmsvc_release_call(struct nlm_rqst *); void nlmsvc_locks_init_private(struct file_lock *, struct nlm_host *, pid_t); int nlmsvc_dispatch(struct svc_rqst *rqstp); diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index e3a6d69c1fa6..249ef49f1bcd 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -39,7 +39,7 @@ static_assert(offsetof(struct nlm4_testargs_wrapper, xdrgen) == 0); struct nlm4_lockargs_wrapper { struct nlm4_lockargs xdrgen; - struct nlm_cookie cookie; + struct lockd_cookie cookie; struct nlm_lock lock; }; @@ -88,7 +88,7 @@ static_assert(offsetof(struct nlm4_testres_wrapper, xdrgen) == 0); struct nlm4_res_wrapper { struct nlm4_res xdrgen; - struct nlm_cookie cookie; + struct lockd_cookie cookie; }; static_assert(offsetof(struct nlm4_res_wrapper, xdrgen) == 0); @@ -100,7 +100,7 @@ struct nlm4_shareres_wrapper { static_assert(offsetof(struct nlm4_shareres_wrapper, xdrgen) == 0); static __be32 -nlm4_netobj_to_cookie(struct nlm_cookie *cookie, netobj *object) +nlm4_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) { if (object->len > NLM_MAXCOOKIELEN) return nlm_lck_denied_nolocks; diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c index f4520149d6d7..7fb03042ebee 100644 --- a/fs/lockd/svclock.c +++ b/fs/lockd/svclock.c @@ -48,7 +48,7 @@ static LIST_HEAD(nlm_blocked); static DEFINE_SPINLOCK(nlm_blocked_lock); #if IS_ENABLED(CONFIG_SUNRPC_DEBUG) -static const char *nlmdbg_cookie2a(const struct nlm_cookie *cookie) +static const char *nlmdbg_cookie2a(const struct lockd_cookie *cookie) { /* * We can get away with a static buffer because this is only called @@ -75,7 +75,7 @@ static const char *nlmdbg_cookie2a(const struct nlm_cookie *cookie) return buf; } #else -static inline const char *nlmdbg_cookie2a(const struct nlm_cookie *cookie) +static inline const char *nlmdbg_cookie2a(const struct lockd_cookie *cookie) { return "???"; } @@ -171,7 +171,7 @@ nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock) return NULL; } -static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b) +static int lockd_cookie_match(struct lockd_cookie *a, struct lockd_cookie *b) { if (a->len != b->len) return 0; @@ -184,13 +184,13 @@ static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b) * Find a block with a given NLM cookie. */ static inline struct nlm_block * -nlmsvc_find_block(struct nlm_cookie *cookie) +nlmsvc_find_block(struct lockd_cookie *cookie) { struct nlm_block *block; spin_lock(&nlm_blocked_lock); list_for_each_entry(block, &nlm_blocked, b_list) { - if (nlm_cookie_match(&block->b_call->a_args.cookie,cookie)) + if (lockd_cookie_match(&block->b_call->a_args.cookie, cookie)) goto found; } spin_unlock(&nlm_blocked_lock); @@ -222,7 +222,7 @@ nlmsvc_find_block(struct nlm_cookie *cookie) static struct nlm_block * nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_host *host, struct nlm_file *file, struct nlm_lock *lock, - struct nlm_cookie *cookie) + struct lockd_cookie *cookie) { struct nlm_block *block; struct nlm_rqst *call = NULL; @@ -477,7 +477,7 @@ nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block) __be32 nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file, struct nlm_host *host, struct nlm_lock *lock, int wait, - struct nlm_cookie *cookie, int reclaim) + struct lockd_cookie *cookie, int reclaim) { struct inode *inode __maybe_unused = nlmsvc_file_inode(file); struct nlm_block *block = NULL; @@ -982,7 +982,7 @@ static const struct rpc_call_ops nlmsvc_grant_ops = { * block. */ void -nlmsvc_grant_reply(struct nlm_cookie *cookie, __be32 status) +nlmsvc_grant_reply(struct lockd_cookie *cookie, __be32 status) { struct nlm_block *block; struct file_lock *fl; diff --git a/fs/lockd/svcxdr.h b/fs/lockd/svcxdr.h index 4f1a451da5ba..911b5fd707b1 100644 --- a/fs/lockd/svcxdr.h +++ b/fs/lockd/svcxdr.h @@ -70,7 +70,7 @@ svcxdr_decode_string(struct xdr_stream *xdr, char **data, unsigned int *data_len * specially. */ static inline bool -svcxdr_decode_cookie(struct xdr_stream *xdr, struct nlm_cookie *cookie) +svcxdr_decode_cookie(struct xdr_stream *xdr, struct lockd_cookie *cookie) { __be32 *p; u32 len; @@ -98,7 +98,7 @@ svcxdr_decode_cookie(struct xdr_stream *xdr, struct nlm_cookie *cookie) } static inline bool -svcxdr_encode_cookie(struct xdr_stream *xdr, const struct nlm_cookie *cookie) +svcxdr_encode_cookie(struct xdr_stream *xdr, const struct lockd_cookie *cookie) { __be32 *p; diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h index 3c60817c4349..c7e0518862d7 100644 --- a/fs/lockd/xdr.h +++ b/fs/lockd/xdr.h @@ -49,8 +49,7 @@ struct nlm_lock { * 32 bytes. */ -struct nlm_cookie -{ +struct lockd_cookie { unsigned char data[NLM_MAXCOOKIELEN]; unsigned int len; }; @@ -59,7 +58,7 @@ struct nlm_cookie * Generic lockd arguments for all but sm_notify */ struct nlm_args { - struct nlm_cookie cookie; + struct lockd_cookie cookie; struct nlm_lock lock; u32 block; u32 reclaim; @@ -73,7 +72,7 @@ struct nlm_args { * Generic lockd result */ struct nlm_res { - struct nlm_cookie cookie; + struct lockd_cookie cookie; __be32 status; struct nlm_lock lock; }; From ef66678dd48bf4f266ae70562df65813a041e29a Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:43 -0400 Subject: [PATCH 056/106] lockd: Rename struct nlm_lock to lockd_lock A subsequent patch will convert fs/lockd/svcproc.c to use machine-generated XDR encoding and decoding functions in a manner similar to fs/lockd/svc4proc.c. Machine-generated types derived from the NLM specification will conflict with the internal types of the same name. Rename the internal struct nlm_lock type to lockd_lock to avoid such naming conflicts. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/clnt4xdr.c | 16 ++++++++-------- fs/lockd/clntlock.c | 2 +- fs/lockd/clntproc.c | 2 +- fs/lockd/clntxdr.c | 16 ++++++++-------- fs/lockd/lockd.h | 16 ++++++++-------- fs/lockd/svc4proc.c | 30 +++++++++++++++--------------- fs/lockd/svclock.c | 22 +++++++++++----------- fs/lockd/svcproc.c | 2 +- fs/lockd/svcsubs.c | 2 +- fs/lockd/trace.h | 4 ++-- fs/lockd/xdr.c | 8 ++++---- fs/lockd/xdr.h | 6 +++--- 12 files changed, 63 insertions(+), 63 deletions(-) diff --git a/fs/lockd/clnt4xdr.c b/fs/lockd/clnt4xdr.c index 6d881f9702a9..8973711264cb 100644 --- a/fs/lockd/clnt4xdr.c +++ b/fs/lockd/clnt4xdr.c @@ -63,7 +63,7 @@ static s64 loff_t_to_s64(loff_t offset) return res; } -static void nlm4_compute_offsets(const struct nlm_lock *lock, +static void nlm4_compute_offsets(const struct lockd_lock *lock, u64 *l_offset, u64 *l_len) { const struct file_lock *fl = &lock->fl; @@ -240,7 +240,7 @@ static int decode_nlm4_stat(struct xdr_stream *xdr, __be32 *stat) static void encode_nlm4_holder(struct xdr_stream *xdr, const struct nlm_res *result) { - const struct nlm_lock *lock = &result->lock; + const struct lockd_lock *lock = &result->lock; u64 l_offset, l_len; __be32 *p; @@ -256,7 +256,7 @@ static void encode_nlm4_holder(struct xdr_stream *xdr, static int decode_nlm4_holder(struct xdr_stream *xdr, struct nlm_res *result) { - struct nlm_lock *lock = &result->lock; + struct lockd_lock *lock = &result->lock; struct file_lock *fl = &lock->fl; u64 l_offset, l_len; u32 exclusive; @@ -317,7 +317,7 @@ static void encode_caller_name(struct xdr_stream *xdr, const char *name) * }; */ static void encode_nlm4_lock(struct xdr_stream *xdr, - const struct nlm_lock *lock) + const struct lockd_lock *lock) { u64 l_offset, l_len; __be32 *p; @@ -355,7 +355,7 @@ static void nlm4_xdr_enc_testargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK); @@ -377,7 +377,7 @@ static void nlm4_xdr_enc_lockargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_bool(xdr, args->block); @@ -400,7 +400,7 @@ static void nlm4_xdr_enc_cancargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_bool(xdr, args->block); @@ -419,7 +419,7 @@ static void nlm4_xdr_enc_unlockargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_nlm4_lock(xdr, lock); diff --git a/fs/lockd/clntlock.c b/fs/lockd/clntlock.c index 8fa30c42c92a..f797cc99f94d 100644 --- a/fs/lockd/clntlock.c +++ b/fs/lockd/clntlock.c @@ -158,7 +158,7 @@ int nlmclnt_wait(struct nlm_wait *block, struct nlm_rqst *req, long timeout) /* * The server lockd has called us back to tell us the lock was granted */ -__be32 nlmclnt_grant(const struct sockaddr *addr, const struct nlm_lock *lock) +__be32 nlmclnt_grant(const struct sockaddr *addr, const struct lockd_lock *lock) { const struct file_lock *fl = &lock->fl; const struct nfs_fh *fh = &lock->fh; diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index 50cfab2f31c7..1aa6597ae0b7 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c @@ -129,7 +129,7 @@ static struct nlm_lockowner *nlmclnt_find_lockowner(struct nlm_host *host, fl_ow static void nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl) { struct nlm_args *argp = &req->a_args; - struct nlm_lock *lock = &argp->lock; + struct lockd_lock *lock = &argp->lock; char *nodename = req->a_host->h_rpcclnt->cl_nodename; nlmclnt_next_cookie(&argp->cookie); diff --git a/fs/lockd/clntxdr.c b/fs/lockd/clntxdr.c index 2a4d28847254..efa45f12960d 100644 --- a/fs/lockd/clntxdr.c +++ b/fs/lockd/clntxdr.c @@ -60,7 +60,7 @@ static s32 loff_t_to_s32(loff_t offset) return res; } -static void nlm_compute_offsets(const struct nlm_lock *lock, +static void nlm_compute_offsets(const struct lockd_lock *lock, u32 *l_offset, u32 *l_len) { const struct file_lock *fl = &lock->fl; @@ -236,7 +236,7 @@ static int decode_nlm_stat(struct xdr_stream *xdr, static void encode_nlm_holder(struct xdr_stream *xdr, const struct nlm_res *result) { - const struct nlm_lock *lock = &result->lock; + const struct lockd_lock *lock = &result->lock; u32 l_offset, l_len; __be32 *p; @@ -252,7 +252,7 @@ static void encode_nlm_holder(struct xdr_stream *xdr, static int decode_nlm_holder(struct xdr_stream *xdr, struct nlm_res *result) { - struct nlm_lock *lock = &result->lock; + struct lockd_lock *lock = &result->lock; struct file_lock *fl = &lock->fl; u32 exclusive, l_offset, l_len; int error; @@ -319,7 +319,7 @@ static void encode_caller_name(struct xdr_stream *xdr, const char *name) * }; */ static void encode_nlm_lock(struct xdr_stream *xdr, - const struct nlm_lock *lock) + const struct lockd_lock *lock) { u32 l_offset, l_len; __be32 *p; @@ -356,7 +356,7 @@ static void nlm_xdr_enc_testargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_bool(xdr, lock->fl.c.flc_type == F_WRLCK); @@ -378,7 +378,7 @@ static void nlm_xdr_enc_lockargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_bool(xdr, args->block); @@ -401,7 +401,7 @@ static void nlm_xdr_enc_cancargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_bool(xdr, args->block); @@ -420,7 +420,7 @@ static void nlm_xdr_enc_unlockargs(struct rpc_rqst *req, const void *data) { const struct nlm_args *args = data; - const struct nlm_lock *lock = &args->lock; + const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); encode_nlm_lock(xdr, lock); diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index 119818568507..032790834c7e 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -253,7 +253,7 @@ void nlmclnt_queue_block(struct nlm_wait *block); __be32 nlmclnt_dequeue_block(struct nlm_wait *block); int nlmclnt_wait(struct nlm_wait *block, struct nlm_rqst *req, long timeout); __be32 nlmclnt_grant(const struct sockaddr *addr, - const struct nlm_lock *lock); + const struct lockd_lock *lock); void nlmclnt_recovery(struct nlm_host *); int nlmclnt_reclaim(struct nlm_host *, struct file_lock *, struct nlm_rqst *); @@ -313,13 +313,13 @@ typedef int (*nlm_host_match_fn_t)(void *cur, struct nlm_host *ref); */ int lock_to_openmode(struct file_lock *); __be32 nlmsvc_lock(struct svc_rqst *, struct nlm_file *, - struct nlm_host *, struct nlm_lock *, int, + struct nlm_host *, struct lockd_lock *, int, struct lockd_cookie *, int); -__be32 nlmsvc_unlock(struct net *net, struct nlm_file *, struct nlm_lock *); +__be32 nlmsvc_unlock(struct net *net, struct nlm_file *, struct lockd_lock *); __be32 nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, - struct nlm_host *host, struct nlm_lock *lock, - struct nlm_lock *conflock); -__be32 nlmsvc_cancel_blocked(struct net *net, struct nlm_file *, struct nlm_lock *); + struct nlm_host *host, struct lockd_lock *lock, + struct lockd_lock *conflock); +__be32 nlmsvc_cancel_blocked(struct net *net, struct nlm_file *, struct lockd_lock *); void nlmsvc_retry_blocked(struct svc_rqst *rqstp); void nlmsvc_traverse_blocks(struct nlm_host *, struct nlm_file *, nlm_host_match_fn_t match); @@ -332,10 +332,10 @@ int nlmsvc_dispatch(struct svc_rqst *rqstp); * File handling for the server personality */ __be32 nlm_lookup_file(struct svc_rqst *, struct nlm_file **, - struct nlm_lock *, int); + struct lockd_lock *, int); void nlm_release_file(struct nlm_file *); void nlmsvc_put_lockowner(struct nlm_lockowner *); -void nlmsvc_release_lockowner(struct nlm_lock *); +void nlmsvc_release_lockowner(struct lockd_lock *); void nlmsvc_mark_resources(struct net *); void nlmsvc_free_host_resources(struct nlm_host *); void nlmsvc_invalidate_all(void); diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 249ef49f1bcd..f7067fae6c86 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -26,13 +26,13 @@ #include "nlm4xdr_gen.h" /* - * Wrapper structures combine xdrgen types with legacy nlm_lock. + * Wrapper structures combine xdrgen types with legacy lockd_lock. * The xdrgen field must be first so the structure can be cast * to its XDR type for the RPC dispatch layer. */ struct nlm4_testargs_wrapper { struct nlm4_testargs xdrgen; - struct nlm_lock lock; + struct lockd_lock lock; }; static_assert(offsetof(struct nlm4_testargs_wrapper, xdrgen) == 0); @@ -40,21 +40,21 @@ static_assert(offsetof(struct nlm4_testargs_wrapper, xdrgen) == 0); struct nlm4_lockargs_wrapper { struct nlm4_lockargs xdrgen; struct lockd_cookie cookie; - struct nlm_lock lock; + struct lockd_lock lock; }; static_assert(offsetof(struct nlm4_lockargs_wrapper, xdrgen) == 0); struct nlm4_cancargs_wrapper { struct nlm4_cancargs xdrgen; - struct nlm_lock lock; + struct lockd_lock lock; }; static_assert(offsetof(struct nlm4_cancargs_wrapper, xdrgen) == 0); struct nlm4_unlockargs_wrapper { struct nlm4_unlockargs xdrgen; - struct nlm_lock lock; + struct lockd_lock lock; }; static_assert(offsetof(struct nlm4_unlockargs_wrapper, xdrgen) == 0); @@ -74,12 +74,12 @@ static_assert(offsetof(struct nlm4_notify_wrapper, xdrgen) == 0); struct nlm4_testres_wrapper { struct nlm4_testres xdrgen; - struct nlm_lock lock; + struct lockd_lock lock; }; struct nlm4_shareargs_wrapper { struct nlm4_shareargs xdrgen; - struct nlm_lock lock; + struct lockd_lock lock; }; static_assert(offsetof(struct nlm4_shareargs_wrapper, xdrgen) == 0); @@ -110,7 +110,7 @@ nlm4_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) } static __be32 -nlm4_lock_to_nlm_lock(struct nlm_lock *lock, struct nlm4_lock *alock) +nlm4_lock_to_lockd_lock(struct lockd_lock *lock, struct nlm4_lock *alock) { if (alock->fh.len > NFS_MAXFHSIZE) return nlm_lck_denied; @@ -142,7 +142,7 @@ nlm4svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored) static __be32 nlm4svc_lookup_file(struct svc_rqst *rqstp, struct nlm_host *host, - struct nlm_lock *lock, struct nlm_file **filp, + struct lockd_lock *lock, struct nlm_file **filp, struct nlm4_lock *xdr_lock, unsigned char type) { bool is_test = (rqstp->rq_proc == NLMPROC4_TEST || @@ -269,7 +269,7 @@ static __be32 nlm4svc_proc_test(struct svc_rqst *rqstp) nlmsvc_release_lockowner(&argp->lock); if (resp->xdrgen.stat.stat == nlm_lck_denied) { - struct nlm_lock *conf = &resp->lock; + struct lockd_lock *conf = &resp->lock; struct nlm4_holder *holder = &resp->xdrgen.stat.u.holder; holder->exclusive = (conf->fl.c.flc_type != F_RDLCK); @@ -527,8 +527,8 @@ nlm4svc_proc_granted(struct svc_rqst *rqstp) resp->xdrgen.cookie = argp->xdrgen.cookie; - resp->xdrgen.stat.stat = nlm4_lock_to_nlm_lock(&argp->lock, - &argp->xdrgen.alock); + resp->xdrgen.stat.stat = nlm4_lock_to_lockd_lock(&argp->lock, + &argp->xdrgen.alock); if (resp->xdrgen.stat.stat) goto out; @@ -842,7 +842,7 @@ __nlm4svc_proc_granted_msg(struct svc_rqst *rqstp, struct nlm_res *resp) if (nlm4_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie)) goto out; - if (nlm4_lock_to_nlm_lock(&argp->lock, &argp->xdrgen.alock)) + if (nlm4_lock_to_lockd_lock(&argp->lock, &argp->xdrgen.alock)) goto out; resp->status = nlmclnt_grant(svc_addr(rqstp), &argp->lock); @@ -982,7 +982,7 @@ static __be32 nlm4svc_proc_share(struct svc_rqst *rqstp) { struct nlm4_shareargs_wrapper *argp = rqstp->rq_argp; struct nlm4_shareres_wrapper *resp = rqstp->rq_resp; - struct nlm_lock *lock = &argp->lock; + struct lockd_lock *lock = &argp->lock; struct nlm_host *host = NULL; struct nlm_file *file = NULL; struct nlm4_lock xdr_lock = { @@ -1050,7 +1050,7 @@ static __be32 nlm4svc_proc_unshare(struct svc_rqst *rqstp) { struct nlm4_shareargs_wrapper *argp = rqstp->rq_argp; struct nlm4_shareres_wrapper *resp = rqstp->rq_resp; - struct nlm_lock *lock = &argp->lock; + struct lockd_lock *lock = &argp->lock; struct nlm4_lock xdr_lock = { .fh = argp->xdrgen.share.fh, .oh = argp->xdrgen.share.oh, diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c index 7fb03042ebee..e48d31f14a65 100644 --- a/fs/lockd/svclock.c +++ b/fs/lockd/svclock.c @@ -37,7 +37,7 @@ static void nlmsvc_release_block(struct nlm_block *block); static void nlmsvc_insert_block(struct nlm_block *block, unsigned long); static void nlmsvc_remove_block(struct nlm_block *block); -static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct nlm_lock *lock); +static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct lockd_lock *lock); static void nlmsvc_freegrantargs(struct nlm_rqst *call); static const struct rpc_call_ops nlmsvc_grant_ops; @@ -142,7 +142,7 @@ nlmsvc_remove_block(struct nlm_block *block) * Find a block for a given lock */ static struct nlm_block * -nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock) +nlmsvc_lookup_block(struct nlm_file *file, struct lockd_lock *lock) { struct nlm_block *block; struct file_lock *fl; @@ -221,7 +221,7 @@ nlmsvc_find_block(struct lockd_cookie *cookie) */ static struct nlm_block * nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_host *host, - struct nlm_file *file, struct nlm_lock *lock, + struct nlm_file *file, struct lockd_lock *lock, struct lockd_cookie *cookie) { struct nlm_block *block; @@ -399,7 +399,7 @@ static struct nlm_lockowner *nlmsvc_find_lockowner(struct nlm_host *host, pid_t } void -nlmsvc_release_lockowner(struct nlm_lock *lock) +nlmsvc_release_lockowner(struct lockd_lock *lock) { if (lock->fl.c.flc_owner) nlmsvc_put_lockowner(lock->fl.c.flc_owner); @@ -415,7 +415,7 @@ void nlmsvc_locks_init_private(struct file_lock *fl, struct nlm_host *host, * Initialize arguments for GRANTED call. The nlm_rqst structure * has been cleared already. */ -static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct nlm_lock *lock) +static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct lockd_lock *lock) { locks_copy_lock(&call->a_args.lock.fl, &lock->fl); memcpy(&call->a_args.lock.fh, &lock->fh, sizeof(call->a_args.lock.fh)); @@ -476,7 +476,7 @@ nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block) */ __be32 nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file, - struct nlm_host *host, struct nlm_lock *lock, int wait, + struct nlm_host *host, struct lockd_lock *lock, int wait, struct lockd_cookie *cookie, int reclaim) { struct inode *inode __maybe_unused = nlmsvc_file_inode(file); @@ -609,8 +609,8 @@ nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file, */ __be32 nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, - struct nlm_host *host, struct nlm_lock *lock, - struct nlm_lock *conflock) + struct nlm_host *host, struct lockd_lock *lock, + struct lockd_lock *conflock) { int error; __be32 ret; @@ -669,7 +669,7 @@ nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, * must be removed. */ __be32 -nlmsvc_unlock(struct net *net, struct nlm_file *file, struct nlm_lock *lock) +nlmsvc_unlock(struct net *net, struct nlm_file *file, struct lockd_lock *lock) { int error = 0; @@ -707,7 +707,7 @@ nlmsvc_unlock(struct net *net, struct nlm_file *file, struct nlm_lock *lock) * The calling procedure must check whether the file can be closed. */ __be32 -nlmsvc_cancel_blocked(struct net *net, struct nlm_file *file, struct nlm_lock *lock) +nlmsvc_cancel_blocked(struct net *net, struct nlm_file *file, struct lockd_lock *lock) { struct nlm_block *block; int status = 0; @@ -848,7 +848,7 @@ static void nlmsvc_grant_blocked(struct nlm_block *block) { struct nlm_file *file = block->b_file; - struct nlm_lock *lock = &block->b_call->a_args.lock; + struct lockd_lock *lock = &block->b_call->a_args.lock; int mode; int error; loff_t fl_start, fl_end; diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 110e186802b6..2e1dbd4e1df9 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -67,7 +67,7 @@ nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, { struct nlm_host *host = NULL; struct nlm_file *file = NULL; - struct nlm_lock *lock = &argp->lock; + struct lockd_lock *lock = &argp->lock; bool is_test = (rqstp->rq_proc == NLMPROC_TEST || rqstp->rq_proc == NLMPROC_TEST_MSG); int mode; diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index 9da9d6e0b42e..e24bacea7e03 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -132,7 +132,7 @@ static __be32 nlm_do_fopen(struct svc_rqst *rqstp, */ __be32 nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, - struct nlm_lock *lock, int mode) + struct lockd_lock *lock, int mode) { struct nlm_file *file; unsigned int hash; diff --git a/fs/lockd/trace.h b/fs/lockd/trace.h index 7214d7e96a42..aa858d9d406d 100644 --- a/fs/lockd/trace.h +++ b/fs/lockd/trace.h @@ -48,7 +48,7 @@ NLM_STATUS_LIST DECLARE_EVENT_CLASS(nlmclnt_lock_event, TP_PROTO( - const struct nlm_lock *lock, + const struct lockd_lock *lock, const struct sockaddr *addr, unsigned int addrlen, __be32 status @@ -87,7 +87,7 @@ DECLARE_EVENT_CLASS(nlmclnt_lock_event, #define DEFINE_NLMCLNT_EVENT(name) \ DEFINE_EVENT(nlmclnt_lock_event, name, \ TP_PROTO( \ - const struct nlm_lock *lock, \ + const struct lockd_lock *lock, \ const struct sockaddr *addr, \ unsigned int addrlen, \ __be32 status \ diff --git a/fs/lockd/xdr.c b/fs/lockd/xdr.c index dfca8b8dab73..55868222984a 100644 --- a/fs/lockd/xdr.c +++ b/fs/lockd/xdr.c @@ -69,7 +69,7 @@ svcxdr_decode_fhandle(struct xdr_stream *xdr, struct nfs_fh *fh) } static bool -svcxdr_decode_lock(struct xdr_stream *xdr, struct nlm_lock *lock) +svcxdr_decode_lock(struct xdr_stream *xdr, struct lockd_lock *lock) { struct file_lock *fl = &lock->fl; s32 start, len, end; @@ -101,7 +101,7 @@ svcxdr_decode_lock(struct xdr_stream *xdr, struct nlm_lock *lock) } static bool -svcxdr_encode_holder(struct xdr_stream *xdr, const struct nlm_lock *lock) +svcxdr_encode_holder(struct xdr_stream *xdr, const struct lockd_lock *lock) { const struct file_lock *fl = &lock->fl; s32 start, len; @@ -271,7 +271,7 @@ bool nlmsvc_decode_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) { struct nlm_args *argp = rqstp->rq_argp; - struct nlm_lock *lock = &argp->lock; + struct lockd_lock *lock = &argp->lock; memset(lock, 0, sizeof(*lock)); locks_init_lock(&lock->fl); @@ -298,7 +298,7 @@ bool nlmsvc_decode_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr) { struct nlm_args *argp = rqstp->rq_argp; - struct nlm_lock *lock = &argp->lock; + struct lockd_lock *lock = &argp->lock; if (!svcxdr_decode_string(xdr, &lock->caller, &lock->len)) return false; diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h index c7e0518862d7..805027d9b0fe 100644 --- a/fs/lockd/xdr.h +++ b/fs/lockd/xdr.h @@ -32,7 +32,7 @@ struct svc_rqst; #define nlm_lck_denied_grace_period cpu_to_be32(NLM_LCK_DENIED_GRACE_PERIOD) /* Lock info passed via NLM */ -struct nlm_lock { +struct lockd_lock { char * caller; unsigned int len; /* length of "caller" */ struct nfs_fh fh; @@ -59,7 +59,7 @@ struct lockd_cookie { */ struct nlm_args { struct lockd_cookie cookie; - struct nlm_lock lock; + struct lockd_lock lock; u32 block; u32 reclaim; u32 state; @@ -74,7 +74,7 @@ struct nlm_args { struct nlm_res { struct lockd_cookie cookie; __be32 status; - struct nlm_lock lock; + struct lockd_lock lock; }; /* From 815778f88b1b9566517ce107b842c0d97254d7ad Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:44 -0400 Subject: [PATCH 057/106] lockd: Rename struct nlm_args to lockd_args As part of the effort to enable lockd's server-side XDR functions to be generated from the NLM protocol specification (using xdrgen), the internal type names must be changed to avoid conflicts with the machine-generated type names. Rename struct nlm_args to struct lockd_args to avoid conflicts with the NLMv3 XDR type definitions that will be introduced when svcproc.c is converted to use xdrgen. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/clnt4xdr.c | 8 ++--- fs/lockd/clntproc.c | 4 +-- fs/lockd/clntxdr.c | 8 ++--- fs/lockd/lockd.h | 2 +- fs/lockd/svcproc.c | 80 ++++++++++++++++++++++----------------------- fs/lockd/xdr.c | 12 +++---- fs/lockd/xdr.h | 2 +- 7 files changed, 58 insertions(+), 58 deletions(-) diff --git a/fs/lockd/clnt4xdr.c b/fs/lockd/clnt4xdr.c index 8973711264cb..d0b08a12fe45 100644 --- a/fs/lockd/clnt4xdr.c +++ b/fs/lockd/clnt4xdr.c @@ -354,7 +354,7 @@ static void nlm4_xdr_enc_testargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); @@ -376,7 +376,7 @@ static void nlm4_xdr_enc_lockargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); @@ -399,7 +399,7 @@ static void nlm4_xdr_enc_cancargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); @@ -418,7 +418,7 @@ static void nlm4_xdr_enc_unlockargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index 1aa6597ae0b7..abdf2a51caf2 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c @@ -128,7 +128,7 @@ static struct nlm_lockowner *nlmclnt_find_lockowner(struct nlm_host *host, fl_ow */ static void nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl) { - struct nlm_args *argp = &req->a_args; + struct lockd_args *argp = &req->a_args; struct lockd_lock *lock = &argp->lock; char *nodename = req->a_host->h_rpcclnt->cl_nodename; @@ -266,7 +266,7 @@ nlmclnt_call(const struct cred *cred, struct nlm_rqst *req, u32 proc) { struct nlm_host *host = req->a_host; struct rpc_clnt *clnt; - struct nlm_args *argp = &req->a_args; + struct lockd_args *argp = &req->a_args; struct nlm_res *resp = &req->a_res; struct rpc_message msg = { .rpc_argp = argp, diff --git a/fs/lockd/clntxdr.c b/fs/lockd/clntxdr.c index efa45f12960d..444a34bc799a 100644 --- a/fs/lockd/clntxdr.c +++ b/fs/lockd/clntxdr.c @@ -355,7 +355,7 @@ static void nlm_xdr_enc_testargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); @@ -377,7 +377,7 @@ static void nlm_xdr_enc_lockargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); @@ -400,7 +400,7 @@ static void nlm_xdr_enc_cancargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); @@ -419,7 +419,7 @@ static void nlm_xdr_enc_unlockargs(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_args *args = data; + const struct lockd_args *args = data; const struct lockd_lock *lock = &args->lock; encode_cookie(xdr, &args->cookie); diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index 032790834c7e..a97676639d3e 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -171,7 +171,7 @@ struct nlm_rqst { refcount_t a_count; unsigned int a_flags; /* initial RPC task flags */ struct nlm_host * a_host; /* host handle */ - struct nlm_args a_args; /* arguments */ + struct lockd_args a_args; /* arguments */ struct nlm_res a_res; /* result */ struct nlm_block * a_block; unsigned int a_retries; /* Retry count */ diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 2e1dbd4e1df9..8a49b864f6ee 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -62,7 +62,7 @@ static inline __be32 cast_status(__be32 status) * Obtain client and file from arguments */ static __be32 -nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, +nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct lockd_args *argp, struct nlm_host **hostp, struct nlm_file **filp) { struct nlm_host *host = NULL; @@ -136,7 +136,7 @@ nlmsvc_proc_null(struct svc_rqst *rqstp) static __be32 __nlmsvc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; struct nlm_file *file; __be32 rc = rpc_success; @@ -173,7 +173,7 @@ nlmsvc_proc_test(struct svc_rqst *rqstp) static __be32 __nlmsvc_proc_lock(struct svc_rqst *rqstp, struct nlm_res *resp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; struct nlm_file *file; __be32 rc = rpc_success; @@ -211,7 +211,7 @@ nlmsvc_proc_lock(struct svc_rqst *rqstp) static __be32 __nlmsvc_proc_cancel(struct svc_rqst *rqstp, struct nlm_res *resp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; struct nlm_file *file; struct net *net = SVC_NET(rqstp); @@ -253,7 +253,7 @@ nlmsvc_proc_cancel(struct svc_rqst *rqstp) static __be32 __nlmsvc_proc_unlock(struct svc_rqst *rqstp, struct nlm_res *resp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; struct nlm_file *file; struct net *net = SVC_NET(rqstp); @@ -296,7 +296,7 @@ nlmsvc_proc_unlock(struct svc_rqst *rqstp) static __be32 __nlmsvc_proc_granted(struct svc_rqst *rqstp, struct nlm_res *resp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; resp->cookie = argp->cookie; @@ -345,7 +345,7 @@ static const struct rpc_call_ops nlmsvc_callback_ops = { static __be32 nlmsvc_callback(struct svc_rqst *rqstp, u32 proc, __be32 (*func)(struct svc_rqst *, struct nlm_res *)) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; struct nlm_rqst *call; __be32 stat; @@ -411,7 +411,7 @@ nlmsvc_proc_granted_msg(struct svc_rqst *rqstp) static __be32 nlmsvc_proc_share(struct svc_rqst *rqstp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_res *resp = rqstp->rq_resp; struct nlm_host *host; struct nlm_file *file; @@ -449,7 +449,7 @@ nlmsvc_proc_share(struct svc_rqst *rqstp) static __be32 nlmsvc_proc_unshare(struct svc_rqst *rqstp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_res *resp = rqstp->rq_resp; struct nlm_host *host; struct nlm_file *file; @@ -486,7 +486,7 @@ nlmsvc_proc_unshare(struct svc_rqst *rqstp) static __be32 nlmsvc_proc_nm_lock(struct svc_rqst *rqstp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; dprintk("lockd: NM_LOCK called\n"); @@ -500,7 +500,7 @@ nlmsvc_proc_nm_lock(struct svc_rqst *rqstp) static __be32 nlmsvc_proc_free_all(struct svc_rqst *rqstp) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; /* Obtain client */ @@ -582,8 +582,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_test, .pc_decode = nlmsvc_decode_testargs, .pc_encode = nlmsvc_encode_testres, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St+2+No+Rg, .pc_name = "TEST", @@ -592,8 +592,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_lock, .pc_decode = nlmsvc_decode_lockargs, .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St, .pc_name = "LOCK", @@ -602,8 +602,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_cancel, .pc_decode = nlmsvc_decode_cancargs, .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St, .pc_name = "CANCEL", @@ -612,8 +612,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_unlock, .pc_decode = nlmsvc_decode_unlockargs, .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St, .pc_name = "UNLOCK", @@ -622,8 +622,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_granted, .pc_decode = nlmsvc_decode_testargs, .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St, .pc_name = "GRANTED", @@ -632,8 +632,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_test_msg, .pc_decode = nlmsvc_decode_testargs, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "TEST_MSG", @@ -642,8 +642,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_lock_msg, .pc_decode = nlmsvc_decode_lockargs, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "LOCK_MSG", @@ -652,8 +652,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_cancel_msg, .pc_decode = nlmsvc_decode_cancargs, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "CANCEL_MSG", @@ -662,8 +662,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_unlock_msg, .pc_decode = nlmsvc_decode_unlockargs, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "UNLOCK_MSG", @@ -672,8 +672,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_granted_msg, .pc_decode = nlmsvc_decode_testargs, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "GRANTED_MSG", @@ -772,8 +772,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_share, .pc_decode = nlmsvc_decode_shareargs, .pc_encode = nlmsvc_encode_shareres, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St+1, .pc_name = "SHARE", @@ -782,8 +782,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_unshare, .pc_decode = nlmsvc_decode_shareargs, .pc_encode = nlmsvc_encode_shareres, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St+1, .pc_name = "UNSHARE", @@ -792,8 +792,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_nm_lock, .pc_decode = nlmsvc_decode_lockargs, .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_res), .pc_xdrressize = Ck+St, .pc_name = "NM_LOCK", @@ -802,8 +802,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_free_all, .pc_decode = nlmsvc_decode_notify, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_args), - .pc_argzero = sizeof(struct nlm_args), + .pc_argsize = sizeof(struct lockd_args), + .pc_argzero = sizeof(struct lockd_args), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = 0, .pc_name = "FREE_ALL", @@ -814,7 +814,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { * Storage requirements for XDR arguments and results */ union nlmsvc_xdrstore { - struct nlm_args args; + struct lockd_args args; struct nlm_res res; struct nlm_reboot reboot; }; diff --git a/fs/lockd/xdr.c b/fs/lockd/xdr.c index 55868222984a..0130cdea5642 100644 --- a/fs/lockd/xdr.c +++ b/fs/lockd/xdr.c @@ -154,7 +154,7 @@ nlmsvc_decode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; u32 exclusive; if (!svcxdr_decode_cookie(xdr, &argp->cookie)) @@ -172,7 +172,7 @@ nlmsvc_decode_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; u32 exclusive; if (!svcxdr_decode_cookie(xdr, &argp->cookie)) @@ -197,7 +197,7 @@ nlmsvc_decode_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; u32 exclusive; if (!svcxdr_decode_cookie(xdr, &argp->cookie)) @@ -217,7 +217,7 @@ nlmsvc_decode_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; if (!svcxdr_decode_cookie(xdr, &argp->cookie)) return false; @@ -270,7 +270,7 @@ nlmsvc_decode_reboot(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct lockd_lock *lock = &argp->lock; memset(lock, 0, sizeof(*lock)); @@ -297,7 +297,7 @@ nlmsvc_decode_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_args *argp = rqstp->rq_argp; + struct lockd_args *argp = rqstp->rq_argp; struct lockd_lock *lock = &argp->lock; if (!svcxdr_decode_string(xdr, &lock->caller, &lock->len)) diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h index 805027d9b0fe..ffdcad0f8680 100644 --- a/fs/lockd/xdr.h +++ b/fs/lockd/xdr.h @@ -57,7 +57,7 @@ struct lockd_cookie { /* * Generic lockd arguments for all but sm_notify */ -struct nlm_args { +struct lockd_args { struct lockd_cookie cookie; struct lockd_lock lock; u32 block; From 86ed2898fafb8106072f9db3887deec80222e6cf Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:45 -0400 Subject: [PATCH 058/106] lockd: Rename struct nlm_res to lockd_res As part of the effort to enable lockd's server-side XDR functions to be generated from the NLM protocol specification (using xdrgen), the internal type names must be changed to avoid conflicts with the machine-generated type names. Rename struct nlm_res to struct lockd_res to avoid conflicts with the NLMv3 XDR type definitions that will be introduced when svcproc.c is converted to use xdrgen. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/clnt4xdr.c | 14 ++++++------ fs/lockd/clntproc.c | 6 ++--- fs/lockd/clntxdr.c | 16 ++++++------- fs/lockd/lockd.h | 2 +- fs/lockd/svc4proc.c | 12 +++++----- fs/lockd/svcproc.c | 56 ++++++++++++++++++++++----------------------- fs/lockd/xdr.c | 10 ++++---- fs/lockd/xdr.h | 2 +- 8 files changed, 59 insertions(+), 59 deletions(-) diff --git a/fs/lockd/clnt4xdr.c b/fs/lockd/clnt4xdr.c index d0b08a12fe45..96a4a1e6a6b6 100644 --- a/fs/lockd/clnt4xdr.c +++ b/fs/lockd/clnt4xdr.c @@ -238,7 +238,7 @@ static int decode_nlm4_stat(struct xdr_stream *xdr, __be32 *stat) * }; */ static void encode_nlm4_holder(struct xdr_stream *xdr, - const struct nlm_res *result) + const struct lockd_res *result) { const struct lockd_lock *lock = &result->lock; u64 l_offset, l_len; @@ -254,7 +254,7 @@ static void encode_nlm4_holder(struct xdr_stream *xdr, xdr_encode_hyper(p, l_len); } -static int decode_nlm4_holder(struct xdr_stream *xdr, struct nlm_res *result) +static int decode_nlm4_holder(struct xdr_stream *xdr, struct lockd_res *result) { struct lockd_lock *lock = &result->lock; struct file_lock *fl = &lock->fl; @@ -435,7 +435,7 @@ static void nlm4_xdr_enc_res(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_res *result = data; + const struct lockd_res *result = data; encode_cookie(xdr, &result->cookie); encode_nlm4_stat(xdr, result->status); @@ -458,7 +458,7 @@ static void nlm4_xdr_enc_testres(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_res *result = data; + const struct lockd_res *result = data; encode_cookie(xdr, &result->cookie); encode_nlm4_stat(xdr, result->status); @@ -489,7 +489,7 @@ static void nlm4_xdr_enc_testres(struct rpc_rqst *req, * }; */ static int decode_nlm4_testrply(struct xdr_stream *xdr, - struct nlm_res *result) + struct lockd_res *result) { int error; @@ -506,7 +506,7 @@ static int nlm4_xdr_dec_testres(struct rpc_rqst *req, struct xdr_stream *xdr, void *data) { - struct nlm_res *result = data; + struct lockd_res *result = data; int error; error = decode_cookie(xdr, &result->cookie); @@ -527,7 +527,7 @@ static int nlm4_xdr_dec_res(struct rpc_rqst *req, struct xdr_stream *xdr, void *data) { - struct nlm_res *result = data; + struct lockd_res *result = data; int error; error = decode_cookie(xdr, &result->cookie); diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index abdf2a51caf2..f06faf577cea 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c @@ -267,7 +267,7 @@ nlmclnt_call(const struct cred *cred, struct nlm_rqst *req, u32 proc) struct nlm_host *host = req->a_host; struct rpc_clnt *clnt; struct lockd_args *argp = &req->a_args; - struct nlm_res *resp = &req->a_res; + struct lockd_res *resp = &req->a_res; struct rpc_message msg = { .rpc_argp = argp, .rpc_resp = resp, @@ -523,7 +523,7 @@ nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl) { const struct cred *cred = nfs_file_cred(fl->c.flc_file); struct nlm_host *host = req->a_host; - struct nlm_res *resp = &req->a_res; + struct lockd_res *resp = &req->a_res; struct nlm_wait block; unsigned char flags = fl->c.flc_flags; unsigned char type; @@ -686,7 +686,7 @@ static int nlmclnt_unlock(struct nlm_rqst *req, struct file_lock *fl) { struct nlm_host *host = req->a_host; - struct nlm_res *resp = &req->a_res; + struct lockd_res *resp = &req->a_res; int status; unsigned char flags = fl->c.flc_flags; diff --git a/fs/lockd/clntxdr.c b/fs/lockd/clntxdr.c index 444a34bc799a..3789ecb0b984 100644 --- a/fs/lockd/clntxdr.c +++ b/fs/lockd/clntxdr.c @@ -234,7 +234,7 @@ static int decode_nlm_stat(struct xdr_stream *xdr, * }; */ static void encode_nlm_holder(struct xdr_stream *xdr, - const struct nlm_res *result) + const struct lockd_res *result) { const struct lockd_lock *lock = &result->lock; u32 l_offset, l_len; @@ -250,7 +250,7 @@ static void encode_nlm_holder(struct xdr_stream *xdr, *p = cpu_to_be32(l_len); } -static int decode_nlm_holder(struct xdr_stream *xdr, struct nlm_res *result) +static int decode_nlm_holder(struct xdr_stream *xdr, struct lockd_res *result) { struct lockd_lock *lock = &result->lock; struct file_lock *fl = &lock->fl; @@ -436,7 +436,7 @@ static void nlm_xdr_enc_res(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_res *result = data; + const struct lockd_res *result = data; encode_cookie(xdr, &result->cookie); encode_nlm_stat(xdr, result->status); @@ -456,7 +456,7 @@ static void nlm_xdr_enc_res(struct rpc_rqst *req, * }; */ static void encode_nlm_testrply(struct xdr_stream *xdr, - const struct nlm_res *result) + const struct lockd_res *result) { if (result->status == nlm_lck_denied) encode_nlm_holder(xdr, result); @@ -466,7 +466,7 @@ static void nlm_xdr_enc_testres(struct rpc_rqst *req, struct xdr_stream *xdr, const void *data) { - const struct nlm_res *result = data; + const struct lockd_res *result = data; encode_cookie(xdr, &result->cookie); encode_nlm_stat(xdr, result->status); @@ -495,7 +495,7 @@ static void nlm_xdr_enc_testres(struct rpc_rqst *req, * }; */ static int decode_nlm_testrply(struct xdr_stream *xdr, - struct nlm_res *result) + struct lockd_res *result) { int error; @@ -512,7 +512,7 @@ static int nlm_xdr_dec_testres(struct rpc_rqst *req, struct xdr_stream *xdr, void *data) { - struct nlm_res *result = data; + struct lockd_res *result = data; int error; error = decode_cookie(xdr, &result->cookie); @@ -533,7 +533,7 @@ static int nlm_xdr_dec_res(struct rpc_rqst *req, struct xdr_stream *xdr, void *data) { - struct nlm_res *result = data; + struct lockd_res *result = data; int error; error = decode_cookie(xdr, &result->cookie); diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index a97676639d3e..4054e97723d8 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -172,7 +172,7 @@ struct nlm_rqst { unsigned int a_flags; /* initial RPC task flags */ struct nlm_host * a_host; /* host handle */ struct lockd_args a_args; /* arguments */ - struct nlm_res a_res; /* result */ + struct lockd_res a_res; /* result */ struct nlm_block * a_block; unsigned int a_retries; /* Retry count */ u8 a_owner[NLMCLNT_OHSIZE]; diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index f7067fae6c86..1682a7c91a78 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -562,7 +562,7 @@ static const struct rpc_call_ops nlm4svc_callback_ops = { */ static __be32 nlm4svc_callback(struct svc_rqst *rqstp, struct nlm_host *host, u32 proc, - __be32 (*func)(struct svc_rqst *, struct nlm_res *)) + __be32 (*func)(struct svc_rqst *, struct lockd_res *)) { struct nlm_rqst *call; __be32 stat; @@ -585,7 +585,7 @@ nlm4svc_callback(struct svc_rqst *rqstp, struct nlm_host *host, u32 proc, } static __be32 -__nlm4svc_proc_test_msg(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlm4svc_proc_test_msg(struct svc_rqst *rqstp, struct lockd_res *resp) { struct nlm4_testargs_wrapper *argp = rqstp->rq_argp; unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; @@ -645,7 +645,7 @@ static __be32 nlm4svc_proc_test_msg(struct svc_rqst *rqstp) } static __be32 -__nlm4svc_proc_lock_msg(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlm4svc_proc_lock_msg(struct svc_rqst *rqstp, struct lockd_res *resp) { struct nlm4_lockargs_wrapper *argp = rqstp->rq_argp; unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; @@ -707,7 +707,7 @@ static __be32 nlm4svc_proc_lock_msg(struct svc_rqst *rqstp) } static __be32 -__nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp, struct lockd_res *resp) { struct nlm4_cancargs_wrapper *argp = rqstp->rq_argp; unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; @@ -771,7 +771,7 @@ static __be32 nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp) } static __be32 -__nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp, struct lockd_res *resp) { struct nlm4_unlockargs_wrapper *argp = rqstp->rq_argp; struct net *net = SVC_NET(rqstp); @@ -834,7 +834,7 @@ static __be32 nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp) } static __be32 -__nlm4svc_proc_granted_msg(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlm4svc_proc_granted_msg(struct svc_rqst *rqstp, struct lockd_res *resp) { struct nlm4_testargs_wrapper *argp = rqstp->rq_argp; diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 8a49b864f6ee..e033320b840f 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -134,7 +134,7 @@ nlmsvc_proc_null(struct svc_rqst *rqstp) * TEST: Check for conflicting lock */ static __be32 -__nlmsvc_proc_test(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlmsvc_proc_test(struct svc_rqst *rqstp, struct lockd_res *resp) { struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; @@ -171,7 +171,7 @@ nlmsvc_proc_test(struct svc_rqst *rqstp) } static __be32 -__nlmsvc_proc_lock(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlmsvc_proc_lock(struct svc_rqst *rqstp, struct lockd_res *resp) { struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; @@ -209,7 +209,7 @@ nlmsvc_proc_lock(struct svc_rqst *rqstp) } static __be32 -__nlmsvc_proc_cancel(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlmsvc_proc_cancel(struct svc_rqst *rqstp, struct lockd_res *resp) { struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; @@ -251,7 +251,7 @@ nlmsvc_proc_cancel(struct svc_rqst *rqstp) * UNLOCK: release a lock */ static __be32 -__nlmsvc_proc_unlock(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlmsvc_proc_unlock(struct svc_rqst *rqstp, struct lockd_res *resp) { struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; @@ -294,7 +294,7 @@ nlmsvc_proc_unlock(struct svc_rqst *rqstp) * was granted */ static __be32 -__nlmsvc_proc_granted(struct svc_rqst *rqstp, struct nlm_res *resp) +__nlmsvc_proc_granted(struct svc_rqst *rqstp, struct lockd_res *resp) { struct lockd_args *argp = rqstp->rq_argp; @@ -343,7 +343,7 @@ static const struct rpc_call_ops nlmsvc_callback_ops = { * doesn't break any clients. */ static __be32 nlmsvc_callback(struct svc_rqst *rqstp, u32 proc, - __be32 (*func)(struct svc_rqst *, struct nlm_res *)) + __be32 (*func)(struct svc_rqst *, struct lockd_res *)) { struct lockd_args *argp = rqstp->rq_argp; struct nlm_host *host; @@ -412,7 +412,7 @@ static __be32 nlmsvc_proc_share(struct svc_rqst *rqstp) { struct lockd_args *argp = rqstp->rq_argp; - struct nlm_res *resp = rqstp->rq_resp; + struct lockd_res *resp = rqstp->rq_resp; struct nlm_host *host; struct nlm_file *file; @@ -450,7 +450,7 @@ static __be32 nlmsvc_proc_unshare(struct svc_rqst *rqstp) { struct lockd_args *argp = rqstp->rq_argp; - struct nlm_res *resp = rqstp->rq_resp; + struct lockd_res *resp = rqstp->rq_resp; struct nlm_host *host; struct nlm_file *file; @@ -539,7 +539,7 @@ nlmsvc_proc_sm_notify(struct svc_rqst *rqstp) static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp) { - struct nlm_res *argp = rqstp->rq_argp; + struct lockd_res *argp = rqstp->rq_argp; if (!nlmsvc_ops) return rpc_success; @@ -584,7 +584,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_testres, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St+2+No+Rg, .pc_name = "TEST", }, @@ -594,7 +594,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_res, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St, .pc_name = "LOCK", }, @@ -604,7 +604,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_res, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St, .pc_name = "CANCEL", }, @@ -614,7 +614,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_res, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St, .pc_name = "UNLOCK", }, @@ -624,7 +624,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_res, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St, .pc_name = "GRANTED", }, @@ -682,8 +682,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_null, .pc_decode = nlmsvc_decode_void, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_res), - .pc_argzero = sizeof(struct nlm_res), + .pc_argsize = sizeof(struct lockd_res), + .pc_argzero = sizeof(struct lockd_res), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "TEST_RES", @@ -692,8 +692,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_null, .pc_decode = nlmsvc_decode_void, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_res), - .pc_argzero = sizeof(struct nlm_res), + .pc_argsize = sizeof(struct lockd_res), + .pc_argzero = sizeof(struct lockd_res), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "LOCK_RES", @@ -702,8 +702,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_null, .pc_decode = nlmsvc_decode_void, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_res), - .pc_argzero = sizeof(struct nlm_res), + .pc_argsize = sizeof(struct lockd_res), + .pc_argzero = sizeof(struct lockd_res), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "CANCEL_RES", @@ -712,8 +712,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_null, .pc_decode = nlmsvc_decode_void, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_res), - .pc_argzero = sizeof(struct nlm_res), + .pc_argsize = sizeof(struct lockd_res), + .pc_argzero = sizeof(struct lockd_res), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "UNLOCK_RES", @@ -722,8 +722,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_granted_res, .pc_decode = nlmsvc_decode_res, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_res), - .pc_argzero = sizeof(struct nlm_res), + .pc_argsize = sizeof(struct lockd_res), + .pc_argzero = sizeof(struct lockd_res), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "GRANTED_RES", @@ -774,7 +774,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_shareres, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St+1, .pc_name = "SHARE", }, @@ -784,7 +784,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_shareres, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St+1, .pc_name = "UNSHARE", }, @@ -794,7 +794,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_encode = nlmsvc_encode_res, .pc_argsize = sizeof(struct lockd_args), .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_res), + .pc_ressize = sizeof(struct lockd_res), .pc_xdrressize = Ck+St, .pc_name = "NM_LOCK", }, @@ -815,7 +815,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { */ union nlmsvc_xdrstore { struct lockd_args args; - struct nlm_res res; + struct lockd_res res; struct nlm_reboot reboot; }; diff --git a/fs/lockd/xdr.c b/fs/lockd/xdr.c index 0130cdea5642..bcf65152a436 100644 --- a/fs/lockd/xdr.c +++ b/fs/lockd/xdr.c @@ -127,7 +127,7 @@ svcxdr_encode_holder(struct xdr_stream *xdr, const struct lockd_lock *lock) } static bool -svcxdr_encode_testrply(struct xdr_stream *xdr, const struct nlm_res *resp) +svcxdr_encode_testrply(struct xdr_stream *xdr, const struct lockd_res *resp) { if (!svcxdr_encode_stats(xdr, resp->status)) return false; @@ -231,7 +231,7 @@ nlmsvc_decode_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_res *resp = rqstp->rq_argp; + struct lockd_res *resp = rqstp->rq_argp; if (!svcxdr_decode_cookie(xdr, &resp->cookie)) return false; @@ -322,7 +322,7 @@ nlmsvc_encode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_encode_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_res *resp = rqstp->rq_resp; + struct lockd_res *resp = rqstp->rq_resp; return svcxdr_encode_cookie(xdr, &resp->cookie) && svcxdr_encode_testrply(xdr, resp); @@ -331,7 +331,7 @@ nlmsvc_encode_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_encode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_res *resp = rqstp->rq_resp; + struct lockd_res *resp = rqstp->rq_resp; return svcxdr_encode_cookie(xdr, &resp->cookie) && svcxdr_encode_stats(xdr, resp->status); @@ -340,7 +340,7 @@ nlmsvc_encode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_encode_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_res *resp = rqstp->rq_resp; + struct lockd_res *resp = rqstp->rq_resp; if (!svcxdr_encode_cookie(xdr, &resp->cookie)) return false; diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h index ffdcad0f8680..a480df7cae31 100644 --- a/fs/lockd/xdr.h +++ b/fs/lockd/xdr.h @@ -71,7 +71,7 @@ struct lockd_args { /* * Generic lockd result */ -struct nlm_res { +struct lockd_res { struct lockd_cookie cookie; __be32 status; struct lockd_lock lock; From c03ded750fc6ec545bdff338fdb0594d82aaefce Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:46 -0400 Subject: [PATCH 059/106] lockd: Rename struct nlm_reboot to lockd_reboot As part of the effort to enable lockd's server-side XDR functions to be generated from the NLM protocol specification (using xdrgen), the internal type names must be changed to avoid conflicts with the machine-generated type names. Rename struct nlm_reboot to struct lockd_reboot for consistency with the other renamed internal types. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/host.c | 4 ++-- fs/lockd/lockd.h | 4 ++-- fs/lockd/mon.c | 2 +- fs/lockd/svc4proc.c | 4 ++-- fs/lockd/svcproc.c | 8 ++++---- fs/lockd/xdr.c | 2 +- fs/lockd/xdr.h | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/fs/lockd/host.c b/fs/lockd/host.c index ea8a8e166f7e..d572cb27533f 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -552,7 +552,7 @@ struct nlm_host * nlm_get_host(struct nlm_host *host) static struct nlm_host *next_host_state(struct hlist_head *cache, struct nsm_handle *nsm, - const struct nlm_reboot *info) + const struct lockd_reboot *info) { struct nlm_host *host; struct hlist_head *chain; @@ -582,7 +582,7 @@ static struct nlm_host *next_host_state(struct hlist_head *cache, * We were notified that the specified host has rebooted. Release * all resources held by that peer. */ -void nlm_host_rebooted(const struct net *net, const struct nlm_reboot *info) +void nlm_host_rebooted(const struct net *net, const struct lockd_reboot *info) { struct nsm_handle *nsm; struct nlm_host *host; diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index 4054e97723d8..ca389525a170 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -285,7 +285,7 @@ struct nlm_host * nlm_get_host(struct nlm_host *); void nlm_shutdown_hosts(void); void nlm_shutdown_hosts_net(struct net *net); void nlm_host_rebooted(const struct net *net, - const struct nlm_reboot *); + const struct lockd_reboot *); /* * Host monitoring @@ -299,7 +299,7 @@ struct nsm_handle *nsm_get_handle(const struct net *net, const char *hostname, const size_t hostname_len); struct nsm_handle *nsm_reboot_lookup(const struct net *net, - const struct nlm_reboot *info); + const struct lockd_reboot *info); void nsm_release(struct nsm_handle *nsm); /* diff --git a/fs/lockd/mon.c b/fs/lockd/mon.c index 3d3ee88ca4dc..a8f5ac6f0577 100644 --- a/fs/lockd/mon.c +++ b/fs/lockd/mon.c @@ -377,7 +377,7 @@ struct nsm_handle *nsm_get_handle(const struct net *net, * error occurred. */ struct nsm_handle *nsm_reboot_lookup(const struct net *net, - const struct nlm_reboot *info) + const struct lockd_reboot *info) { struct nsm_handle *cached; struct lockd_net *ln = net_generic(net, lockd_net_id); diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 1682a7c91a78..997f4f437997 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -61,7 +61,7 @@ static_assert(offsetof(struct nlm4_unlockargs_wrapper, xdrgen) == 0); struct nlm4_notifyargs_wrapper { struct nlm4_notifyargs xdrgen; - struct nlm_reboot reboot; + struct lockd_reboot reboot; }; static_assert(offsetof(struct nlm4_notifyargs_wrapper, xdrgen) == 0); @@ -918,7 +918,7 @@ static __be32 nlm4svc_proc_granted_res(struct svc_rqst *rqstp) static __be32 nlm4svc_proc_sm_notify(struct svc_rqst *rqstp) { struct nlm4_notifyargs_wrapper *argp = rqstp->rq_argp; - struct nlm_reboot *reboot = &argp->reboot; + struct lockd_reboot *reboot = &argp->reboot; if (!nlm_privileged_requester(rqstp)) { char buf[RPC_MAX_ADDRBUFLEN]; diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index e033320b840f..a79c9a46db60 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -518,7 +518,7 @@ nlmsvc_proc_free_all(struct svc_rqst *rqstp) static __be32 nlmsvc_proc_sm_notify(struct svc_rqst *rqstp) { - struct nlm_reboot *argp = rqstp->rq_argp; + struct lockd_reboot *argp = rqstp->rq_argp; dprintk("lockd: SM_NOTIFY called\n"); @@ -732,8 +732,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_func = nlmsvc_proc_sm_notify, .pc_decode = nlmsvc_decode_reboot, .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_reboot), - .pc_argzero = sizeof(struct nlm_reboot), + .pc_argsize = sizeof(struct lockd_reboot), + .pc_argzero = sizeof(struct lockd_reboot), .pc_ressize = sizeof(struct nlm_void), .pc_xdrressize = St, .pc_name = "SM_NOTIFY", @@ -816,7 +816,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { union nlmsvc_xdrstore { struct lockd_args args; struct lockd_res res; - struct nlm_reboot reboot; + struct lockd_reboot reboot; }; /* diff --git a/fs/lockd/xdr.c b/fs/lockd/xdr.c index bcf65152a436..c78c64557fea 100644 --- a/fs/lockd/xdr.c +++ b/fs/lockd/xdr.c @@ -244,7 +244,7 @@ nlmsvc_decode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) bool nlmsvc_decode_reboot(struct svc_rqst *rqstp, struct xdr_stream *xdr) { - struct nlm_reboot *argp = rqstp->rq_argp; + struct lockd_reboot *argp = rqstp->rq_argp; __be32 *p; u32 len; diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h index a480df7cae31..65d2d6d34310 100644 --- a/fs/lockd/xdr.h +++ b/fs/lockd/xdr.h @@ -80,7 +80,7 @@ struct lockd_res { /* * statd callback when client has rebooted */ -struct nlm_reboot { +struct lockd_reboot { char *mon; unsigned int len; u32 state; From 94e8b24e7daaa50e3af9c66b3cc6a67c942df875 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:47 -0400 Subject: [PATCH 060/106] lockd: Rename struct nlm_share to lockd_share As part of the effort to enable lockd's server-side XDR functions to be generated from the NLM protocol specification (using xdrgen), the internal type names must be changed to avoid conflicts with the machine-generated type names. Rename struct nlm_share to struct lockd_share to avoid conflicts with the NLMv3 XDR type definitions that will be introduced when svcproc.c is converted to use xdrgen. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/lockd.h | 4 ++-- fs/lockd/share.h | 4 ++-- fs/lockd/svcshare.c | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index ca389525a170..5c79681b7e95 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -179,7 +179,7 @@ struct nlm_rqst { void * a_callback_data; /* sent to nlmclnt_operations callbacks */ }; -struct nlm_share; +struct lockd_share; /* * This struct describes a file held open by lockd on behalf of @@ -190,7 +190,7 @@ struct nlm_file { struct nfs_fh f_handle; /* NFS file handle */ struct file * f_file[2]; /* VFS file pointers, indexed by O_ flags */ - struct nlm_share * f_shares; /* DOS shares */ + struct lockd_share * f_shares; /* DOS shares */ struct list_head f_blocks; /* blocked locks */ unsigned int f_locks; /* guesstimate # of locks */ unsigned int f_count; /* reference count */ diff --git a/fs/lockd/share.h b/fs/lockd/share.h index 20ea8ee49168..1ec3ccdb2aef 100644 --- a/fs/lockd/share.h +++ b/fs/lockd/share.h @@ -14,8 +14,8 @@ /* * DOS share for a specific file */ -struct nlm_share { - struct nlm_share * s_next; /* linked list */ +struct lockd_share { + struct lockd_share * s_next; /* linked list */ struct nlm_host * s_host; /* client host */ struct nlm_file * s_file; /* shared file */ struct xdr_netobj s_owner; /* owner handle */ diff --git a/fs/lockd/svcshare.c b/fs/lockd/svcshare.c index 53f5655c128c..5ac0ec25d62d 100644 --- a/fs/lockd/svcshare.c +++ b/fs/lockd/svcshare.c @@ -19,7 +19,7 @@ #include "share.h" static inline int -nlm_cmp_owner(struct nlm_share *share, struct xdr_netobj *oh) +nlm_cmp_owner(struct lockd_share *share, struct xdr_netobj *oh) { return share->s_owner.len == oh->len && !memcmp(share->s_owner.data, oh->data, oh->len); @@ -39,7 +39,7 @@ __be32 nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file, struct xdr_netobj *oh, u32 access, u32 mode) { - struct nlm_share *share; + struct lockd_share *share; u8 *ohdata; if (nlmsvc_file_cannot_lock(file)) @@ -85,7 +85,7 @@ __be32 nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file, struct xdr_netobj *oh) { - struct nlm_share *share, **shpp; + struct lockd_share *share, **shpp; if (nlmsvc_file_cannot_lock(file)) return nlm_lck_denied_nolocks; @@ -111,7 +111,7 @@ nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file, void nlmsvc_traverse_shares(struct nlm_host *host, struct nlm_file *file, nlm_host_match_fn_t match) { - struct nlm_share *share, **shpp; + struct lockd_share *share, **shpp; shpp = &file->f_shares; while ((share = *shpp) != NULL) { From 94c281d5e98921b7bfe5c9090f6355a30fffaa65 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:48 -0400 Subject: [PATCH 061/106] lockd: Use xdrgen XDR functions for the NLMv3 NULL procedure Hand-written XDR encoders and decoders are difficult to maintain and can diverge from protocol specifications. Migrating to xdrgen-generated code improves type safety and ensures the implementation matches the NLM version 3 protocol specification exactly. Convert the NULL procedure to use nlm_svc_decode_void and nlm_svc_encode_void, generated from Documentation/sunrpc/xdr/nlm3.x. NULL has no arguments or results, so it is the first procedure converted. NULL returns no XDR-encoded data, so pc_xdrressize is set to XDR_void. The argzero field is also set to zero since xdrgen decoders initialize all decoded values. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index a79c9a46db60..ad37f3611eea 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -13,7 +13,16 @@ #include #include "lockd.h" + +/* + * xdr.h defines SM_PRIV_SIZE as a macro. nlm3xdr_gen.h defines + * it as an enum constant. Undefine the macro before including + * the generated header. + */ +#undef SM_PRIV_SIZE + #include "share.h" +#include "nlm3xdr_gen.h" #define NLMDBG_FACILITY NLMDBG_CLIENT @@ -120,13 +129,18 @@ nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct lockd_args *argp, return nlm_lck_denied_nolocks; } -/* - * NULL: Test for presence of service +/** + * nlmsvc_proc_null - NULL: Test for presence of service + * @rqstp: RPC transaction context + * + * Return: + * %rpc_success: RPC executed successfully + * + * RPC synopsis: + * void NLM_NULL(void) = 0; */ -static __be32 -nlmsvc_proc_null(struct svc_rqst *rqstp) +static __be32 nlmsvc_proc_null(struct svc_rqst *rqstp) { - dprintk("lockd: NULL called\n"); return rpc_success; } @@ -568,15 +582,15 @@ struct nlm_void { int dummy; }; #define Rg 2 /* range - offset + size */ static const struct svc_procedure nlmsvc_procedures[24] = { - [NLMPROC_NULL] = { - .pc_func = nlmsvc_proc_null, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_void), - .pc_argzero = sizeof(struct nlm_void), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "NULL", + [NLM_NULL] = { + .pc_func = nlmsvc_proc_null, + .pc_decode = nlm_svc_decode_void, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = XDR_void, + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "NULL", }, [NLMPROC_TEST] = { .pc_func = nlmsvc_proc_test, From 3031fd999e2d144f865b660cec819179c9a20e06 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:49 -0400 Subject: [PATCH 062/106] lockd: Use xdrgen XDR functions for the NLMv3 TEST procedure The NLM TEST procedure requires host and file lookups to check lock state, operations that will be common across multiple NLM procedures being migrated to xdrgen. Introducing the nlm3svc_lookup_host() and nlm3svc_lookup_file() helpers now keeps these common patterns in one place for subsequent conversions in this series. This patch converts the TEST procedure to use xdrgen functions nlm_svc_decode_nlm_testargs and nlm_svc_encode_nlm_testres generated from the NLM version 3 protocol specification. The procedure handler is rewritten to use xdrgen types through wrapper structures that bridge between generated code and the legacy lockd_lock representation still used by the core lockd logic. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in nlm3svc_lookup_file() rather than relying on zero-initialization. The conflicting holder's offset and length are saturated to NLM_OFFSET_MAX when constructing the reply. A conflicting lock established by an NLMv4 client or by a local process can sit beyond the NLMv3 signed 32-bit range, and copying fl_start and fl_end straight into the unsigned 32-bit XDR fields would wrap and report a bogus range. The previous hand-written encoder in svcxdr_encode_holder() used loff_t_to_s32() for the same reason, but this patch series intends to separate the concerns of data conversion (XDR) from dealing with local byte range constraints, so clamping is hoisted into the proc function. The previous hand-written decoder in svcxdr_decode_cookie() rewrote a zero-length NLM cookie into a four-byte zero cookie, with a comment attributing the substitution to HP-UX clients. The xdrgen-generated netobj decoder performs no such rewrite, so a zero-length request cookie now round-trips unchanged into the reply. HP-UX has reached end of support, and NLM_TEST reply matching relies on the RPC XID rather than the NLM cookie, so the workaround is dropped intentionally here. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/lockd.h | 23 ++++++ fs/lockd/svcproc.c | 195 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 206 insertions(+), 12 deletions(-) diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index 5c79681b7e95..0be0dac59ea2 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -422,6 +422,29 @@ static inline int nlm_compare_locks(const struct file_lock *fl1, &&(fl1->c.flc_type == fl2->c.flc_type || fl2->c.flc_type == F_UNLCK); } +/** + * lockd_set_file_lock_range3 - set the byte range of a file_lock + * @fl: file_lock whose length fields are to be initialized + * @off: starting offset of the lock, in bytes + * @len: length of the byte range, in bytes, or zero + * + * NLMv3 uses a (start, length) representation for lock byte ranges, + * while the kernel's file_lock uses (start, end). Treat a length of + * zero or arithmetic overflow (end wrapping negative when the sum + * exceeds S32_MAX) as "lock to end of file." + */ +static inline void +lockd_set_file_lock_range3(struct file_lock *fl, u32 off, u32 len) +{ + s32 end = off + len - 1; + + fl->fl_start = off; + if (len == 0 || end < 0) + fl->fl_end = OFFSET_MAX; + else + fl->fl_end = end; +} + /** * lockd_set_file_lock_range4 - set the byte range of a file_lock * @fl: file_lock whose length fields are to be initialized diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index ad37f3611eea..7794e6f88a71 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -26,6 +26,106 @@ #define NLMDBG_FACILITY NLMDBG_CLIENT +/* + * Size of an NFSv2 file handle, in bytes, which is 32. + * Defined locally to avoid including uapi/linux/nfs2.h. + */ +#define NLM3_FHSIZE 32 + +/* + * Wrapper structures combine xdrgen types with legacy lockd_lock. + * The xdrgen field must be first so the structure can be cast + * to its XDR type for the RPC dispatch layer. + */ +struct nlm_testargs_wrapper { + struct nlm_testargs xdrgen; + struct lockd_lock lock; +}; + +static_assert(offsetof(struct nlm_testargs_wrapper, xdrgen) == 0); + +struct nlm_testres_wrapper { + struct nlm_testres xdrgen; + struct lockd_lock lock; +}; + +static_assert(offsetof(struct nlm_testres_wrapper, xdrgen) == 0); + +static struct nlm_host * +nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored) +{ + struct nlm_host *host; + + if (!nlmsvc_ops) + return NULL; + host = nlmsvc_lookup_host(rqstp, caller.data, caller.len); + if (!host) + return NULL; + if (monitored && nsm_monitor(host) < 0) { + nlmsvc_release_host(host); + return NULL; + } + return host; +} + +static __be32 +nlm3svc_lookup_file(struct svc_rqst *rqstp, struct nlm_host *host, + struct lockd_lock *lock, struct nlm_file **filp, + struct nlm_lock *xdr_lock, unsigned char type) +{ + bool is_test = (rqstp->rq_proc == NLMPROC_TEST || + rqstp->rq_proc == NLMPROC_TEST_MSG); + struct file_lock *fl = &lock->fl; + struct nlm_file *file = NULL; + __be32 error; + int mode; + + if (xdr_lock->fh.len != NLM3_FHSIZE) + return nlm_lck_denied_nolocks; + lock->fh.size = xdr_lock->fh.len; + memcpy(lock->fh.data, xdr_lock->fh.data, xdr_lock->fh.len); + + lock->oh.len = xdr_lock->oh.len; + lock->oh.data = xdr_lock->oh.data; + + lock->svid = xdr_lock->uppid; + lock->lock_start = xdr_lock->l_offset; + lock->lock_len = xdr_lock->l_len; + + locks_init_lock(fl); + fl->c.flc_type = type; + lockd_set_file_lock_range3(fl, lock->lock_start, lock->lock_len); + + mode = lock_to_openmode(fl); + if (is_test) + mode = O_RDWR; + + error = nlm_lookup_file(rqstp, &file, lock, mode); + switch (error) { + case nlm_granted: + break; + case nlm__int__stale_fh: + case nlm__int__failed: + return nlm_lck_denied_nolocks; + default: + return error; + } + *filp = file; + + fl->c.flc_flags = FL_POSIX; + if (is_test) + fl->c.flc_file = nlmsvc_file_file(file); + else + fl->c.flc_file = file->f_file[mode]; + fl->c.flc_pid = current->tgid; + fl->fl_lmops = &nlmsvc_lock_operations; + nlmsvc_locks_init_private(fl, host, (pid_t)lock->svid); + if (!fl->c.flc_owner) + return nlm_lck_denied_nolocks; + + return nlm_granted; +} + #ifdef CONFIG_LOCKD_V4 static inline __be32 cast_status(__be32 status) { @@ -178,10 +278,79 @@ __nlmsvc_proc_test(struct svc_rqst *rqstp, struct lockd_res *resp) return rc; } -static __be32 -nlmsvc_proc_test(struct svc_rqst *rqstp) +/** + * nlmsvc_proc_test - TEST: Check for conflicting lock + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * + * RPC synopsis: + * nlm_testres NLM_TEST(nlm_testargs) = 1; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The server would be able to grant the + * requested lock. + * %LCK_DENIED: The requested lock conflicted with existing + * lock reservations for the file. + * %LCK_DENIED_NOLOCKS: The server could not allocate the resources + * needed to process the request. + * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is + * re-establishing existing locks, and is not + * yet ready to accept normal service requests. + */ +static __be32 nlmsvc_proc_test(struct svc_rqst *rqstp) { - return __nlmsvc_proc_test(rqstp, rqstp->rq_resp); + struct nlm_testargs_wrapper *argp = rqstp->rq_argp; + unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; + struct nlm_testres_wrapper *resp = rqstp->rq_resp; + struct nlm_file *file = NULL; + struct nlm_host *host; + + resp->xdrgen.cookie = argp->xdrgen.cookie; + + resp->xdrgen.test_stat.stat = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); + if (!host) + goto out; + + resp->xdrgen.test_stat.stat = + nlm3svc_lookup_file(rqstp, host, &argp->lock, &file, + &argp->xdrgen.alock, type); + if (resp->xdrgen.test_stat.stat) + goto out; + + resp->xdrgen.test_stat.stat = + cast_status(nlmsvc_testlock(rqstp, file, host, &argp->lock, + &resp->lock)); + nlmsvc_release_lockowner(&argp->lock); + + if (resp->xdrgen.test_stat.stat == nlm_lck_denied) { + struct lockd_lock *conf = &resp->lock; + struct nlm_holder *holder = &resp->xdrgen.test_stat.u.holder; + + holder->exclusive = (conf->fl.c.flc_type != F_RDLCK); + holder->uppid = conf->svid; + holder->oh.len = conf->oh.len; + holder->oh.data = conf->oh.data; + holder->l_offset = min_t(loff_t, conf->fl.fl_start, + NLM_OFFSET_MAX); + if (conf->fl.fl_end == OFFSET_MAX) + holder->l_len = 0; + else + holder->l_len = min_t(loff_t, + conf->fl.fl_end - + conf->fl.fl_start + 1, + NLM_OFFSET_MAX); + } + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->xdrgen.test_stat.stat == nlm__int__drop_reply ? + rpc_drop_reply : rpc_success; } static __be32 @@ -592,15 +761,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "NULL", }, - [NLMPROC_TEST] = { - .pc_func = nlmsvc_proc_test, - .pc_decode = nlmsvc_decode_testargs, - .pc_encode = nlmsvc_encode_testres, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St+2+No+Rg, - .pc_name = "TEST", + [NLM_TEST] = { + .pc_func = nlmsvc_proc_test, + .pc_decode = nlm_svc_decode_nlm_testargs, + .pc_encode = nlm_svc_encode_nlm_testres, + .pc_argsize = sizeof(struct nlm_testargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_testres_wrapper), + .pc_xdrressize = NLM3_nlm_testres_sz, + .pc_name = "TEST", }, [NLMPROC_LOCK] = { .pc_func = nlmsvc_proc_lock, @@ -828,6 +997,8 @@ static const struct svc_procedure nlmsvc_procedures[24] = { * Storage requirements for XDR arguments and results */ union nlmsvc_xdrstore { + struct nlm_testargs_wrapper testargs; + struct nlm_testres_wrapper testres; struct lockd_args args; struct lockd_res res; struct lockd_reboot reboot; From a77fec7b732905f143e1977ac8614f1845579e72 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:50 -0400 Subject: [PATCH 063/106] lockd: Use xdrgen XDR functions for the NLMv3 LOCK procedure The NLM LOCK procedure requires the same host and file lookup operations established in the TEST procedure conversion. This patch extends the xdrgen migration to the LOCK procedure, leveraging the shared nlm3svc_lookup_host() and nlm3svc_lookup_file() helpers to establish consistent patterns across the series. This patch converts the LOCK procedure to use xdrgen functions nlm_svc_decode_nlm_lockargs and nlm_svc_encode_nlm_res generated from the NLM version 3 protocol specification. The procedure handler uses xdrgen types through wrapper structures that bridge between generated code and the legacy lockd_lock representation still used by the core lockd logic. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The cookie and lock members of the wrapper are populated explicitly in nlm_netobj_to_cookie() and nlm3svc_lookup_file() rather than relying on zero-initialization. The hand-rolled svcxdr_decode_cookie() previously substituted a four-byte zero cookie when a zero-length cookie arrived on the wire, a compatibility shim for HP-UX clients that had been carried in fs/lockd/ since the original import. The xdrgen decoder reproduces the cookie verbatim, and nlm_netobj_to_cookie() copies whatever length the peer sent. As subsequent patches replace the remaining call sites of svcxdr_decode_cookie(), this series retires that HP-UX compat behavior on the server side. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 118 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 12 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 7794e6f88a71..66bc9a2efddb 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -51,6 +51,30 @@ struct nlm_testres_wrapper { static_assert(offsetof(struct nlm_testres_wrapper, xdrgen) == 0); +struct nlm_lockargs_wrapper { + struct nlm_lockargs xdrgen; + struct lockd_cookie cookie; + struct lockd_lock lock; +}; + +static_assert(offsetof(struct nlm_lockargs_wrapper, xdrgen) == 0); + +struct nlm_res_wrapper { + struct nlm_res xdrgen; +}; + +static_assert(offsetof(struct nlm_res_wrapper, xdrgen) == 0); + +static __be32 +nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) +{ + if (object->len > NLM_MAXCOOKIELEN) + return nlm_lck_denied_nolocks; + cookie->len = object->len; + memcpy(cookie->data, object->data, object->len); + return nlm_granted; +} + static struct nlm_host * nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored) { @@ -385,10 +409,79 @@ __nlmsvc_proc_lock(struct svc_rqst *rqstp, struct lockd_res *resp) return rc; } +static __be32 +nlmsvc_do_lock(struct svc_rqst *rqstp, bool monitored) +{ + struct nlm_lockargs_wrapper *argp = rqstp->rq_argp; + unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; + struct nlm_res_wrapper *resp = rqstp->rq_resp; + struct nlm_file *file = NULL; + struct nlm_host *host = NULL; + + resp->xdrgen.cookie = argp->xdrgen.cookie; + + resp->xdrgen.stat.stat = nlm_netobj_to_cookie(&argp->cookie, + &argp->xdrgen.cookie); + if (resp->xdrgen.stat.stat) + goto out; + + resp->xdrgen.stat.stat = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, + monitored); + if (!host) + goto out; + + resp->xdrgen.stat.stat = nlm3svc_lookup_file(rqstp, host, &argp->lock, + &file, &argp->xdrgen.alock, + type); + if (resp->xdrgen.stat.stat) + goto out; + + resp->xdrgen.stat.stat = cast_status(nlmsvc_lock(rqstp, file, host, + &argp->lock, + argp->xdrgen.block, + &argp->cookie, + argp->xdrgen.reclaim)); + + nlmsvc_release_lockowner(&argp->lock); + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->xdrgen.stat.stat == nlm__int__drop_reply ? + rpc_drop_reply : rpc_success; +} + +/** + * nlmsvc_proc_lock - LOCK: Establish a monitored lock + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * + * RPC synopsis: + * nlm_res NLM_LOCK(nlm_lockargs) = 2; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The requested lock was granted. + * %LCK_DENIED: The requested lock conflicted with existing + * lock reservations for the file. + * %LCK_DENIED_NOLOCKS: The server could not allocate the resources + * needed to process the request. + * %LCK_BLOCKED: The blocking request cannot be granted + * immediately. The server will send an + * NLM_GRANTED callback to the client when + * the lock can be granted. + * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is + * re-establishing existing locks, and is not + * yet ready to accept normal service requests. + */ static __be32 nlmsvc_proc_lock(struct svc_rqst *rqstp) { - return __nlmsvc_proc_lock(rqstp, rqstp->rq_resp); + return nlmsvc_do_lock(rqstp, true); } static __be32 @@ -674,7 +767,7 @@ nlmsvc_proc_nm_lock(struct svc_rqst *rqstp) dprintk("lockd: NM_LOCK called\n"); argp->monitor = 0; /* just clean the monitor flag */ - return nlmsvc_proc_lock(rqstp); + return __nlmsvc_proc_lock(rqstp, rqstp->rq_resp); } /* @@ -771,15 +864,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_testres_sz, .pc_name = "TEST", }, - [NLMPROC_LOCK] = { - .pc_func = nlmsvc_proc_lock, - .pc_decode = nlmsvc_decode_lockargs, - .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St, - .pc_name = "LOCK", + [NLM_LOCK] = { + .pc_func = nlmsvc_proc_lock, + .pc_decode = nlm_svc_decode_nlm_lockargs, + .pc_encode = nlm_svc_encode_nlm_res, + .pc_argsize = sizeof(struct nlm_lockargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_res_wrapper), + .pc_xdrressize = NLM3_nlm_res_sz, + .pc_name = "LOCK", }, [NLMPROC_CANCEL] = { .pc_func = nlmsvc_proc_cancel, @@ -998,9 +1091,10 @@ static const struct svc_procedure nlmsvc_procedures[24] = { */ union nlmsvc_xdrstore { struct nlm_testargs_wrapper testargs; + struct nlm_lockargs_wrapper lockargs; struct nlm_testres_wrapper testres; + struct nlm_res_wrapper res; struct lockd_args args; - struct lockd_res res; struct lockd_reboot reboot; }; From 954a3f3f6cff4144f9bfafb676089250f57b9b00 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:51 -0400 Subject: [PATCH 064/106] lockd: Use xdrgen XDR functions for the NLMv3 CANCEL procedure The NLM CANCEL procedure allows clients to cancel outstanding blocked lock requests. This patch continues the xdrgen migration by converting the CANCEL procedure. CANCEL reuses the nlm3svc_lookup_host() and nlm3svc_lookup_file() helpers established in the TEST procedure conversion. This patch converts the CANCEL procedure to use xdrgen functions nlm_svc_decode_nlm_cancargs and nlm_svc_encode_nlm_res generated from the NLM version 3 protocol specification. The procedure handler uses xdrgen types through a wrapper structure that bridges between generated code and the legacy lockd_lock representation still used by the core lockd logic. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in nlm3svc_lookup_file() rather than relying on zero-initialization. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 81 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 66bc9a2efddb..a5e40ca3f109 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -65,6 +65,13 @@ struct nlm_res_wrapper { static_assert(offsetof(struct nlm_res_wrapper, xdrgen) == 0); +struct nlm_cancargs_wrapper { + struct nlm_cancargs xdrgen; + struct lockd_lock lock; +}; + +static_assert(offsetof(struct nlm_cancargs_wrapper, xdrgen) == 0); + static __be32 nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) { @@ -517,10 +524,63 @@ __nlmsvc_proc_cancel(struct svc_rqst *rqstp, struct lockd_res *resp) return rpc_success; } +/** + * nlmsvc_proc_cancel - CANCEL: Cancel an outstanding blocked lock request + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * + * RPC synopsis: + * nlm_res NLM_CANCEL(nlm_cancargs) = 3; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The requested lock was canceled. + * %LCK_DENIED: There was no lock to cancel. + * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is + * re-establishing existing locks, and is not + * yet ready to accept normal service requests. + * + * The Linux NLM server implementation also returns: + * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated. + */ static __be32 nlmsvc_proc_cancel(struct svc_rqst *rqstp) { - return __nlmsvc_proc_cancel(rqstp, rqstp->rq_resp); + struct nlm_cancargs_wrapper *argp = rqstp->rq_argp; + unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; + struct nlm_res_wrapper *resp = rqstp->rq_resp; + struct net *net = SVC_NET(rqstp); + struct nlm_host *host = NULL; + struct nlm_file *file = NULL; + + resp->xdrgen.cookie = argp->xdrgen.cookie; + + resp->xdrgen.stat.stat = nlm_lck_denied_grace_period; + if (locks_in_grace(net)) + goto out; + + resp->xdrgen.stat.stat = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); + if (!host) + goto out; + + resp->xdrgen.stat.stat = nlm3svc_lookup_file(rqstp, host, &argp->lock, + &file, &argp->xdrgen.alock, + type); + if (resp->xdrgen.stat.stat) + goto out; + + resp->xdrgen.stat.stat = nlmsvc_cancel_blocked(net, file, &argp->lock); + nlmsvc_release_lockowner(&argp->lock); + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->xdrgen.stat.stat == nlm__int__drop_reply ? + rpc_drop_reply : rpc_success; } /* @@ -874,15 +934,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_res_sz, .pc_name = "LOCK", }, - [NLMPROC_CANCEL] = { - .pc_func = nlmsvc_proc_cancel, - .pc_decode = nlmsvc_decode_cancargs, - .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St, - .pc_name = "CANCEL", + [NLM_CANCEL] = { + .pc_func = nlmsvc_proc_cancel, + .pc_decode = nlm_svc_decode_nlm_cancargs, + .pc_encode = nlm_svc_encode_nlm_res, + .pc_argsize = sizeof(struct nlm_cancargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_res_wrapper), + .pc_xdrressize = NLM3_nlm_res_sz, + .pc_name = "CANCEL", }, [NLMPROC_UNLOCK] = { .pc_func = nlmsvc_proc_unlock, @@ -1092,6 +1152,7 @@ static const struct svc_procedure nlmsvc_procedures[24] = { union nlmsvc_xdrstore { struct nlm_testargs_wrapper testargs; struct nlm_lockargs_wrapper lockargs; + struct nlm_cancargs_wrapper cancargs; struct nlm_testres_wrapper testres; struct nlm_res_wrapper res; struct lockd_args args; From 1b0490e6a0849f2f9e9bd901a8a151368f57030f Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:52 -0400 Subject: [PATCH 065/106] lockd: Use xdrgen XDR functions for the NLMv3 UNLOCK procedure The NLM UNLOCK procedure allows clients to release held locks, completing the basic lock lifecycle alongside TEST, LOCK, and CANCEL procedures already converted in this series. Convert UNLOCK to use the xdrgen functions nlm_svc_decode_nlm_unlockargs and nlm_svc_encode_nlm_res generated from the NLM version 3 protocol specification, reusing the nlm3svc_lookup_host() and nlm3svc_lookup_file() helpers introduced earlier in the series. The procedure handler uses xdrgen types through a wrapper structure that bridges between generated code and the legacy lockd_lock representation still used by the core lockd logic. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in nlm3svc_lookup_file() rather than relying on zero-initialization. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 79 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 10 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index a5e40ca3f109..ccc6a633df7b 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -72,6 +72,13 @@ struct nlm_cancargs_wrapper { static_assert(offsetof(struct nlm_cancargs_wrapper, xdrgen) == 0); +struct nlm_unlockargs_wrapper { + struct nlm_unlockargs xdrgen; + struct lockd_lock lock; +}; + +static_assert(offsetof(struct nlm_unlockargs_wrapper, xdrgen) == 0); + static __be32 nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) { @@ -619,10 +626,61 @@ __nlmsvc_proc_unlock(struct svc_rqst *rqstp, struct lockd_res *resp) return rpc_success; } +/** + * nlmsvc_proc_unlock - UNLOCK: Remove a lock + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * + * RPC synopsis: + * nlm_res NLM_UNLOCK(nlm_unlockargs) = 4; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The requested lock was released. + * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is + * re-establishing existing locks, and is not + * yet ready to accept normal service requests. + * + * The Linux NLM server implementation also returns: + * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated. + */ static __be32 nlmsvc_proc_unlock(struct svc_rqst *rqstp) { - return __nlmsvc_proc_unlock(rqstp, rqstp->rq_resp); + struct nlm_unlockargs_wrapper *argp = rqstp->rq_argp; + struct nlm_res_wrapper *resp = rqstp->rq_resp; + struct net *net = SVC_NET(rqstp); + struct nlm_host *host = NULL; + struct nlm_file *file = NULL; + + resp->xdrgen.cookie = argp->xdrgen.cookie; + + resp->xdrgen.stat.stat = nlm_lck_denied_grace_period; + if (locks_in_grace(net)) + goto out; + + resp->xdrgen.stat.stat = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); + if (!host) + goto out; + + resp->xdrgen.stat.stat = nlm3svc_lookup_file(rqstp, host, &argp->lock, + &file, &argp->xdrgen.alock, + F_UNLCK); + if (resp->xdrgen.stat.stat) + goto out; + + resp->xdrgen.stat.stat = nlmsvc_unlock(net, file, &argp->lock); + nlmsvc_release_lockowner(&argp->lock); + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->xdrgen.stat.stat == nlm__int__drop_reply ? + rpc_drop_reply : rpc_success; } /* @@ -944,15 +1002,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_res_sz, .pc_name = "CANCEL", }, - [NLMPROC_UNLOCK] = { - .pc_func = nlmsvc_proc_unlock, - .pc_decode = nlmsvc_decode_unlockargs, - .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St, - .pc_name = "UNLOCK", + [NLM_UNLOCK] = { + .pc_func = nlmsvc_proc_unlock, + .pc_decode = nlm_svc_decode_nlm_unlockargs, + .pc_encode = nlm_svc_encode_nlm_res, + .pc_argsize = sizeof(struct nlm_unlockargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_res_wrapper), + .pc_xdrressize = NLM3_nlm_res_sz, + .pc_name = "UNLOCK", }, [NLMPROC_GRANTED] = { .pc_func = nlmsvc_proc_granted, @@ -1153,6 +1211,7 @@ union nlmsvc_xdrstore { struct nlm_testargs_wrapper testargs; struct nlm_lockargs_wrapper lockargs; struct nlm_cancargs_wrapper cancargs; + struct nlm_unlockargs_wrapper unlockargs; struct nlm_testres_wrapper testres; struct nlm_res_wrapper res; struct lockd_args args; From e04d248b8a1e90df2bbb9c8a06fa34024ee9bdf0 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:53 -0400 Subject: [PATCH 066/106] lockd: Use xdrgen XDR functions for the NLMv3 GRANTED procedure The NLM GRANTED procedure allows servers to notify clients when a previously blocked lock request has been granted, completing the asynchronous lock request flow. This patch converts the NLMv3 GRANTED procedure to use xdrgen-generated XDR functions. The conversion replaces the legacy decoder with the xdrgen functions nlm_svc_decode_nlm_testargs and nlm_svc_encode_nlm_res generated from the NLM version 3 protocol specification. The procedure handler accesses xdrgen types through a wrapper structure that bridges between generated code and the legacy lockd_lock representation still used by the core lockd logic. A new helper function nlm_lock_to_lockd_lock() converts an xdrgen nlm_lock into the legacy lockd_lock format. The helper complements the existing nlm3svc_lookup_host() and nlm3svc_lookup_file() functions used throughout this series. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The helper populates each field of the wrapper's lock member that any downstream consumer reads: fh, oh, svid, and the file_lock byte range. Because pc_argzero no longer scrubs the rq_argp slot, the shared nlmclnt_lock_event tracepoint class is updated to source its byte-range fields from lock->fl.fl_start and lock->fl.fl_end, which both the client and server populate unconditionally; the old lock_start and lock_len fields are no longer required by the trace. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 65 +++++++++++++++++++++++++++++++++++++++------- fs/lockd/trace.h | 12 ++++----- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index ccc6a633df7b..264212e44ec5 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -89,6 +89,20 @@ nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) return nlm_granted; } +static __be32 +nlm_lock_to_lockd_lock(struct lockd_lock *lock, struct nlm_lock *alock) +{ + if (alock->fh.len != NLM3_FHSIZE) + return nlm_lck_denied; + lock->fh.size = alock->fh.len; + memcpy(lock->fh.data, alock->fh.data, alock->fh.len); + lock->oh.len = alock->oh.len; + lock->oh.data = alock->oh.data; + lock->svid = alock->uppid; + lockd_set_file_lock_range3(&lock->fl, alock->l_offset, alock->l_len); + return nlm_granted; +} + static struct nlm_host * nlm3svc_lookup_host(struct svc_rqst *rqstp, string caller, bool monitored) { @@ -700,10 +714,41 @@ __nlmsvc_proc_granted(struct svc_rqst *rqstp, struct lockd_res *resp) return rpc_success; } +/** + * nlmsvc_proc_granted - GRANTED: Blocked lock has been granted + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * + * RPC synopsis: + * nlm_res NLM_GRANTED(nlm_testargs) = 5; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The granted lock was accepted. + * %LCK_DENIED: The procedure failed, possibly due to + * internal resource constraints. + * %LCK_DENIED_GRACE_PERIOD: The client host recently restarted and + * its NLM is re-establishing existing locks, + * so it is not yet ready to accept callbacks. + */ static __be32 nlmsvc_proc_granted(struct svc_rqst *rqstp) { - return __nlmsvc_proc_granted(rqstp, rqstp->rq_resp); + struct nlm_testargs_wrapper *argp = rqstp->rq_argp; + struct nlm_res_wrapper *resp = rqstp->rq_resp; + + resp->xdrgen.cookie = argp->xdrgen.cookie; + + resp->xdrgen.stat.stat = nlm_lock_to_lockd_lock(&argp->lock, + &argp->xdrgen.alock); + if (resp->xdrgen.stat.stat) + goto out; + + resp->xdrgen.stat.stat = nlmclnt_grant(svc_addr(rqstp), &argp->lock); + +out: + return rpc_success; } /* @@ -1012,15 +1057,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_res_sz, .pc_name = "UNLOCK", }, - [NLMPROC_GRANTED] = { - .pc_func = nlmsvc_proc_granted, - .pc_decode = nlmsvc_decode_testargs, - .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St, - .pc_name = "GRANTED", + [NLM_GRANTED] = { + .pc_func = nlmsvc_proc_granted, + .pc_decode = nlm_svc_decode_nlm_testargs, + .pc_encode = nlm_svc_encode_nlm_res, + .pc_argsize = sizeof(struct nlm_testargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_res_wrapper), + .pc_xdrressize = NLM3_nlm_res_sz, + .pc_name = "GRANTED", }, [NLMPROC_TEST_MSG] = { .pc_func = nlmsvc_proc_test_msg, diff --git a/fs/lockd/trace.h b/fs/lockd/trace.h index aa858d9d406d..a11d04e8c835 100644 --- a/fs/lockd/trace.h +++ b/fs/lockd/trace.h @@ -61,8 +61,8 @@ DECLARE_EVENT_CLASS(nlmclnt_lock_event, __field(u32, svid) __field(u32, fh) __field(unsigned long, status) - __field(u64, start) - __field(u64, len) + __field(loff_t, start) + __field(loff_t, end) __sockaddr(addr, addrlen) ), @@ -70,16 +70,16 @@ DECLARE_EVENT_CLASS(nlmclnt_lock_event, __entry->oh = ~crc32_le(0xffffffff, lock->oh.data, lock->oh.len); __entry->svid = lock->svid; __entry->fh = nfs_fhandle_hash(&lock->fh); - __entry->start = lock->lock_start; - __entry->len = lock->lock_len; + __entry->start = lock->fl.fl_start; + __entry->end = lock->fl.fl_end; __entry->status = be32_to_cpu(status); __assign_sockaddr(addr, addr, addrlen); ), TP_printk( - "addr=%pISpc oh=0x%08x svid=0x%08x fh=0x%08x start=%llu len=%llu status=%s", + "addr=%pISpc oh=0x%08x svid=0x%08x fh=0x%08x start=%lld end=%lld status=%s", __get_sockaddr(addr), __entry->oh, __entry->svid, - __entry->fh, __entry->start, __entry->len, + __entry->fh, __entry->start, __entry->end, show_nlm_status(__entry->status) ) ); From 596401c14c7ed86c9e8f4224950f22b6e2cc0a09 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:54 -0400 Subject: [PATCH 067/106] lockd: Refactor nlmsvc_callback() The xdrgen-based XDR conversion requires each RPC procedure to extract its own arguments, since xdrgen generates distinct argument structures for each procedure rather than using a single shared type. Move the host lookup logic from nlmsvc_callback() into each of the five MSG procedure handlers (TEST_MSG, LOCK_MSG, CANCEL_MSG, UNLOCK_MSG, and GRANTED_MSG). Each handler now performs its own host lookup from rqstp->rq_argp and passes the resulting host pointer to nlmsvc_callback(). This establishes the per-procedure argument-handling pattern that the subsequent xdrgen conversion patches require. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 72 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 264212e44ec5..2e6afb9a19b9 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -781,20 +781,13 @@ static const struct rpc_call_ops nlmsvc_callback_ops = { * because we send the callback before the reply proper. I hope this * doesn't break any clients. */ -static __be32 nlmsvc_callback(struct svc_rqst *rqstp, u32 proc, +static __be32 +nlmsvc_callback(struct svc_rqst *rqstp, struct nlm_host *host, u32 proc, __be32 (*func)(struct svc_rqst *, struct lockd_res *)) { - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; struct nlm_rqst *call; __be32 stat; - host = nlmsvc_lookup_host(rqstp, - argp->lock.caller, - argp->lock.len); - if (host == NULL) - return rpc_system_err; - call = nlm_alloc_call(host); nlmsvc_release_host(host); if (call == NULL) @@ -814,34 +807,77 @@ static __be32 nlmsvc_callback(struct svc_rqst *rqstp, u32 proc, static __be32 nlmsvc_proc_test_msg(struct svc_rqst *rqstp) { + struct lockd_args *argp = rqstp->rq_argp; + struct nlm_host *host; + dprintk("lockd: TEST_MSG called\n"); - return nlmsvc_callback(rqstp, NLMPROC_TEST_RES, __nlmsvc_proc_test); + + host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + if (!host) + return rpc_system_err; + + return nlmsvc_callback(rqstp, host, NLMPROC_TEST_RES, + __nlmsvc_proc_test); } static __be32 nlmsvc_proc_lock_msg(struct svc_rqst *rqstp) { + struct lockd_args *argp = rqstp->rq_argp; + struct nlm_host *host; + dprintk("lockd: LOCK_MSG called\n"); - return nlmsvc_callback(rqstp, NLMPROC_LOCK_RES, __nlmsvc_proc_lock); + + host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + if (!host) + return rpc_system_err; + + return nlmsvc_callback(rqstp, host, NLMPROC_LOCK_RES, + __nlmsvc_proc_lock); } static __be32 nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp) { + struct lockd_args *argp = rqstp->rq_argp; + struct nlm_host *host; + dprintk("lockd: CANCEL_MSG called\n"); - return nlmsvc_callback(rqstp, NLMPROC_CANCEL_RES, __nlmsvc_proc_cancel); + + host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + if (!host) + return rpc_system_err; + + return nlmsvc_callback(rqstp, host, NLMPROC_CANCEL_RES, + __nlmsvc_proc_cancel); } -static __be32 -nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp) +static __be32 nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp) { + struct lockd_args *argp = rqstp->rq_argp; + struct nlm_host *host; + dprintk("lockd: UNLOCK_MSG called\n"); - return nlmsvc_callback(rqstp, NLMPROC_UNLOCK_RES, __nlmsvc_proc_unlock); + + host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + if (!host) + return rpc_system_err; + + return nlmsvc_callback(rqstp, host, NLMPROC_UNLOCK_RES, + __nlmsvc_proc_unlock); } -static __be32 -nlmsvc_proc_granted_msg(struct svc_rqst *rqstp) +static __be32 nlmsvc_proc_granted_msg(struct svc_rqst *rqstp) { + struct lockd_args *argp = rqstp->rq_argp; + struct nlm_host *host; + dprintk("lockd: GRANTED_MSG called\n"); - return nlmsvc_callback(rqstp, NLMPROC_GRANTED_RES, __nlmsvc_proc_granted); + + host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + if (!host) + return rpc_system_err; + + return nlmsvc_callback(rqstp, host, NLMPROC_GRANTED_RES, + __nlmsvc_proc_granted); } /* From ba4c62bb35ad5705a2a01d66cb5b6142bff1854e Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:55 -0400 Subject: [PATCH 068/106] lockd: Use xdrgen XDR functions for the NLMv3 TEST_MSG procedure Continue the xdrgen migration by converting NLMv3 TEST_MSG, the async counterpart to TEST that clients use to check lock availability without blocking. The procedure now uses nlm_svc_decode_nlm_testargs and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. The procedure handler reaches the xdrgen types through the nlm_testargs_wrapper structure, which bridges between generated code and the legacy lockd_lock representation. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in nlm3svc_lookup_file() rather than relying on zero-initialization. The NLM async callback mechanism uses client-side functions which continue to take legacy results like struct lockd_res, preventing TEST and TEST_MSG from sharing code for now. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 112 ++++++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 2e6afb9a19b9..cd32cfc62338 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -296,40 +296,6 @@ static __be32 nlmsvc_proc_null(struct svc_rqst *rqstp) return rpc_success; } -/* - * TEST: Check for conflicting lock - */ -static __be32 -__nlmsvc_proc_test(struct svc_rqst *rqstp, struct lockd_res *resp) -{ - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; - struct nlm_file *file; - __be32 rc = rpc_success; - - dprintk("lockd: TEST called\n"); - resp->cookie = argp->cookie; - - /* Obtain client and file */ - if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return resp->status == nlm__int__drop_reply ? - rpc_drop_reply : rpc_success; - - /* Now check for conflicting locks */ - resp->status = cast_status(nlmsvc_testlock(rqstp, file, host, - &argp->lock, &resp->lock)); - if (resp->status == nlm__int__drop_reply) - rc = rpc_drop_reply; - else - dprintk("lockd: TEST status %d vers %d\n", - ntohl(resp->status), rqstp->rq_vers); - - nlmsvc_release_lockowner(&argp->lock); - nlmsvc_release_host(host); - nlm_release_file(file); - return rc; -} - /** * nlmsvc_proc_test - TEST: Check for conflicting lock * @rqstp: RPC transaction context @@ -805,19 +771,69 @@ nlmsvc_callback(struct svc_rqst *rqstp, struct nlm_host *host, u32 proc, return rpc_success; } +static __be32 +__nlmsvc_proc_test_msg(struct svc_rqst *rqstp, struct lockd_res *resp) +{ + struct nlm_testargs_wrapper *argp = rqstp->rq_argp; + unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; + struct nlm_lockowner *owner; + struct nlm_file *file = NULL; + struct nlm_host *host = NULL; + + resp->status = nlm_lck_denied_nolocks; + if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie)) + goto out; + + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); + if (!host) + goto out; + + resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock, + &file, &argp->xdrgen.alock, type); + if (resp->status) + goto out; + + owner = argp->lock.fl.c.flc_owner; + resp->status = cast_status(nlmsvc_testlock(rqstp, file, host, + &argp->lock, &resp->lock)); + nlmsvc_put_lockowner(owner); + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success; +} + +/** + * nlmsvc_proc_test_msg - TEST_MSG: Check for conflicting lock + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * %rpc_garbage_args: The request arguments are malformed. + * %rpc_system_err: RPC execution failed. + * + * RPC synopsis: + * void NLMPROC_TEST_MSG(nlm_testargs) = 6; + * + * The response to this request is delivered via the TEST_RES procedure. + */ static __be32 nlmsvc_proc_test_msg(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; + struct nlm_testargs_wrapper *argp = rqstp->rq_argp; + struct nlm_host *host; - dprintk("lockd: TEST_MSG called\n"); + if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN) + return rpc_garbage_args; - host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); if (!host) return rpc_system_err; return nlmsvc_callback(rqstp, host, NLMPROC_TEST_RES, - __nlmsvc_proc_test); + __nlmsvc_proc_test_msg); } static __be32 nlmsvc_proc_lock_msg(struct svc_rqst *rqstp) @@ -1103,15 +1119,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_res_sz, .pc_name = "GRANTED", }, - [NLMPROC_TEST_MSG] = { - .pc_func = nlmsvc_proc_test_msg, - .pc_decode = nlmsvc_decode_testargs, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "TEST_MSG", + [NLM_TEST_MSG] = { + .pc_func = nlmsvc_proc_test_msg, + .pc_decode = nlm_svc_decode_nlm_testargs, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_testargs_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "TEST_MSG", }, [NLMPROC_LOCK_MSG] = { .pc_func = nlmsvc_proc_lock_msg, From 438c9af3b135b614b9ea771456f1d441b3291cb5 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:56 -0400 Subject: [PATCH 069/106] lockd: Use xdrgen XDR functions for the NLMv3 LOCK_MSG procedure Continue the xdrgen migration by converting NLMv3 LOCK_MSG, the async counterpart to LOCK that clients use to request locks that may block. The procedure now uses nlm_svc_decode_nlm_lockargs and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. The procedure handler reaches the xdrgen types through the nlm_lockargs_wrapper structure, which bridges between generated code and the legacy lockd_lock representation. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in nlm3svc_lookup_file() rather than relying on zero-initialization. The NLM async callback mechanism uses client-side functions which continue to take legacy results like struct lockd_res, preventing LOCK and LOCK_MSG from sharing code for now. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 77 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 14 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index cd32cfc62338..17625d43dc37 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -836,19 +836,68 @@ static __be32 nlmsvc_proc_test_msg(struct svc_rqst *rqstp) __nlmsvc_proc_test_msg); } +static __be32 +__nlmsvc_proc_lock_msg(struct svc_rqst *rqstp, struct lockd_res *resp) +{ + struct nlm_lockargs_wrapper *argp = rqstp->rq_argp; + unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; + struct nlm_file *file = NULL; + struct nlm_host *host = NULL; + + resp->status = nlm_lck_denied_nolocks; + if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie)) + goto out; + + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, true); + if (!host) + goto out; + + resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock, + &file, &argp->xdrgen.alock, type); + if (resp->status) + goto out; + + resp->status = cast_status(nlmsvc_lock(rqstp, file, host, &argp->lock, + argp->xdrgen.block, &resp->cookie, + argp->xdrgen.reclaim)); + nlmsvc_release_lockowner(&argp->lock); + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success; +} + +/** + * nlmsvc_proc_lock_msg - LOCK_MSG: Establish a monitored lock + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * %rpc_garbage_args: The request arguments are malformed. + * %rpc_system_err: RPC execution failed. + * + * RPC synopsis: + * void NLMPROC_LOCK_MSG(nlm_lockargs) = 7; + * + * The response to this request is delivered via the LOCK_RES procedure. + */ static __be32 nlmsvc_proc_lock_msg(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; + struct nlm_lockargs_wrapper *argp = rqstp->rq_argp; + struct nlm_host *host; - dprintk("lockd: LOCK_MSG called\n"); + if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN) + return rpc_garbage_args; - host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); if (!host) return rpc_system_err; return nlmsvc_callback(rqstp, host, NLMPROC_LOCK_RES, - __nlmsvc_proc_lock); + __nlmsvc_proc_lock_msg); } static __be32 nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp) @@ -1129,15 +1178,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "TEST_MSG", }, - [NLMPROC_LOCK_MSG] = { - .pc_func = nlmsvc_proc_lock_msg, - .pc_decode = nlmsvc_decode_lockargs, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "LOCK_MSG", + [NLM_LOCK_MSG] = { + .pc_func = nlmsvc_proc_lock_msg, + .pc_decode = nlm_svc_decode_nlm_lockargs, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_lockargs_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "LOCK_MSG", }, [NLMPROC_CANCEL_MSG] = { .pc_func = nlmsvc_proc_cancel_msg, From 02a47b6c5c85b28bf4dbf0c6c0ee29e0f735271f Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:57 -0400 Subject: [PATCH 070/106] lockd: Use xdrgen XDR functions for the NLMv3 CANCEL_MSG procedure The CANCEL_MSG procedure is part of NLM's asynchronous lock request flow, where clients send CANCEL_MSG to cancel pending lock requests. This patch continues the xdrgen migration by converting CANCEL_MSG to use generated XDR functions. This patch converts the CANCEL_MSG procedure to use xdrgen functions nlm_svc_decode_nlm_cancargs and nlm_svc_encode_void generated from the NLM version 3 protocol specification. The procedure handler uses xdrgen types through the nlm_cancargs_wrapper structure that bridges between generated code and the legacy lockd_lock representation. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in nlm3svc_lookup_file() rather than relying on zero-initialization. The previous hand-written decoder in svcxdr_decode_cookie() rewrote a zero-length NLM cookie into a four-byte zero cookie, with a comment attributing the substitution to HP-UX clients. The xdrgen-generated netobj decoder performs no such rewrite, so a zero-length request cookie now round-trips unchanged into the CANCEL_RES reply. HP-UX has reached end of support, and CANCEL_MSG is fire-and-forget with no client-side reply matching on the NLM cookie, so the workaround is dropped intentionally here. The NLM async callback mechanism uses client-side functions which continue to take legacy results like struct lockd_res, preventing CANCEL and CANCEL_MSG from sharing code for now. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 114 ++++++++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 47 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 17625d43dc37..d055079c9485 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -478,39 +478,6 @@ nlmsvc_proc_lock(struct svc_rqst *rqstp) return nlmsvc_do_lock(rqstp, true); } -static __be32 -__nlmsvc_proc_cancel(struct svc_rqst *rqstp, struct lockd_res *resp) -{ - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; - struct nlm_file *file; - struct net *net = SVC_NET(rqstp); - - dprintk("lockd: CANCEL called\n"); - - resp->cookie = argp->cookie; - - /* Don't accept requests during grace period */ - if (locks_in_grace(net)) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } - - /* Obtain client and file */ - if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return resp->status == nlm__int__drop_reply ? - rpc_drop_reply : rpc_success; - - /* Try to cancel request. */ - resp->status = cast_status(nlmsvc_cancel_blocked(net, file, &argp->lock)); - - dprintk("lockd: CANCEL status %d\n", ntohl(resp->status)); - nlmsvc_release_lockowner(&argp->lock); - nlmsvc_release_host(host); - nlm_release_file(file); - return rpc_success; -} - /** * nlmsvc_proc_cancel - CANCEL: Cancel an outstanding blocked lock request * @rqstp: RPC transaction context @@ -900,19 +867,72 @@ static __be32 nlmsvc_proc_lock_msg(struct svc_rqst *rqstp) __nlmsvc_proc_lock_msg); } +static __be32 +__nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp, struct lockd_res *resp) +{ + struct nlm_cancargs_wrapper *argp = rqstp->rq_argp; + unsigned char type = argp->xdrgen.exclusive ? F_WRLCK : F_RDLCK; + struct net *net = SVC_NET(rqstp); + struct nlm_file *file = NULL; + struct nlm_host *host = NULL; + + resp->status = nlm_lck_denied_nolocks; + if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie)) + goto out; + + resp->status = nlm_lck_denied_grace_period; + if (locks_in_grace(net)) + goto out; + + resp->status = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); + if (!host) + goto out; + + resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock, + &file, &argp->xdrgen.alock, type); + if (resp->status) + goto out; + + resp->status = nlmsvc_cancel_blocked(net, file, &argp->lock); + nlmsvc_release_lockowner(&argp->lock); + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success; +} + +/** + * nlmsvc_proc_cancel_msg - CANCEL_MSG: Cancel an outstanding lock request + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * %rpc_garbage_args: The request arguments are malformed. + * %rpc_system_err: RPC execution failed. + * + * RPC synopsis: + * void NLMPROC_CANCEL_MSG(nlm_cancargs) = 8; + * + * The response to this request is delivered via the CANCEL_RES procedure. + */ static __be32 nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; + struct nlm_cancargs_wrapper *argp = rqstp->rq_argp; + struct nlm_host *host; - dprintk("lockd: CANCEL_MSG called\n"); + if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN) + return rpc_garbage_args; - host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); if (!host) return rpc_system_err; return nlmsvc_callback(rqstp, host, NLMPROC_CANCEL_RES, - __nlmsvc_proc_cancel); + __nlmsvc_proc_cancel_msg); } static __be32 nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp) @@ -1188,15 +1208,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "LOCK_MSG", }, - [NLMPROC_CANCEL_MSG] = { - .pc_func = nlmsvc_proc_cancel_msg, - .pc_decode = nlmsvc_decode_cancargs, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "CANCEL_MSG", + [NLM_CANCEL_MSG] = { + .pc_func = nlmsvc_proc_cancel_msg, + .pc_decode = nlm_svc_decode_nlm_cancargs, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_cancargs_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "CANCEL_MSG", }, [NLMPROC_UNLOCK_MSG] = { .pc_func = nlmsvc_proc_unlock_msg, From 6a2f89e49b4fb69aa6e04ed1c7cb202f0c38406e Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:58 -0400 Subject: [PATCH 071/106] lockd: Use xdrgen XDR functions for the NLMv3 UNLOCK_MSG procedure Continue the xdrgen migration by converting NLMv3 UNLOCK_MSG, the async counterpart to UNLOCK that clients use to release locks without waiting for a reply. The procedure now uses nlm_svc_decode_nlm_unlockargs and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. The procedure handler reaches the xdrgen types through the nlm_unlockargs_wrapper structure, which bridges between generated code and the legacy lockd_lock representation. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in nlm3svc_lookup_file() rather than relying on zero-initialization. The NLM async callback mechanism uses client-side functions which continue to take legacy results like struct lockd_res, preventing UNLOCK and UNLOCK_MSG from sharing code for now. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 116 ++++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 50 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index d055079c9485..d059accfdebd 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -537,42 +537,6 @@ nlmsvc_proc_cancel(struct svc_rqst *rqstp) rpc_drop_reply : rpc_success; } -/* - * UNLOCK: release a lock - */ -static __be32 -__nlmsvc_proc_unlock(struct svc_rqst *rqstp, struct lockd_res *resp) -{ - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; - struct nlm_file *file; - struct net *net = SVC_NET(rqstp); - - dprintk("lockd: UNLOCK called\n"); - - resp->cookie = argp->cookie; - - /* Don't accept new lock requests during grace period */ - if (locks_in_grace(net)) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } - - /* Obtain client and file */ - if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return resp->status == nlm__int__drop_reply ? - rpc_drop_reply : rpc_success; - - /* Now try to remove the lock */ - resp->status = cast_status(nlmsvc_unlock(net, file, &argp->lock)); - - dprintk("lockd: UNLOCK status %d\n", ntohl(resp->status)); - nlmsvc_release_lockowner(&argp->lock); - nlmsvc_release_host(host); - nlm_release_file(file); - return rpc_success; -} - /** * nlmsvc_proc_unlock - UNLOCK: Remove a lock * @rqstp: RPC transaction context @@ -935,19 +899,71 @@ static __be32 nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp) __nlmsvc_proc_cancel_msg); } +static __be32 +__nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp, struct lockd_res *resp) +{ + struct nlm_unlockargs_wrapper *argp = rqstp->rq_argp; + struct net *net = SVC_NET(rqstp); + struct nlm_file *file = NULL; + struct nlm_host *host = NULL; + + resp->status = nlm_lck_denied_nolocks; + if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie)) + goto out; + + resp->status = nlm_lck_denied_grace_period; + if (locks_in_grace(net)) + goto out; + + resp->status = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); + if (!host) + goto out; + + resp->status = nlm3svc_lookup_file(rqstp, host, &argp->lock, + &file, &argp->xdrgen.alock, F_UNLCK); + if (resp->status) + goto out; + + resp->status = nlmsvc_unlock(net, file, &argp->lock); + nlmsvc_release_lockowner(&argp->lock); + +out: + if (file) + nlm_release_file(file); + nlmsvc_release_host(host); + return resp->status == nlm__int__drop_reply ? rpc_drop_reply : rpc_success; +} + +/** + * nlmsvc_proc_unlock_msg - UNLOCK_MSG: Remove an existing lock + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * %rpc_garbage_args: The request arguments are malformed. + * %rpc_system_err: RPC execution failed. + * + * RPC synopsis: + * void NLMPROC_UNLOCK_MSG(nlm_unlockargs) = 9; + * + * The response to this request is delivered via the UNLOCK_RES procedure. + */ static __be32 nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; + struct nlm_unlockargs_wrapper *argp = rqstp->rq_argp; + struct nlm_host *host; - dprintk("lockd: UNLOCK_MSG called\n"); + if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN) + return rpc_garbage_args; - host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); if (!host) return rpc_system_err; return nlmsvc_callback(rqstp, host, NLMPROC_UNLOCK_RES, - __nlmsvc_proc_unlock); + __nlmsvc_proc_unlock_msg); } static __be32 nlmsvc_proc_granted_msg(struct svc_rqst *rqstp) @@ -1218,15 +1234,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "CANCEL_MSG", }, - [NLMPROC_UNLOCK_MSG] = { - .pc_func = nlmsvc_proc_unlock_msg, - .pc_decode = nlmsvc_decode_unlockargs, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "UNLOCK_MSG", + [NLM_UNLOCK_MSG] = { + .pc_func = nlmsvc_proc_unlock_msg, + .pc_decode = nlm_svc_decode_nlm_unlockargs, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_unlockargs_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "UNLOCK_MSG", }, [NLMPROC_GRANTED_MSG] = { .pc_func = nlmsvc_proc_granted_msg, From 6c534ad999b6696bca55ded82bb7645fe6c5979e Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:13:59 -0400 Subject: [PATCH 072/106] lockd: Use xdrgen XDR functions for the NLMv3 GRANTED_MSG procedure Continue the xdrgen migration by converting NLMv3 GRANTED_MSG, the async counterpart to GRANTED that a remote NLM uses to tell this lockd that a previously blocked client lock request has become available. The procedure now uses nlm_svc_decode_nlm_testargs and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. The procedure handler reaches the xdrgen types through the nlm_testargs_wrapper structure, which bridges between generated code and the legacy lockd_lock representation. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The lock member of the wrapper is populated explicitly in __nlmsvc_proc_granted_msg() by nlm_lock_to_lockd_lock() rather than relying on zero-initialization. The NLM async callback mechanism uses client-side functions which continue to take legacy results like struct lockd_res, preventing GRANTED and GRANTED_MSG from sharing code for now. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 78 ++++++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index d059accfdebd..21a363450d59 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -594,23 +594,6 @@ nlmsvc_proc_unlock(struct svc_rqst *rqstp) rpc_drop_reply : rpc_success; } -/* - * GRANTED: A server calls us to tell that a process' lock request - * was granted - */ -static __be32 -__nlmsvc_proc_granted(struct svc_rqst *rqstp, struct lockd_res *resp) -{ - struct lockd_args *argp = rqstp->rq_argp; - - resp->cookie = argp->cookie; - - dprintk("lockd: GRANTED called\n"); - resp->status = nlmclnt_grant(svc_addr(rqstp), &argp->lock); - dprintk("lockd: GRANTED status %d\n", ntohl(resp->status)); - return rpc_success; -} - /** * nlmsvc_proc_granted - GRANTED: Blocked lock has been granted * @rqstp: RPC transaction context @@ -966,19 +949,52 @@ static __be32 nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp) __nlmsvc_proc_unlock_msg); } +static __be32 +__nlmsvc_proc_granted_msg(struct svc_rqst *rqstp, struct lockd_res *resp) +{ + struct nlm_testargs_wrapper *argp = rqstp->rq_argp; + + resp->status = nlm_lck_denied; + if (nlm_netobj_to_cookie(&resp->cookie, &argp->xdrgen.cookie)) + goto out; + + if (nlm_lock_to_lockd_lock(&argp->lock, &argp->xdrgen.alock)) + goto out; + + resp->status = nlmclnt_grant(svc_addr(rqstp), &argp->lock); + +out: + return rpc_success; +} + +/** + * nlmsvc_proc_granted_msg - GRANTED_MSG: Blocked lock has been granted + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_garbage_args: The request arguments are malformed. + * %rpc_system_err: RPC execution failed. + * + * RPC synopsis: + * void NLMPROC_GRANTED_MSG(nlm_testargs) = 10; + * + * The response to this request is delivered via the GRANTED_RES procedure. + */ static __be32 nlmsvc_proc_granted_msg(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; + struct nlm_testargs_wrapper *argp = rqstp->rq_argp; + struct nlm_host *host; - dprintk("lockd: GRANTED_MSG called\n"); + if (argp->xdrgen.cookie.len > NLM_MAXCOOKIELEN) + return rpc_garbage_args; - host = nlmsvc_lookup_host(rqstp, argp->lock.caller, argp->lock.len); + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.alock.caller_name, false); if (!host) return rpc_system_err; return nlmsvc_callback(rqstp, host, NLMPROC_GRANTED_RES, - __nlmsvc_proc_granted); + __nlmsvc_proc_granted_msg); } /* @@ -1244,15 +1260,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "UNLOCK_MSG", }, - [NLMPROC_GRANTED_MSG] = { - .pc_func = nlmsvc_proc_granted_msg, - .pc_decode = nlmsvc_decode_testargs, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "GRANTED_MSG", + [NLM_GRANTED_MSG] = { + .pc_func = nlmsvc_proc_granted_msg, + .pc_decode = nlm_svc_decode_nlm_testargs, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_testargs_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "GRANTED_MSG", }, [NLMPROC_TEST_RES] = { .pc_func = nlmsvc_proc_null, From 386bed664e0d20e60908bc51d33994bf6c1a05a0 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:00 -0400 Subject: [PATCH 073/106] lockd: Use xdrgen XDR functions for the NLMv3 TEST_RES procedure Continue the xdrgen migration by converting NLMv3 TEST_RES, the callback that a remote NLM uses to return async TEST results to this lockd. The procedure now uses nlm_svc_decode_nlm_testres and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Setting pc_xdrressize to XDR_void reflects that TEST_RES, as a callback, returns no data; the previous value of St over-reserved a status word in the reply buffer. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 21a363450d59..ed1164ff431a 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -1270,15 +1270,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "GRANTED_MSG", }, - [NLMPROC_TEST_RES] = { - .pc_func = nlmsvc_proc_null, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_res), - .pc_argzero = sizeof(struct lockd_res), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "TEST_RES", + [NLM_TEST_RES] = { + .pc_func = nlmsvc_proc_null, + .pc_decode = nlm_svc_decode_nlm_testres, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_testres), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "TEST_RES", }, [NLMPROC_LOCK_RES] = { .pc_func = nlmsvc_proc_null, From 9783a82b75feb3cf1d2c6f31194e756fb0c13fca Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:01 -0400 Subject: [PATCH 074/106] lockd: Use xdrgen XDR functions for the NLMv3 LOCK_RES procedure Continue the xdrgen migration by converting NLMv3 LOCK_RES, the callback that a remote NLM uses to return async LOCK results to this lockd. The procedure now uses nlm_svc_decode_nlm_res and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Setting pc_xdrressize to XDR_void reflects that LOCK_RES, as a callback, returns no data; the previous value of St over-reserved a status word in the reply buffer. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index ed1164ff431a..c86ba0b94793 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -1280,15 +1280,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "TEST_RES", }, - [NLMPROC_LOCK_RES] = { - .pc_func = nlmsvc_proc_null, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_res), - .pc_argzero = sizeof(struct lockd_res), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "LOCK_RES", + [NLM_LOCK_RES] = { + .pc_func = nlmsvc_proc_null, + .pc_decode = nlm_svc_decode_nlm_res, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_res), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "LOCK_RES", }, [NLMPROC_CANCEL_RES] = { .pc_func = nlmsvc_proc_null, From 82452a0254deb5c602c0d8ec288d21d381dd72b3 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:02 -0400 Subject: [PATCH 075/106] lockd: Use xdrgen XDR functions for the NLMv3 CANCEL_RES procedure Continue the xdrgen migration by converting NLMv3 CANCEL_RES, the callback that a remote NLM uses to return async CANCEL results to this lockd. The procedure now uses nlm_svc_decode_nlm_res and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Setting pc_xdrressize to XDR_void reflects that CANCEL_RES, as a callback, returns no data; the previous value of St over-reserved a status word in the reply buffer. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index c86ba0b94793..e7d399afbe83 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -1290,15 +1290,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "LOCK_RES", }, - [NLMPROC_CANCEL_RES] = { - .pc_func = nlmsvc_proc_null, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_res), - .pc_argzero = sizeof(struct lockd_res), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "CANCEL_RES", + [NLM_CANCEL_RES] = { + .pc_func = nlmsvc_proc_null, + .pc_decode = nlm_svc_decode_nlm_res, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_res), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "CANCEL_RES", }, [NLMPROC_UNLOCK_RES] = { .pc_func = nlmsvc_proc_null, From 0e20aa949192712a362a35d863d35f5292b04555 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:03 -0400 Subject: [PATCH 076/106] lockd: Use xdrgen XDR functions for the NLMv3 UNLOCK_RES procedure Continue the xdrgen migration by converting NLMv3 UNLOCK_RES, the callback that a remote NLM uses to return async UNLOCK results to this lockd. The procedure now uses nlm_svc_decode_nlm_res and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Setting pc_xdrressize to XDR_void reflects that UNLOCK_RES, as a callback, returns no data; the previous value of St over-reserved a status word in the reply buffer. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index e7d399afbe83..f17d3f8d85ec 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -1300,15 +1300,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "CANCEL_RES", }, - [NLMPROC_UNLOCK_RES] = { - .pc_func = nlmsvc_proc_null, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_res), - .pc_argzero = sizeof(struct lockd_res), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "UNLOCK_RES", + [NLM_UNLOCK_RES] = { + .pc_func = nlmsvc_proc_null, + .pc_decode = nlm_svc_decode_nlm_res, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_res), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "UNLOCK_RES", }, [NLMPROC_GRANTED_RES] = { .pc_func = nlmsvc_proc_granted_res, From e0018ec659f559749bb3ded7acb6bb03447bc416 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:04 -0400 Subject: [PATCH 077/106] lockd: Use xdrgen XDR functions for the NLMv3 GRANTED_RES procedure Continue the xdrgen migration by converting NLMv3 GRANTED_RES, the callback that a remote NLM uses to return async GRANTED results to this lockd. The procedure now uses nlm_svc_decode_nlm_res and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Setting pc_xdrressize to XDR_void reflects that GRANTED_RES, as a callback, returns no data; the previous value of St over-reserved a status word in the reply buffer. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 60 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index f17d3f8d85ec..a2ac747e96ce 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -61,6 +61,7 @@ static_assert(offsetof(struct nlm_lockargs_wrapper, xdrgen) == 0); struct nlm_res_wrapper { struct nlm_res xdrgen; + struct lockd_cookie cookie; }; static_assert(offsetof(struct nlm_res_wrapper, xdrgen) == 0); @@ -997,6 +998,30 @@ static __be32 nlmsvc_proc_granted_msg(struct svc_rqst *rqstp) __nlmsvc_proc_granted_msg); } +/** + * nlmsvc_proc_granted_res - GRANTED_RES: Lock Granted result + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * + * RPC synopsis: + * void NLMPROC_GRANTED_RES(nlm_res) = 15; + */ +static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp) +{ + struct nlm_res_wrapper *argp = rqstp->rq_argp; + + if (!nlmsvc_ops) + return rpc_success; + + if (nlm_netobj_to_cookie(&argp->cookie, &argp->xdrgen.cookie)) + return rpc_success; + nlmsvc_grant_reply(&argp->cookie, argp->xdrgen.stat.stat); + + return rpc_success; +} + /* * SHARE: create a DOS share or alter existing share. */ @@ -1125,23 +1150,6 @@ nlmsvc_proc_sm_notify(struct svc_rqst *rqstp) return rpc_success; } -/* - * client sent a GRANTED_RES, let's remove the associated block - */ -static __be32 -nlmsvc_proc_granted_res(struct svc_rqst *rqstp) -{ - struct lockd_res *argp = rqstp->rq_argp; - - if (!nlmsvc_ops) - return rpc_success; - - dprintk("lockd: GRANTED_RES called\n"); - - nlmsvc_grant_reply(&argp->cookie, argp->status); - return rpc_success; -} - static __be32 nlmsvc_proc_unused(struct svc_rqst *rqstp) { @@ -1310,15 +1318,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "UNLOCK_RES", }, - [NLMPROC_GRANTED_RES] = { - .pc_func = nlmsvc_proc_granted_res, - .pc_decode = nlmsvc_decode_res, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_res), - .pc_argzero = sizeof(struct lockd_res), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "GRANTED_RES", + [NLM_GRANTED_RES] = { + .pc_func = nlmsvc_proc_granted_res, + .pc_decode = nlm_svc_decode_nlm_res, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_res_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "GRANTED_RES", }, [NLMPROC_NSM_NOTIFY] = { .pc_func = nlmsvc_proc_sm_notify, From 371275eeb032ac4122a0847c97ee5c0f36ae15c2 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:05 -0400 Subject: [PATCH 078/106] lockd: Use xdrgen XDR functions for the NLMv3 SM_NOTIFY procedure Continue the xdrgen migration by converting NLMv3 SM_NOTIFY, a private callback from statd to notify lockd when a remote host has rebooted. The procedure now uses nlm_svc_decode_nlm_notifyargs and nlm_svc_encode_void, generated from the NLM version 3 protocol specification. A new struct nlm_notifyargs_wrapper bridges between the xdrgen-generated nlm_notifyargs and the lockd_reboot structure expected by nlm_host_rebooted(). The wrapper contains both the xdrgen-decoded arguments and a reboot field for the existing API. Setting pc_argzero to zero is safe because the generated decoder fills argp->xdrgen before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Setting pc_xdrressize to XDR_void reflects that SM_NOTIFY returns no data; the previous value of St over-reserved a status word in the reply buffer. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 86 +++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 31 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index a2ac747e96ce..e0328dc89deb 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -80,6 +80,13 @@ struct nlm_unlockargs_wrapper { static_assert(offsetof(struct nlm_unlockargs_wrapper, xdrgen) == 0); +struct nlm_notifyargs_wrapper { + struct nlm_notifyargs xdrgen; + struct lockd_reboot reboot; +}; + +static_assert(offsetof(struct nlm_notifyargs_wrapper, xdrgen) == 0); + static __be32 nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) { @@ -1022,6 +1029,44 @@ static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp) return rpc_success; } +/** + * nlmsvc_proc_sm_notify - SM_NOTIFY: Peer has rebooted + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_system_err: RPC execution failed. + * + * The SM_NOTIFY procedure is a private callback from Linux statd and is + * not part of the official NLM protocol. + * + * RPC synopsis: + * void NLM_SM_NOTIFY(nlm_notifyargs) = 16; + */ +static __be32 nlmsvc_proc_sm_notify(struct svc_rqst *rqstp) +{ + struct nlm_notifyargs_wrapper *argp = rqstp->rq_argp; + struct lockd_reboot *reboot = &argp->reboot; + + if (!nlm_privileged_requester(rqstp)) { + char buf[RPC_MAX_ADDRBUFLEN]; + + pr_warn("lockd: rejected NSM callback from %s\n", + svc_print_addr(rqstp, buf, sizeof(buf))); + return rpc_system_err; + } + + reboot->len = argp->xdrgen.notify.name.len; + reboot->mon = (char *)argp->xdrgen.notify.name.data; + reboot->state = argp->xdrgen.notify.state; + memcpy(&reboot->priv.data, argp->xdrgen.private, + sizeof(reboot->priv.data)); + + nlm_host_rebooted(SVC_NET(rqstp), reboot); + + return rpc_success; +} + /* * SHARE: create a DOS share or alter existing share. */ @@ -1129,27 +1174,6 @@ nlmsvc_proc_free_all(struct svc_rqst *rqstp) return rpc_success; } -/* - * SM_NOTIFY: private callback from statd (not part of official NLM proto) - */ -static __be32 -nlmsvc_proc_sm_notify(struct svc_rqst *rqstp) -{ - struct lockd_reboot *argp = rqstp->rq_argp; - - dprintk("lockd: SM_NOTIFY called\n"); - - if (!nlm_privileged_requester(rqstp)) { - char buf[RPC_MAX_ADDRBUFLEN]; - printk(KERN_WARNING "lockd: rejected NSM callback from %s\n", - svc_print_addr(rqstp, buf, sizeof(buf))); - return rpc_system_err; - } - - nlm_host_rebooted(SVC_NET(rqstp), argp); - return rpc_success; -} - static __be32 nlmsvc_proc_unused(struct svc_rqst *rqstp) { @@ -1328,15 +1352,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "GRANTED_RES", }, - [NLMPROC_NSM_NOTIFY] = { - .pc_func = nlmsvc_proc_sm_notify, - .pc_decode = nlmsvc_decode_reboot, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_reboot), - .pc_argzero = sizeof(struct lockd_reboot), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "SM_NOTIFY", + [NLM_SM_NOTIFY] = { + .pc_func = nlmsvc_proc_sm_notify, + .pc_decode = nlm_svc_decode_nlm_notifyargs, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_notifyargs_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "SM_NOTIFY", }, [17] = { .pc_func = nlmsvc_proc_unused, @@ -1418,10 +1442,10 @@ union nlmsvc_xdrstore { struct nlm_lockargs_wrapper lockargs; struct nlm_cancargs_wrapper cancargs; struct nlm_unlockargs_wrapper unlockargs; + struct nlm_notifyargs_wrapper notifyargs; struct nlm_testres_wrapper testres; struct nlm_res_wrapper res; struct lockd_args args; - struct lockd_reboot reboot; }; /* From 2403834551a9a36e937eeb1fc8522f5a0f5ce6e7 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:06 -0400 Subject: [PATCH 079/106] lockd: Convert NLMv3 server-side undefined procedures to xdrgen Complete the xdrgen migration of NLMv3 server-side procedures by converting the three unused procedure slots (17, 18, and 19). These slots already returned rpc_proc_unavail; they are converted here only to retire the last users of the hand-coded nlmsvc_decode_void and nlmsvc_encode_void helpers. The three undefined procedure entries now use the xdrgen functions nlm_svc_decode_void and nlm_svc_encode_void. The nlmsvc_proc_unused function is also moved earlier in the file to follow the convention of placing procedure implementations before the procedure table. The pc_argsize, pc_ressize, and pc_argzero fields are now set to zero since no arguments or results are processed. Setting pc_xdrressize to XDR_void reflects that these procedures return no reply payload; the previous value of St over-reserved a status word in the reply buffer. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 66 +++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index e0328dc89deb..79c410a75156 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -1067,6 +1067,18 @@ static __be32 nlmsvc_proc_sm_notify(struct svc_rqst *rqstp) return rpc_success; } +/** + * nlmsvc_proc_unused - stub for unused procedures + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_proc_unavail: Program can't support procedure. + */ +static __be32 nlmsvc_proc_unused(struct svc_rqst *rqstp) +{ + return rpc_proc_unavail; +} + /* * SHARE: create a DOS share or alter existing share. */ @@ -1174,12 +1186,6 @@ nlmsvc_proc_free_all(struct svc_rqst *rqstp) return rpc_success; } -static __be32 -nlmsvc_proc_unused(struct svc_rqst *rqstp) -{ - return rpc_proc_unavail; -} - /* * NLM Server procedures. */ @@ -1363,34 +1369,34 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_name = "SM_NOTIFY", }, [17] = { - .pc_func = nlmsvc_proc_unused, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_void), - .pc_argzero = sizeof(struct nlm_void), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "UNUSED", + .pc_func = nlmsvc_proc_unused, + .pc_decode = nlm_svc_decode_void, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = 0, + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "UNUSED", }, [18] = { - .pc_func = nlmsvc_proc_unused, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_void), - .pc_argzero = sizeof(struct nlm_void), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "UNUSED", + .pc_func = nlmsvc_proc_unused, + .pc_decode = nlm_svc_decode_void, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = 0, + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "UNUSED", }, [19] = { - .pc_func = nlmsvc_proc_unused, - .pc_decode = nlmsvc_decode_void, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct nlm_void), - .pc_argzero = sizeof(struct nlm_void), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = St, - .pc_name = "UNUSED", + .pc_func = nlmsvc_proc_unused, + .pc_decode = nlm_svc_decode_void, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = 0, + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "UNUSED", }, [NLMPROC_SHARE] = { .pc_func = nlmsvc_proc_share, From 1a2bba073f60f24777f6e7214b83387ed5e1d271 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:07 -0400 Subject: [PATCH 080/106] lockd: Use xdrgen XDR functions for the NLMv3 SHARE procedure Convert the NLMv3 SHARE procedure to use xdrgen-generated XDR functions nlm_svc_decode_nlm_shareargs and nlm_svc_encode_nlm_shareres. This patch introduces struct nlm_shareargs_wrapper and struct nlm_shareres_wrapper to bridge between the xdrgen-generated structures and the internal lockd types. The procedure handler is updated to access arguments through the argp->xdrgen hierarchy and uses nlm3svc_lookup_host and nlm3svc_lookup_file for host and file resolution. The .pc_argzero field is set to zero because the generated decoder fills argp->xdrgen before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 114 +++++++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 36 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 79c410a75156..32625ee07c35 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -87,6 +87,19 @@ struct nlm_notifyargs_wrapper { static_assert(offsetof(struct nlm_notifyargs_wrapper, xdrgen) == 0); +struct nlm_shareargs_wrapper { + struct nlm_shareargs xdrgen; + struct lockd_lock lock; +}; + +static_assert(offsetof(struct nlm_shareargs_wrapper, xdrgen) == 0); + +struct nlm_shareres_wrapper { + struct nlm_shareres xdrgen; +}; + +static_assert(offsetof(struct nlm_shareres_wrapper, xdrgen) == 0); + static __be32 nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) { @@ -1079,42 +1092,69 @@ static __be32 nlmsvc_proc_unused(struct svc_rqst *rqstp) return rpc_proc_unavail; } -/* - * SHARE: create a DOS share or alter existing share. +/** + * nlmsvc_proc_share - SHARE: Open a file using DOS file-sharing modes + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * + * RPC synopsis: + * nlm_shareres NLM_SHARE(nlm_shareargs) = 20; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The requested share lock was granted. + * %LCK_DENIED: The requested lock conflicted with existing + * lock reservations for the file. + * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is + * re-establishing existing locks, and is not + * yet ready to accept normal service requests. + * + * The Linux NLM server implementation also returns: + * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated. */ -static __be32 -nlmsvc_proc_share(struct svc_rqst *rqstp) +static __be32 nlmsvc_proc_share(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - struct lockd_res *resp = rqstp->rq_resp; - struct nlm_host *host; - struct nlm_file *file; + struct nlm_shareargs_wrapper *argp = rqstp->rq_argp; + struct nlm_shareres_wrapper *resp = rqstp->rq_resp; + struct lockd_lock *lock = &argp->lock; + struct nlm_host *host = NULL; + struct nlm_file *file = NULL; + struct nlm_lock xdr_lock = { + .fh = argp->xdrgen.share.fh, + .oh = argp->xdrgen.share.oh, + .uppid = LOCKD_SHARE_SVID, + }; - dprintk("lockd: SHARE called\n"); + resp->xdrgen.cookie = argp->xdrgen.cookie; - resp->cookie = argp->cookie; + resp->xdrgen.stat = nlm_lck_denied_grace_period; + if (locks_in_grace(SVC_NET(rqstp)) && !argp->xdrgen.reclaim) + goto out; - /* Don't accept new lock requests during grace period */ - if (locks_in_grace(SVC_NET(rqstp)) && !argp->reclaim) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } + resp->xdrgen.stat = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.share.caller_name, false); + if (!host) + goto out; - /* Obtain client and file */ - if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return resp->status == nlm__int__drop_reply ? - rpc_drop_reply : rpc_success; + resp->xdrgen.stat = nlm3svc_lookup_file(rqstp, host, lock, &file, + &xdr_lock, F_RDLCK); + if (resp->xdrgen.stat) + goto out; - /* Now try to create the share */ - resp->status = cast_status(nlmsvc_share_file(host, file, &argp->lock.oh, - argp->fsm_access, - argp->fsm_mode)); + resp->xdrgen.stat = nlmsvc_share_file(host, file, &lock->oh, + argp->xdrgen.share.access, + argp->xdrgen.share.mode); - dprintk("lockd: SHARE status %d\n", ntohl(resp->status)); - nlmsvc_release_lockowner(&argp->lock); + nlmsvc_release_lockowner(lock); + +out: + if (file) + nlm_release_file(file); nlmsvc_release_host(host); - nlm_release_file(file); - return rpc_success; + return resp->xdrgen.stat == nlm__int__drop_reply ? + rpc_drop_reply : rpc_success; } /* @@ -1398,15 +1438,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = XDR_void, .pc_name = "UNUSED", }, - [NLMPROC_SHARE] = { - .pc_func = nlmsvc_proc_share, - .pc_decode = nlmsvc_decode_shareargs, - .pc_encode = nlmsvc_encode_shareres, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St+1, - .pc_name = "SHARE", + [NLM_SHARE] = { + .pc_func = nlmsvc_proc_share, + .pc_decode = nlm_svc_decode_nlm_shareargs, + .pc_encode = nlm_svc_encode_nlm_shareres, + .pc_argsize = sizeof(struct nlm_shareargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_shareres_wrapper), + .pc_xdrressize = NLM3_nlm_shareres_sz, + .pc_name = "SHARE", }, [NLMPROC_UNSHARE] = { .pc_func = nlmsvc_proc_unshare, @@ -1449,8 +1489,10 @@ union nlmsvc_xdrstore { struct nlm_cancargs_wrapper cancargs; struct nlm_unlockargs_wrapper unlockargs; struct nlm_notifyargs_wrapper notifyargs; + struct nlm_shareargs_wrapper shareargs; struct nlm_testres_wrapper testres; struct nlm_res_wrapper res; + struct nlm_shareres_wrapper shareres; struct lockd_args args; }; From ac3d7a8ca411e59546d69dbd23f24128924b426c Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:08 -0400 Subject: [PATCH 081/106] lockd: Use xdrgen XDR functions for the NLMv3 UNSHARE procedure Convert the NLMv3 UNSHARE procedure to use xdrgen-generated XDR functions nlm_svc_decode_nlm_shareargs and nlm_svc_encode_nlm_shareres. The procedure handler is updated to use the wrapper structures (nlm_shareargs_wrapper and nlm_shareres_wrapper) introduced by the SHARE conversion patch and accesses arguments through the argp->xdrgen hierarchy. The .pc_argzero field is set to zero because the generated decoder fills argp->xdrgen before the procedure runs, so the zeroing memset performed by the dispatch layer is no longer needed. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 94 +++++++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 35 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 32625ee07c35..de4c15aa725c 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -1157,41 +1157,65 @@ static __be32 nlmsvc_proc_share(struct svc_rqst *rqstp) rpc_drop_reply : rpc_success; } -/* - * UNSHARE: Release a DOS share. +/** + * nlmsvc_proc_unshare - UNSHARE: Release a share reservation + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * + * RPC synopsis: + * nlm_shareres NLM_UNSHARE(nlm_shareargs) = 21; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The share reservation was released. + * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is + * re-establishing existing locks, and is not + * yet ready to accept normal service requests. + * + * The Linux NLM server implementation also returns: + * %LCK_DENIED_NOLOCKS: A needed resource could not be allocated. */ -static __be32 -nlmsvc_proc_unshare(struct svc_rqst *rqstp) +static __be32 nlmsvc_proc_unshare(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - struct lockd_res *resp = rqstp->rq_resp; - struct nlm_host *host; - struct nlm_file *file; + struct nlm_shareargs_wrapper *argp = rqstp->rq_argp; + struct nlm_shareres_wrapper *resp = rqstp->rq_resp; + struct lockd_lock *lock = &argp->lock; + struct nlm_lock xdr_lock = { + .fh = argp->xdrgen.share.fh, + .oh = argp->xdrgen.share.oh, + .uppid = LOCKD_SHARE_SVID, + }; + struct nlm_host *host = NULL; + struct nlm_file *file = NULL; - dprintk("lockd: UNSHARE called\n"); + resp->xdrgen.cookie = argp->xdrgen.cookie; - resp->cookie = argp->cookie; + resp->xdrgen.stat = nlm_lck_denied_grace_period; + if (locks_in_grace(SVC_NET(rqstp))) + goto out; - /* Don't accept requests during grace period */ - if (locks_in_grace(SVC_NET(rqstp))) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } + resp->xdrgen.stat = nlm_lck_denied_nolocks; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.share.caller_name, false); + if (!host) + goto out; - /* Obtain client and file */ - if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return resp->status == nlm__int__drop_reply ? - rpc_drop_reply : rpc_success; + resp->xdrgen.stat = nlm3svc_lookup_file(rqstp, host, lock, &file, + &xdr_lock, F_RDLCK); + if (resp->xdrgen.stat) + goto out; - /* Now try to unshare the file */ - resp->status = cast_status(nlmsvc_unshare_file(host, file, - &argp->lock.oh)); + resp->xdrgen.stat = nlmsvc_unshare_file(host, file, &lock->oh); - dprintk("lockd: UNSHARE status %d\n", ntohl(resp->status)); - nlmsvc_release_lockowner(&argp->lock); + nlmsvc_release_lockowner(lock); + +out: + if (file) + nlm_release_file(file); nlmsvc_release_host(host); - nlm_release_file(file); - return rpc_success; + return resp->xdrgen.stat == nlm__int__drop_reply ? + rpc_drop_reply : rpc_success; } /* @@ -1448,15 +1472,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_shareres_sz, .pc_name = "SHARE", }, - [NLMPROC_UNSHARE] = { - .pc_func = nlmsvc_proc_unshare, - .pc_decode = nlmsvc_decode_shareargs, - .pc_encode = nlmsvc_encode_shareres, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St+1, - .pc_name = "UNSHARE", + [NLM_UNSHARE] = { + .pc_func = nlmsvc_proc_unshare, + .pc_decode = nlm_svc_decode_nlm_shareargs, + .pc_encode = nlm_svc_encode_nlm_shareres, + .pc_argsize = sizeof(struct nlm_shareargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_shareres_wrapper), + .pc_xdrressize = NLM3_nlm_shareres_sz, + .pc_name = "UNSHARE", }, [NLMPROC_NM_LOCK] = { .pc_func = nlmsvc_proc_nm_lock, From 92f689d5fa3fa953afc5290524cd0e9057a2d4b6 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:09 -0400 Subject: [PATCH 082/106] lockd: Use xdrgen XDR functions for the NLMv3 NM_LOCK procedure Now that nlmsvc_do_lock() has been introduced to handle both monitored and non-monitored lock requests, the NLMv3 NM_LOCK procedure can be converted to use xdrgen-generated XDR functions. This conversion allows the removal of __nlmsvc_proc_lock(), a helper function that was previously shared between the LOCK and NM_LOCK procedures. Replace the NLMPROC_NM_LOCK entry in the nlmsvc_procedures array with an entry that uses xdrgen-built XDR decoders and encoders. The procedure handler is reduced to a thin wrapper around nlmsvc_do_lock() with the monitored flag set to false. The pc_argzero=0 choice was justified for the LOCK conversion and applies unchanged here, since both procedures share the same nlm_lockargs_wrapper layout and decoder. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 86 +++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 51 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index de4c15aa725c..4ca0eaef2966 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -392,38 +392,6 @@ static __be32 nlmsvc_proc_test(struct svc_rqst *rqstp) rpc_drop_reply : rpc_success; } -static __be32 -__nlmsvc_proc_lock(struct svc_rqst *rqstp, struct lockd_res *resp) -{ - struct lockd_args *argp = rqstp->rq_argp; - struct nlm_host *host; - struct nlm_file *file; - __be32 rc = rpc_success; - - dprintk("lockd: LOCK called\n"); - - resp->cookie = argp->cookie; - - /* Obtain client and file */ - if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return resp->status == nlm__int__drop_reply ? - rpc_drop_reply : rpc_success; - - /* Now try to lock the file */ - resp->status = cast_status(nlmsvc_lock(rqstp, file, host, &argp->lock, - argp->block, &argp->cookie, - argp->reclaim)); - if (resp->status == nlm__int__drop_reply) - rc = rpc_drop_reply; - else - dprintk("lockd: LOCK status %d\n", ntohl(resp->status)); - - nlmsvc_release_lockowner(&argp->lock); - nlmsvc_release_host(host); - nlm_release_file(file); - return rc; -} - static __be32 nlmsvc_do_lock(struct svc_rqst *rqstp, bool monitored) { @@ -1218,18 +1186,34 @@ static __be32 nlmsvc_proc_unshare(struct svc_rqst *rqstp) rpc_drop_reply : rpc_success; } -/* - * NM_LOCK: Create an unmonitored lock +/** + * nlmsvc_proc_nm_lock - NM_LOCK: Establish a non-monitored lock + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * %rpc_drop_reply: Do not send an RPC reply. + * + * RPC synopsis: + * nlm_res NLM_NM_LOCK(nlm_lockargs) = 22; + * + * Permissible procedure status codes: + * %LCK_GRANTED: The requested lock was granted. + * %LCK_DENIED: The requested lock conflicted with existing + * lock reservations for the file. + * %LCK_DENIED_NOLOCKS: The server could not allocate the resources + * needed to process the request. + * %LCK_BLOCKED: The blocking request cannot be granted + * immediately. The server will send an + * NLM_GRANTED callback to the client when + * the lock can be granted. + * %LCK_DENIED_GRACE_PERIOD: The server has recently restarted and is + * re-establishing existing locks, and is not + * yet ready to accept normal service requests. */ -static __be32 -nlmsvc_proc_nm_lock(struct svc_rqst *rqstp) +static __be32 nlmsvc_proc_nm_lock(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; - - dprintk("lockd: NM_LOCK called\n"); - - argp->monitor = 0; /* just clean the monitor flag */ - return __nlmsvc_proc_lock(rqstp, rqstp->rq_resp); + return nlmsvc_do_lock(rqstp, false); } /* @@ -1482,15 +1466,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_shareres_sz, .pc_name = "UNSHARE", }, - [NLMPROC_NM_LOCK] = { - .pc_func = nlmsvc_proc_nm_lock, - .pc_decode = nlmsvc_decode_lockargs, - .pc_encode = nlmsvc_encode_res, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct lockd_res), - .pc_xdrressize = Ck+St, - .pc_name = "NM_LOCK", + [NLM_NM_LOCK] = { + .pc_func = nlmsvc_proc_nm_lock, + .pc_decode = nlm_svc_decode_nlm_lockargs, + .pc_encode = nlm_svc_encode_nlm_res, + .pc_argsize = sizeof(struct nlm_lockargs_wrapper), + .pc_argzero = 0, + .pc_ressize = sizeof(struct nlm_res_wrapper), + .pc_xdrressize = NLM3_nlm_res_sz, + .pc_name = "NM_LOCK", }, [NLMPROC_FREE_ALL] = { .pc_func = nlmsvc_proc_free_all, From a5239c8d5d9d34dae33877c44df7bef0b1d44c4c Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:10 -0400 Subject: [PATCH 083/106] lockd: Use xdrgen XDR functions for the NLMv3 FREE_ALL procedure With all other NLMv3 procedures now converted to xdrgen-generated XDR functions, the FREE_ALL procedure can be converted as well. This conversion allows the removal of nlmsvc_retrieve_args(), a 52-line helper function that was used only by FREE_ALL to retrieve client information from lockd's internal data structures. Replace the NLMPROC_FREE_ALL entry in the nlmsvc_procedures array with an entry that uses xdrgen-built XDR decoders and encoders. The procedure handler is updated to use the new wrapper structure (nlm_notify_wrapper) and call nlm3svc_lookup_host() directly, eliminating the need for the now-removed helper function. Setting pc_argzero to zero is safe because the generated decoder fills the argp->xdrgen subfields before the procedure runs, so the zeroing memset performed by the dispatch layer is not needed. The nlm_notify_wrapper structure has no members beyond the xdrgen substructure, so no further initialization is required. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 113 +++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 80 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 4ca0eaef2966..ef1ae701b43b 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -100,6 +100,12 @@ struct nlm_shareres_wrapper { static_assert(offsetof(struct nlm_shareres_wrapper, xdrgen) == 0); +struct nlm_notify_wrapper { + struct nlm_notify xdrgen; +}; + +static_assert(offsetof(struct nlm_notify_wrapper, xdrgen) == 0); + static __be32 nlm_netobj_to_cookie(struct lockd_cookie *cookie, netobj *object) { @@ -240,68 +246,6 @@ static inline __be32 cast_status(__be32 status) } #endif -/* - * Obtain client and file from arguments - */ -static __be32 -nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct lockd_args *argp, - struct nlm_host **hostp, struct nlm_file **filp) -{ - struct nlm_host *host = NULL; - struct nlm_file *file = NULL; - struct lockd_lock *lock = &argp->lock; - bool is_test = (rqstp->rq_proc == NLMPROC_TEST || - rqstp->rq_proc == NLMPROC_TEST_MSG); - int mode; - __be32 error = 0; - - /* nfsd callbacks must have been installed for this procedure */ - if (!nlmsvc_ops) - return nlm_lck_denied_nolocks; - - /* Obtain host handle */ - if (!(host = nlmsvc_lookup_host(rqstp, lock->caller, lock->len)) - || (argp->monitor && nsm_monitor(host) < 0)) - goto no_locks; - *hostp = host; - - /* Obtain file pointer. Not used by FREE_ALL call. */ - if (filp != NULL) { - mode = lock_to_openmode(&lock->fl); - - if (is_test) - mode = O_RDWR; - - error = cast_status(nlm_lookup_file(rqstp, &file, lock, mode)); - if (error != 0) - goto no_locks; - *filp = file; - - /* Set up the missing parts of the file_lock structure */ - lock->fl.c.flc_flags = FL_POSIX; - if (is_test) - lock->fl.c.flc_file = nlmsvc_file_file(file); - else - lock->fl.c.flc_file = file->f_file[mode]; - lock->fl.c.flc_pid = current->tgid; - lock->fl.fl_lmops = &nlmsvc_lock_operations; - nlmsvc_locks_init_private(&lock->fl, host, (pid_t)lock->svid); - if (!lock->fl.c.flc_owner) { - /* lockowner allocation has failed */ - nlmsvc_release_host(host); - return nlm_lck_denied_nolocks; - } - } - - return 0; - -no_locks: - nlmsvc_release_host(host); - if (error) - return error; - return nlm_lck_denied_nolocks; -} - /** * nlmsvc_proc_null - NULL: Test for presence of service * @rqstp: RPC transaction context @@ -1216,21 +1160,30 @@ static __be32 nlmsvc_proc_nm_lock(struct svc_rqst *rqstp) return nlmsvc_do_lock(rqstp, false); } -/* - * FREE_ALL: Release all locks and shares held by client +/** + * nlmsvc_proc_free_all - FREE_ALL: Discard client's lock and share state + * @rqstp: RPC transaction context + * + * Returns: + * %rpc_success: RPC executed successfully. + * + * RPC synopsis: + * void NLMPROC_FREE_ALL(nlm_notify) = 23; */ -static __be32 -nlmsvc_proc_free_all(struct svc_rqst *rqstp) +static __be32 nlmsvc_proc_free_all(struct svc_rqst *rqstp) { - struct lockd_args *argp = rqstp->rq_argp; + struct nlm_notify_wrapper *argp = rqstp->rq_argp; struct nlm_host *host; - /* Obtain client */ - if (nlmsvc_retrieve_args(rqstp, argp, &host, NULL)) - return rpc_success; + host = nlm3svc_lookup_host(rqstp, argp->xdrgen.name, false); + if (!host) + goto out; nlmsvc_free_host_resources(host); + nlmsvc_release_host(host); + +out: return rpc_success; } @@ -1476,15 +1429,15 @@ static const struct svc_procedure nlmsvc_procedures[24] = { .pc_xdrressize = NLM3_nlm_res_sz, .pc_name = "NM_LOCK", }, - [NLMPROC_FREE_ALL] = { - .pc_func = nlmsvc_proc_free_all, - .pc_decode = nlmsvc_decode_notify, - .pc_encode = nlmsvc_encode_void, - .pc_argsize = sizeof(struct lockd_args), - .pc_argzero = sizeof(struct lockd_args), - .pc_ressize = sizeof(struct nlm_void), - .pc_xdrressize = 0, - .pc_name = "FREE_ALL", + [NLM_FREE_ALL] = { + .pc_func = nlmsvc_proc_free_all, + .pc_decode = nlm_svc_decode_nlm_notify, + .pc_encode = nlm_svc_encode_void, + .pc_argsize = sizeof(struct nlm_notify_wrapper), + .pc_argzero = 0, + .pc_ressize = 0, + .pc_xdrressize = XDR_void, + .pc_name = "FREE_ALL", }, }; @@ -1498,10 +1451,10 @@ union nlmsvc_xdrstore { struct nlm_unlockargs_wrapper unlockargs; struct nlm_notifyargs_wrapper notifyargs; struct nlm_shareargs_wrapper shareargs; + struct nlm_notify_wrapper notify; struct nlm_testres_wrapper testres; struct nlm_res_wrapper res; struct nlm_shareres_wrapper shareres; - struct lockd_args args; }; /* From 5c0d3a859ba6d66eb3b587fae1ad81ce518d9bd0 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:11 -0400 Subject: [PATCH 084/106] lockd: Remove C macros that are no longer used The conversion of all NLMv3 procedures to xdrgen-generated XDR functions is complete. The hand-rolled XDR size calculation macros (Ck, No, St, Rg) and the nlm_void structure definition served only the older implementations and are now unused. Also removes NLMDBG_FACILITY, which was set to the client debug flag in server-side code but never referenced, and corrects a comment to specify "NLMv3 Server procedures". Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index ef1ae701b43b..f62b0b39c5e9 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -24,8 +24,6 @@ #include "share.h" #include "nlm3xdr_gen.h" -#define NLMDBG_FACILITY NLMDBG_CLIENT - /* * Size of an NFSv2 file handle, in bytes, which is 32. * Defined locally to avoid including uapi/linux/nfs2.h. @@ -1188,16 +1186,9 @@ static __be32 nlmsvc_proc_free_all(struct svc_rqst *rqstp) } /* - * NLM Server procedures. + * NLMv3 Server procedures. */ -struct nlm_void { int dummy; }; - -#define Ck (1+XDR_QUADLEN(NLM_MAXCOOKIELEN)) /* cookie */ -#define St 1 /* status */ -#define No (1+1024/4) /* Net Obj */ -#define Rg 2 /* range - offset + size */ - static const struct svc_procedure nlmsvc_procedures[24] = { [NLM_NULL] = { .pc_func = nlmsvc_proc_null, From 9bf81d102c9d3403f51d00527beabf4151343ad5 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:12 -0400 Subject: [PATCH 085/106] lockd: Remove dead code from fs/lockd/xdr.c All NLMv3 server-side procedures now dispatch through xdrgen-generated encoder and decoder functions, leaving the hand-written XDR processing in fs/lockd/xdr.c with no remaining callers. Remove fs/lockd/xdr.c, the fs/lockd/svcxdr.h header it included, the Makefile entry, and the now-unused nlmsvc_decode_* / nlmsvc_encode_* prototypes from fs/lockd/xdr.h. The structure definitions and status code macros in xdr.h are retained as they are still used by the client-side code. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/Makefile | 2 +- fs/lockd/svcxdr.h | 142 ------------------- fs/lockd/xdr.c | 354 ---------------------------------------------- fs/lockd/xdr.h | 20 --- 4 files changed, 1 insertion(+), 517 deletions(-) delete mode 100644 fs/lockd/svcxdr.h delete mode 100644 fs/lockd/xdr.c diff --git a/fs/lockd/Makefile b/fs/lockd/Makefile index 951a74e4847a..3f9569d3ba0e 100644 --- a/fs/lockd/Makefile +++ b/fs/lockd/Makefile @@ -8,7 +8,7 @@ ccflags-y += -I$(src) # needed for trace events obj-$(CONFIG_LOCKD) += lockd.o lockd-y := clntlock.o clntproc.o clntxdr.o host.o svc.o svclock.o \ - svcshare.o svcproc.o svcsubs.o mon.o trace.o xdr.o netlink.o \ + svcshare.o svcproc.o svcsubs.o mon.o trace.o netlink.o \ nlm3xdr_gen.o lockd-$(CONFIG_LOCKD_V4) += clnt4xdr.o svc4proc.o nlm4xdr_gen.o lockd-$(CONFIG_PROC_FS) += procfs.o diff --git a/fs/lockd/svcxdr.h b/fs/lockd/svcxdr.h deleted file mode 100644 index 911b5fd707b1..000000000000 --- a/fs/lockd/svcxdr.h +++ /dev/null @@ -1,142 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Encode/decode NLM basic data types - * - * Basic NLMv3 XDR data types are not defined in an IETF standards - * document. X/Open has a description of these data types that - * is useful. See Chapter 10 of "Protocols for Interworking: - * XNFS, Version 3W". - * - * Basic NLMv4 XDR data types are defined in Appendix II.1.4 of - * RFC 1813: "NFS Version 3 Protocol Specification". - * - * Author: Chuck Lever - * - * Copyright (c) 2020, Oracle and/or its affiliates. - */ - -#ifndef _LOCKD_SVCXDR_H_ -#define _LOCKD_SVCXDR_H_ - -static inline bool -svcxdr_decode_stats(struct xdr_stream *xdr, __be32 *status) -{ - __be32 *p; - - p = xdr_inline_decode(xdr, XDR_UNIT); - if (!p) - return false; - *status = *p; - - return true; -} - -static inline bool -svcxdr_encode_stats(struct xdr_stream *xdr, __be32 status) -{ - __be32 *p; - - p = xdr_reserve_space(xdr, XDR_UNIT); - if (!p) - return false; - *p = status; - - return true; -} - -static inline bool -svcxdr_decode_string(struct xdr_stream *xdr, char **data, unsigned int *data_len) -{ - __be32 *p; - u32 len; - - if (xdr_stream_decode_u32(xdr, &len) < 0) - return false; - if (len > NLM_MAXSTRLEN) - return false; - p = xdr_inline_decode(xdr, len); - if (!p) - return false; - *data_len = len; - *data = (char *)p; - - return true; -} - -/* - * NLM cookies are defined by specification to be a variable-length - * XDR opaque no longer than 1024 bytes. However, this implementation - * limits their length to 32 bytes, and treats zero-length cookies - * specially. - */ -static inline bool -svcxdr_decode_cookie(struct xdr_stream *xdr, struct lockd_cookie *cookie) -{ - __be32 *p; - u32 len; - - if (xdr_stream_decode_u32(xdr, &len) < 0) - return false; - if (len > NLM_MAXCOOKIELEN) - return false; - if (!len) - goto out_hpux; - - p = xdr_inline_decode(xdr, len); - if (!p) - return false; - cookie->len = len; - memcpy(cookie->data, p, len); - - return true; - - /* apparently HPUX can return empty cookies */ -out_hpux: - cookie->len = 4; - memset(cookie->data, 0, 4); - return true; -} - -static inline bool -svcxdr_encode_cookie(struct xdr_stream *xdr, const struct lockd_cookie *cookie) -{ - __be32 *p; - - if (xdr_stream_encode_u32(xdr, cookie->len) < 0) - return false; - p = xdr_reserve_space(xdr, cookie->len); - if (!p) - return false; - memcpy(p, cookie->data, cookie->len); - - return true; -} - -static inline bool -svcxdr_decode_owner(struct xdr_stream *xdr, struct xdr_netobj *obj) -{ - __be32 *p; - u32 len; - - if (xdr_stream_decode_u32(xdr, &len) < 0) - return false; - if (len > XDR_MAX_NETOBJ) - return false; - p = xdr_inline_decode(xdr, len); - if (!p) - return false; - obj->len = len; - obj->data = (u8 *)p; - - return true; -} - -static inline bool -svcxdr_encode_owner(struct xdr_stream *xdr, const struct xdr_netobj *obj) -{ - if (obj->len > XDR_MAX_NETOBJ) - return false; - return xdr_stream_encode_opaque(xdr, obj->data, obj->len) > 0; -} - -#endif /* _LOCKD_SVCXDR_H_ */ diff --git a/fs/lockd/xdr.c b/fs/lockd/xdr.c deleted file mode 100644 index c78c64557fea..000000000000 --- a/fs/lockd/xdr.c +++ /dev/null @@ -1,354 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * linux/fs/lockd/xdr.c - * - * XDR support for lockd and the lock client. - * - * Copyright (C) 1995, 1996 Olaf Kirch - */ - -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include "lockd.h" -#include "share.h" -#include "svcxdr.h" - -static inline loff_t -s32_to_loff_t(__s32 offset) -{ - return (loff_t)offset; -} - -static inline __s32 -loff_t_to_s32(loff_t offset) -{ - __s32 res; - if (offset >= NLM_OFFSET_MAX) - res = NLM_OFFSET_MAX; - else if (offset <= -NLM_OFFSET_MAX) - res = -NLM_OFFSET_MAX; - else - res = offset; - return res; -} - -/* - * NLM file handles are defined by specification to be a variable-length - * XDR opaque no longer than 1024 bytes. However, this implementation - * constrains their length to exactly the length of an NFSv2 file - * handle. - */ -static bool -svcxdr_decode_fhandle(struct xdr_stream *xdr, struct nfs_fh *fh) -{ - __be32 *p; - u32 len; - - if (xdr_stream_decode_u32(xdr, &len) < 0) - return false; - if (len != NFS2_FHSIZE) - return false; - - p = xdr_inline_decode(xdr, len); - if (!p) - return false; - fh->size = NFS2_FHSIZE; - memcpy(fh->data, p, len); - memset(fh->data + NFS2_FHSIZE, 0, sizeof(fh->data) - NFS2_FHSIZE); - - return true; -} - -static bool -svcxdr_decode_lock(struct xdr_stream *xdr, struct lockd_lock *lock) -{ - struct file_lock *fl = &lock->fl; - s32 start, len, end; - - if (!svcxdr_decode_string(xdr, &lock->caller, &lock->len)) - return false; - if (!svcxdr_decode_fhandle(xdr, &lock->fh)) - return false; - if (!svcxdr_decode_owner(xdr, &lock->oh)) - return false; - if (xdr_stream_decode_u32(xdr, &lock->svid) < 0) - return false; - if (xdr_stream_decode_u32(xdr, &start) < 0) - return false; - if (xdr_stream_decode_u32(xdr, &len) < 0) - return false; - - locks_init_lock(fl); - fl->c.flc_flags = FL_POSIX; - fl->c.flc_type = F_RDLCK; - end = start + len - 1; - fl->fl_start = s32_to_loff_t(start); - if (len == 0 || end < 0) - fl->fl_end = OFFSET_MAX; - else - fl->fl_end = s32_to_loff_t(end); - - return true; -} - -static bool -svcxdr_encode_holder(struct xdr_stream *xdr, const struct lockd_lock *lock) -{ - const struct file_lock *fl = &lock->fl; - s32 start, len; - - /* exclusive */ - if (xdr_stream_encode_bool(xdr, fl->c.flc_type != F_RDLCK) < 0) - return false; - if (xdr_stream_encode_u32(xdr, lock->svid) < 0) - return false; - if (!svcxdr_encode_owner(xdr, &lock->oh)) - return false; - start = loff_t_to_s32(fl->fl_start); - if (fl->fl_end == OFFSET_MAX) - len = 0; - else - len = loff_t_to_s32(fl->fl_end - fl->fl_start + 1); - if (xdr_stream_encode_u32(xdr, start) < 0) - return false; - if (xdr_stream_encode_u32(xdr, len) < 0) - return false; - - return true; -} - -static bool -svcxdr_encode_testrply(struct xdr_stream *xdr, const struct lockd_res *resp) -{ - if (!svcxdr_encode_stats(xdr, resp->status)) - return false; - switch (resp->status) { - case nlm_lck_denied: - if (!svcxdr_encode_holder(xdr, &resp->lock)) - return false; - } - - return true; -} - - -/* - * Decode Call arguments - */ - -bool -nlmsvc_decode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - return true; -} - -bool -nlmsvc_decode_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_args *argp = rqstp->rq_argp; - u32 exclusive; - - if (!svcxdr_decode_cookie(xdr, &argp->cookie)) - return false; - if (xdr_stream_decode_bool(xdr, &exclusive) < 0) - return false; - if (!svcxdr_decode_lock(xdr, &argp->lock)) - return false; - if (exclusive) - argp->lock.fl.c.flc_type = F_WRLCK; - - return true; -} - -bool -nlmsvc_decode_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_args *argp = rqstp->rq_argp; - u32 exclusive; - - if (!svcxdr_decode_cookie(xdr, &argp->cookie)) - return false; - if (xdr_stream_decode_bool(xdr, &argp->block) < 0) - return false; - if (xdr_stream_decode_bool(xdr, &exclusive) < 0) - return false; - if (!svcxdr_decode_lock(xdr, &argp->lock)) - return false; - if (exclusive) - argp->lock.fl.c.flc_type = F_WRLCK; - if (xdr_stream_decode_bool(xdr, &argp->reclaim) < 0) - return false; - if (xdr_stream_decode_u32(xdr, &argp->state) < 0) - return false; - argp->monitor = 1; /* monitor client by default */ - - return true; -} - -bool -nlmsvc_decode_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_args *argp = rqstp->rq_argp; - u32 exclusive; - - if (!svcxdr_decode_cookie(xdr, &argp->cookie)) - return false; - if (xdr_stream_decode_bool(xdr, &argp->block) < 0) - return false; - if (xdr_stream_decode_bool(xdr, &exclusive) < 0) - return false; - if (!svcxdr_decode_lock(xdr, &argp->lock)) - return false; - if (exclusive) - argp->lock.fl.c.flc_type = F_WRLCK; - - return true; -} - -bool -nlmsvc_decode_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_args *argp = rqstp->rq_argp; - - if (!svcxdr_decode_cookie(xdr, &argp->cookie)) - return false; - if (!svcxdr_decode_lock(xdr, &argp->lock)) - return false; - argp->lock.fl.c.flc_type = F_UNLCK; - - return true; -} - -bool -nlmsvc_decode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_res *resp = rqstp->rq_argp; - - if (!svcxdr_decode_cookie(xdr, &resp->cookie)) - return false; - if (!svcxdr_decode_stats(xdr, &resp->status)) - return false; - - return true; -} - -bool -nlmsvc_decode_reboot(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_reboot *argp = rqstp->rq_argp; - __be32 *p; - u32 len; - - if (xdr_stream_decode_u32(xdr, &len) < 0) - return false; - if (len > SM_MAXSTRLEN) - return false; - p = xdr_inline_decode(xdr, len); - if (!p) - return false; - argp->len = len; - argp->mon = (char *)p; - if (xdr_stream_decode_u32(xdr, &argp->state) < 0) - return false; - p = xdr_inline_decode(xdr, SM_PRIV_SIZE); - if (!p) - return false; - memcpy(&argp->priv.data, p, sizeof(argp->priv.data)); - - return true; -} - -bool -nlmsvc_decode_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_args *argp = rqstp->rq_argp; - struct lockd_lock *lock = &argp->lock; - - memset(lock, 0, sizeof(*lock)); - locks_init_lock(&lock->fl); - lock->svid = LOCKD_SHARE_SVID; - - if (!svcxdr_decode_cookie(xdr, &argp->cookie)) - return false; - if (!svcxdr_decode_string(xdr, &lock->caller, &lock->len)) - return false; - if (!svcxdr_decode_fhandle(xdr, &lock->fh)) - return false; - if (!svcxdr_decode_owner(xdr, &lock->oh)) - return false; - /* XXX: Range checks are missing in the original code */ - if (xdr_stream_decode_u32(xdr, &argp->fsm_mode) < 0) - return false; - if (xdr_stream_decode_u32(xdr, &argp->fsm_access) < 0) - return false; - - return true; -} - -bool -nlmsvc_decode_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_args *argp = rqstp->rq_argp; - struct lockd_lock *lock = &argp->lock; - - if (!svcxdr_decode_string(xdr, &lock->caller, &lock->len)) - return false; - if (xdr_stream_decode_u32(xdr, &argp->state) < 0) - return false; - - return true; -} - - -/* - * Encode Reply results - */ - -bool -nlmsvc_encode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - return true; -} - -bool -nlmsvc_encode_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_res *resp = rqstp->rq_resp; - - return svcxdr_encode_cookie(xdr, &resp->cookie) && - svcxdr_encode_testrply(xdr, resp); -} - -bool -nlmsvc_encode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_res *resp = rqstp->rq_resp; - - return svcxdr_encode_cookie(xdr, &resp->cookie) && - svcxdr_encode_stats(xdr, resp->status); -} - -bool -nlmsvc_encode_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr) -{ - struct lockd_res *resp = rqstp->rq_resp; - - if (!svcxdr_encode_cookie(xdr, &resp->cookie)) - return false; - if (!svcxdr_encode_stats(xdr, resp->status)) - return false; - /* sequence */ - if (xdr_stream_encode_u32(xdr, 0) < 0) - return false; - - return true; -} diff --git a/fs/lockd/xdr.h b/fs/lockd/xdr.h index 65d2d6d34310..a1126cca98c6 100644 --- a/fs/lockd/xdr.h +++ b/fs/lockd/xdr.h @@ -20,8 +20,6 @@ struct nsm_private { unsigned char data[SM_PRIV_SIZE]; }; -struct svc_rqst; - #define NLM_MAXCOOKIELEN 32 #define NLM_MAXSTRLEN 1024 @@ -63,9 +61,6 @@ struct lockd_args { u32 block; u32 reclaim; u32 state; - u32 monitor; - u32 fsm_access; - u32 fsm_mode; }; /* @@ -87,19 +82,4 @@ struct lockd_reboot { struct nsm_private priv; }; -bool nlmsvc_decode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_testargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_lockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_cancargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_unlockargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_reboot(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_shareargs(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_decode_notify(struct svc_rqst *rqstp, struct xdr_stream *xdr); - -bool nlmsvc_encode_testres(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_encode_res(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_encode_void(struct svc_rqst *rqstp, struct xdr_stream *xdr); -bool nlmsvc_encode_shareres(struct svc_rqst *rqstp, struct xdr_stream *xdr); - #endif /* _LOCKD_XDR_H */ From 1b8d52328e0d37a51d1a4b11cf0f118a59dc4239 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 12 May 2026 14:14:13 -0400 Subject: [PATCH 086/106] lockd: Unify cast_status cast_status folds internal lock-daemon sentinels into NLMv1/v3 wire status codes for the v3 reply path. Two variants have existed since the original kernel import: a strict allowlist under CONFIG_LOCKD_V4 and a sentinel-translation form for the !CONFIG_LOCKD_V4 build. The split was never grounded in a behavioural difference -- nlmsvc_testlock and nlmsvc_lock, which feed cast_status, return the same set of values in both configurations -- and recent xdrgen conversions have narrowed the caller set further: nlm__int__stale_fh and nlm__int__failed are now translated at their point of origin in nlm3svc_lookup_file, and the cast_status wraps around nlmsvc_cancel_blocked and nlmsvc_unlock have been dropped because those functions return only wire codes. Collapse the two variants into one. The unified form keeps the CONFIG_LOCKD_V4 arm's allowlist, retains the nlm__int__deadlock translation, and folds anything else to nlm_lck_denied_nolocks after a pr_warn_once so an unexpected sentinel from a future refactor remains visible in the kernel log instead of being silently passed to the wire encoder. Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/lockd/svcproc.c | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index f62b0b39c5e9..4836887f11ef 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -203,7 +203,6 @@ nlm3svc_lookup_file(struct svc_rqst *rqstp, struct nlm_host *host, return nlm_granted; } -#ifdef CONFIG_LOCKD_V4 static inline __be32 cast_status(__be32 status) { switch (status) { @@ -218,31 +217,13 @@ static inline __be32 cast_status(__be32 status) status = nlm_lck_denied; break; default: + pr_warn_once("lockd: unhandled internal status %u\n", + be32_to_cpu(status)); status = nlm_lck_denied_nolocks; - } - - return status; -} -#else -static inline __be32 cast_status(__be32 status) -{ - switch (status) { - case nlm__int__deadlock: - status = nlm_lck_denied; - break; - case nlm__int__stale_fh: - case nlm__int__failed: - status = nlm_lck_denied_nolocks; - break; - default: - if (be32_to_cpu(status) > be32_to_cpu(nlm__int__drop_reply)) - pr_warn_once("lockd: unhandled internal status %u\n", - be32_to_cpu(status)); break; } return status; } -#endif /** * nlmsvc_proc_null - NULL: Test for presence of service From a39f0ce0c9da20986b429e2db3e4e8739035d61b Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Sat, 6 Jun 2026 22:24:56 -0400 Subject: [PATCH 087/106] Revert "svcrdma: Use contiguous pages for RDMA Read sink buffers" Jonathan Flynn reports that commit 18755b8c2f24 ("svcrdma: Use contiguous pages for RDMA Read sink buffers") regresses NFS/RDMA WRITE throughput from 73.9 GiB/s to 30.3 GiB/s on a 128-core single-NUMA-node server driving dual 400Gb/s links with 640 nfsd threads. Server CPU utilization rises from 8.5% to 76%, with roughly three quarters of all cycles spent spinning on zone->lock. The sink buffers are allocated as high-order page blocks, split into single pages so each sub-page carries an independent refcount, and later released one page at a time through folio batches. The per-CPU page caches cannot satisfy an allocation stream whose alloc order differs from its free order, so every sink buffer page makes a round trip through the buddy allocator's free lists, serialized on the zone lock of the single NUMA node. The rq_pages entries that the split pages displace, bulk-allocated moments earlier by svc_alloc_arg(), are freed without ever being used, doubling the allocator traffic. The regression cannot be addressed trivially. Revert the commit now; a reworked approach can return in an upcoming merge window. Reported-by: Jonathan Flynn Reported-by: Mike Snitzer Closes: https://lore.kernel.org/linux-nfs/aiHlPmeZq3WgMwoJ@kernel.org/ Closes: https://lore.kernel.org/linux-nfs/3cb119b4b2a8aada30c0c60286778a54@mail.gmail.com/ Fixes: 18755b8c2f24 ("svcrdma: Use contiguous pages for RDMA Read sink buffers") Cc: stable@vger.kernel.org Tested-by: Jonathan Flynn Signed-off-by: Chuck Lever --- net/sunrpc/xprtrdma/svc_rdma_rw.c | 223 ------------------------------ 1 file changed, 223 deletions(-) diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index cca8ec973de4..13554793b039 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -745,216 +745,6 @@ int svc_rdma_prepare_reply_chunk(struct svcxprt_rdma *rdma, return xdr->len; } -/* - * Cap contiguous RDMA Read sink allocations at order-4. - * Higher orders risk allocation failure under - * __GFP_NORETRY, which would negate the benefit of the - * contiguous fast path. - */ -#define SVC_RDMA_CONTIG_MAX_ORDER 4 - -/** - * svc_rdma_alloc_read_pages - Allocate physically contiguous pages - * @nr_pages: number of pages needed - * @order: on success, set to the allocation order - * - * Attempts a higher-order allocation, falling back to smaller orders. - * The returned pages are split immediately so each sub-page has its - * own refcount and can be freed independently. - * - * Returns a pointer to the first page on success, or NULL if even - * order-1 allocation fails. - */ -static struct page * -svc_rdma_alloc_read_pages(unsigned int nr_pages, unsigned int *order) -{ - unsigned int o; - struct page *page; - - o = min(get_order(nr_pages << PAGE_SHIFT), - SVC_RDMA_CONTIG_MAX_ORDER); - - while (o >= 1) { - page = alloc_pages(GFP_KERNEL | __GFP_NORETRY | __GFP_NOWARN, - o); - if (page) { - split_page(page, o); - *order = o; - return page; - } - o--; - } - return NULL; -} - -/* - * svc_rdma_fill_contig_bvec - Replace rq_pages with a contiguous allocation - * @rqstp: RPC transaction context - * @head: context for ongoing I/O - * @bv: bvec entry to fill - * @pages_left: number of data pages remaining in the segment - * @len_left: bytes remaining in the segment - * - * On success, fills @bv with a bvec spanning the contiguous range and - * advances rc_curpage/rc_page_count. Returns the byte length covered, - * or zero if the allocation failed or would overrun rq_maxpages. - */ -static unsigned int -svc_rdma_fill_contig_bvec(struct svc_rqst *rqstp, - struct svc_rdma_recv_ctxt *head, - struct bio_vec *bv, unsigned int pages_left, - unsigned int len_left) -{ - unsigned int order, npages, chunk_pages, chunk_len, i; - struct page *page; - - page = svc_rdma_alloc_read_pages(pages_left, &order); - if (!page) - return 0; - npages = 1 << order; - - if (head->rc_curpage + npages > rqstp->rq_maxpages) { - for (i = 0; i < npages; i++) - __free_page(page + i); - return 0; - } - - /* - * Replace rq_pages[] entries with pages from the contiguous - * allocation. If npages exceeds chunk_pages, the extra pages - * stay in rq_pages[] for later reuse or normal rqst teardown. - */ - for (i = 0; i < npages; i++) { - svc_rqst_page_release(rqstp, - rqstp->rq_pages[head->rc_curpage + i]); - rqstp->rq_pages[head->rc_curpage + i] = page + i; - } - - chunk_pages = min(npages, pages_left); - chunk_len = min_t(unsigned int, chunk_pages << PAGE_SHIFT, len_left); - bvec_set_page(bv, page, chunk_len, 0); - head->rc_page_count += chunk_pages; - head->rc_curpage += chunk_pages; - return chunk_len; -} - -/* - * svc_rdma_fill_page_bvec - Add a single rq_page to the bvec array - * @head: context for ongoing I/O - * @ctxt: R/W context whose bvec array is being filled - * @cur: page to add - * @bvec_idx: pointer to current bvec index, not advanced on merge - * @len_left: bytes remaining in the segment - * - * If @cur is physically contiguous with the preceding bvec, it is - * merged by extending that bvec's length. Otherwise a new bvec - * entry is created. Returns the byte length covered. - */ -static unsigned int -svc_rdma_fill_page_bvec(struct svc_rdma_recv_ctxt *head, - struct svc_rdma_rw_ctxt *ctxt, struct page *cur, - unsigned int *bvec_idx, unsigned int len_left) -{ - unsigned int chunk_len = min_t(unsigned int, PAGE_SIZE, len_left); - - head->rc_page_count++; - head->rc_curpage++; - - if (*bvec_idx > 0) { - struct bio_vec *prev = &ctxt->rw_bvec[*bvec_idx - 1]; - - if (page_to_phys(prev->bv_page) + prev->bv_offset + - prev->bv_len == page_to_phys(cur)) { - prev->bv_len += chunk_len; - return chunk_len; - } - } - - bvec_set_page(&ctxt->rw_bvec[*bvec_idx], cur, chunk_len, 0); - (*bvec_idx)++; - return chunk_len; -} - -/** - * svc_rdma_build_read_segment_contig - Build RDMA Read WR with contiguous pages - * @rqstp: RPC transaction context - * @head: context for ongoing I/O - * @segment: co-ordinates of remote memory to be read - * - * Greedily allocates higher-order pages to cover the segment, - * building one bvec per contiguous chunk. Each allocation is - * split so sub-pages have independent refcounts. When a - * higher-order allocation fails, remaining pages are covered - * individually, merging adjacent pages into the preceding bvec - * when they are physically contiguous. The split sub-pages - * replace entries in rq_pages[] so downstream cleanup is - * unchanged. - * - * Returns: - * %0: the Read WR was constructed successfully - * %-ENOMEM: allocation failed - * %-EIO: a DMA mapping error occurred - */ -static int svc_rdma_build_read_segment_contig(struct svc_rqst *rqstp, - struct svc_rdma_recv_ctxt *head, - const struct svc_rdma_segment *segment) -{ - struct svcxprt_rdma *rdma = svc_rdma_rqst_rdma(rqstp); - struct svc_rdma_chunk_ctxt *cc = &head->rc_cc; - unsigned int nr_data_pages, bvec_idx; - struct svc_rdma_rw_ctxt *ctxt; - unsigned int len_left; - int ret; - - nr_data_pages = PAGE_ALIGN(segment->rs_length) >> PAGE_SHIFT; - if (head->rc_curpage + nr_data_pages > rqstp->rq_maxpages) - return -ENOMEM; - - ctxt = svc_rdma_get_rw_ctxt(rdma, nr_data_pages); - if (!ctxt) - return -ENOMEM; - - bvec_idx = 0; - len_left = segment->rs_length; - while (len_left) { - unsigned int pages_left = PAGE_ALIGN(len_left) >> PAGE_SHIFT; - unsigned int chunk_len = 0; - - if (pages_left >= 2) - chunk_len = svc_rdma_fill_contig_bvec(rqstp, head, - &ctxt->rw_bvec[bvec_idx], - pages_left, len_left); - if (chunk_len) { - bvec_idx++; - } else { - struct page *cur = - rqstp->rq_pages[head->rc_curpage]; - chunk_len = svc_rdma_fill_page_bvec(head, ctxt, cur, - &bvec_idx, - len_left); - } - - len_left -= chunk_len; - } - - ctxt->rw_nents = bvec_idx; - - head->rc_pageoff = offset_in_page(segment->rs_length); - if (head->rc_pageoff) - head->rc_curpage--; - - ret = svc_rdma_rw_ctx_init(rdma, ctxt, segment->rs_offset, - segment->rs_handle, segment->rs_length, - DMA_FROM_DEVICE); - if (ret < 0) - return -EIO; - percpu_counter_inc(&svcrdma_stat_read); - - list_add(&ctxt->rw_list, &cc->cc_rwctxts); - cc->cc_sqecount += ret; - return 0; -} - /** * svc_rdma_build_read_segment - Build RDMA Read WQEs to pull one RDMA segment * @rqstp: RPC transaction context @@ -981,14 +771,6 @@ static int svc_rdma_build_read_segment(struct svc_rqst *rqstp, if (check_add_overflow(head->rc_pageoff, len, &total)) return -EINVAL; nr_bvec = PAGE_ALIGN(total) >> PAGE_SHIFT; - - if (head->rc_pageoff == 0 && nr_bvec >= 2) { - ret = svc_rdma_build_read_segment_contig(rqstp, head, - segment); - if (ret != -ENOMEM) - return ret; - } - ctxt = svc_rdma_get_rw_ctxt(rdma, nr_bvec); if (!ctxt) return -ENOMEM; @@ -1334,11 +1116,6 @@ static void svc_rdma_clear_rqst_pages(struct svc_rqst *rqstp, { unsigned int i; - /* - * Move only pages containing RPC data into rc_pages[]. Pages - * from a contiguous allocation that were not used for the - * payload remain in rq_pages[] for subsequent reuse. - */ for (i = 0; i < head->rc_page_count; i++) { head->rc_pages[i] = rqstp->rq_pages[i]; rqstp->rq_pages[i] = NULL; From 516403d4d85607fdef3ca41d4a56b54e5566fa9a Mon Sep 17 00:00:00 2001 From: Yang Erkun Date: Wed, 13 May 2026 10:42:52 +0800 Subject: [PATCH 088/106] Revert "NFSD: Defer sub-object cleanup in export put callbacks" This reverts commit 48db892356d6cb80f6942885545de4a6dd8d2a29. Commit 48db892356d6 ("NFSD: Defer sub-object cleanup in export put callbacks") moved path_put() and auth_domain_put() out of svc_export_put() and expkey_put() and behind queue_rcu_work() to close a claimed use-after-free in e_show() and c_show() against ex_path and ex_client->name. Discussion in [1] shows neither the diagnosis nor the remedy survives review. The downstream teardown of both sub-objects is already RCU-deferred. auth_domain_put() reaches svcauth_unix_domain_release(), which frees the unix_domain and its ->name through call_rcu(). path_put() reaches dentry_free(), which frees the dentry through call_rcu(), and prepend_path() is already structured to tolerate concurrent dentry teardown. A reader in cache_seq_start_rcu() therefore observes both sub-objects through the next grace period regardless of whether svc_export_put() runs synchronously, so the synchronous form was never unsafe. The crash signature in the report cited by commit 48db892356d6 ("NFSD: Defer sub-object cleanup in export put callbacks") has a different root cause: a /proc/net/rpc cache file held open across network-namespace exit lets cache_destroy_net() free cd->hash_table while a reader is still walking it. The correct fix pins cd->net for the open fd's lifetime and does not require any deferral inside svc_export_put(). Meanwhile, deferring path_put() out of svc_export_put() reintroduces the regression that commit 69d803c40ede ("nfsd: Revert "nfsd: release svc_expkey/svc_export with rcu_work"") repaired: after "exportfs -r" drops the last cache reference, the mount reference held through ex_path lingers in the workqueue, so a subsequent umount fails with EBUSY. Restore the synchronous path_put() and auth_domain_put() in svc_export_put() and expkey_put() and the call_rcu()/kfree_rcu() free of the containing structures. The unrelated fix for ex_uuid/ex_stats from commit 2530766492ec ("nfsd: fix UAF when access ex_uuid or ex_stats") is preserved. Link: https://lore.kernel.org/all/10019b42-4589-4f9f-8d5b-d8197db1ce3c@huawei.com/ [1] Fixes: 48db892356d6 ("NFSD: Defer sub-object cleanup in export put callbacks") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Tested-by: Alexandr Alexandrov Signed-off-by: Yang Erkun Signed-off-by: Chuck Lever --- fs/nfsd/export.c | 67 ++++++++---------------------------------------- fs/nfsd/export.h | 7 ++--- fs/nfsd/nfsctl.c | 8 +----- 3 files changed, 14 insertions(+), 68 deletions(-) diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c index eb020054f9a3..a47c90f40422 100644 --- a/fs/nfsd/export.c +++ b/fs/nfsd/export.c @@ -39,30 +39,19 @@ * second map contains a reference to the entry in the first map. */ -static struct workqueue_struct *nfsd_export_wq; - #define EXPKEY_HASHBITS 8 #define EXPKEY_HASHMAX (1 << EXPKEY_HASHBITS) #define EXPKEY_HASHMASK (EXPKEY_HASHMAX -1) -static void expkey_release(struct work_struct *work) -{ - struct svc_expkey *key = container_of(to_rcu_work(work), - struct svc_expkey, ek_rwork); - - if (test_bit(CACHE_VALID, &key->h.flags) && - !test_bit(CACHE_NEGATIVE, &key->h.flags)) - path_put(&key->ek_path); - auth_domain_put(key->ek_client); - kfree(key); -} - static void expkey_put(struct kref *ref) { struct svc_expkey *key = container_of(ref, struct svc_expkey, h.ref); - INIT_RCU_WORK(&key->ek_rwork, expkey_release); - queue_rcu_work(nfsd_export_wq, &key->ek_rwork); + if (test_bit(CACHE_VALID, &key->h.flags) && + !test_bit(CACHE_NEGATIVE, &key->h.flags)) + path_put(&key->ek_path); + auth_domain_put(key->ek_client); + kfree_rcu(key, ek_rcu); } static int expkey_upcall(struct cache_detail *cd, struct cache_head *h) @@ -633,13 +622,11 @@ static void export_stats_destroy(struct export_stats *stats) EXP_STATS_COUNTERS_NUM); } -static void svc_export_release(struct work_struct *work) +static void svc_export_release(struct rcu_head *rcu_head) { - struct svc_export *exp = container_of(to_rcu_work(work), - struct svc_export, ex_rwork); + struct svc_export *exp = container_of(rcu_head, struct svc_export, + ex_rcu); - path_put(&exp->ex_path); - auth_domain_put(exp->ex_client); nfsd4_fslocs_free(&exp->ex_fslocs); export_stats_destroy(exp->ex_stats); kfree(exp->ex_stats); @@ -651,8 +638,9 @@ static void svc_export_put(struct kref *ref) { struct svc_export *exp = container_of(ref, struct svc_export, h.ref); - INIT_RCU_WORK(&exp->ex_rwork, svc_export_release); - queue_rcu_work(nfsd_export_wq, &exp->ex_rwork); + path_put(&exp->ex_path); + auth_domain_put(exp->ex_client); + call_rcu(&exp->ex_rcu, svc_export_release); } /** @@ -2195,36 +2183,6 @@ const struct seq_operations nfs_exports_op = { .show = e_show, }; -/** - * nfsd_export_wq_init - allocate the export release workqueue - * - * Called once at module load. The workqueue runs deferred svc_export and - * svc_expkey release work scheduled by queue_rcu_work() in the cache put - * callbacks. - * - * Return values: - * %0: workqueue allocated - * %-ENOMEM: allocation failed - */ -int nfsd_export_wq_init(void) -{ - nfsd_export_wq = alloc_workqueue("nfsd_export", WQ_UNBOUND, 0); - if (!nfsd_export_wq) - return -ENOMEM; - return 0; -} - -/** - * nfsd_export_wq_shutdown - drain and free the export release workqueue - * - * Called once at module unload. Per-namespace teardown in - * nfsd_export_shutdown() has already drained all deferred work. - */ -void nfsd_export_wq_shutdown(void) -{ - destroy_workqueue(nfsd_export_wq); -} - /* * Initialize the exports module. */ @@ -2286,9 +2244,6 @@ nfsd_export_shutdown(struct net *net) cache_unregister_net(nn->svc_expkey_cache, net); cache_unregister_net(nn->svc_export_cache, net); - /* Drain deferred export and expkey release work. */ - rcu_barrier(); - flush_workqueue(nfsd_export_wq); cache_destroy_net(nn->svc_expkey_cache, net); cache_destroy_net(nn->svc_export_cache, net); svcauth_unix_purge(net); diff --git a/fs/nfsd/export.h b/fs/nfsd/export.h index b05399374574..d2b09cd76145 100644 --- a/fs/nfsd/export.h +++ b/fs/nfsd/export.h @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -76,7 +75,7 @@ struct svc_export { u32 ex_layout_types; struct nfsd4_deviceid_map *ex_devid_map; struct cache_detail *cd; - struct rcu_work ex_rwork; + struct rcu_head ex_rcu; unsigned long ex_xprtsec_modes; struct export_stats *ex_stats; }; @@ -93,7 +92,7 @@ struct svc_expkey { u32 ek_fsid[6]; struct path ek_path; - struct rcu_work ek_rwork; + struct rcu_head ek_rcu; }; #define EX_ISSYNC(exp) (!((exp)->ex_flags & NFSEXP_ASYNC)) @@ -111,8 +110,6 @@ __be32 check_nfsd_access(struct svc_export *exp, struct svc_rqst *rqstp, /* * Function declarations */ -int nfsd_export_wq_init(void); -void nfsd_export_wq_shutdown(void); int nfsd_export_init(struct net *); void nfsd_export_shutdown(struct net *); void nfsd_export_flush(struct net *); diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 92f4c333f0ff..d0486f4a47ba 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -2523,12 +2523,9 @@ static int __init init_nfsd(void) if (retval) goto out_free_pnfs; nfsd_lockd_init(); /* lockd->nfsd callbacks */ - retval = nfsd_export_wq_init(); - if (retval) - goto out_free_lockd; retval = register_pernet_subsys(&nfsd_net_ops); if (retval < 0) - goto out_free_export_wq; + goto out_free_lockd; retval = register_cld_notifier(); if (retval) goto out_free_subsys; @@ -2557,8 +2554,6 @@ static int __init init_nfsd(void) unregister_cld_notifier(); out_free_subsys: unregister_pernet_subsys(&nfsd_net_ops); -out_free_export_wq: - nfsd_export_wq_shutdown(); out_free_lockd: nfsd_lockd_shutdown(); nfsd_drc_slab_free(); @@ -2579,7 +2574,6 @@ static void __exit exit_nfsd(void) nfsd4_destroy_laundry_wq(); unregister_cld_notifier(); unregister_pernet_subsys(&nfsd_net_ops); - nfsd_export_wq_shutdown(); nfsd_drc_slab_free(); nfsd_lockd_shutdown(); nfsd4_free_slabs(); From f16a1513452edb532fec81e591c64c320866719c Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 14 May 2026 16:56:04 -0400 Subject: [PATCH 089/106] lockd: Plug nlm_file leak when nlm_do_fopen() fails A client can repeatedly drive nlm_do_fopen() failures by presenting file handles that the underlying export rejects. After kzalloc_obj() succeeds in nlm_lookup_file(), the freshly allocated nlm_file is not yet inserted into nlm_files[]. The nlm_do_fopen() failure path jumps to out_unlock, which releases nlm_file_mutex and returns without freeing the allocation, so each failure leaks one nlm_file. Route the failure through out_free so kfree() runs before the function returns. Fixes: 7f024fcd5c97 ("Keep read and write fds with each nlm_file") Cc: stable@vger.kernel.org Signed-off-by: Chuck Lever --- fs/lockd/svcsubs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index e24bacea7e03..0b81d8db0919 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -166,7 +166,7 @@ nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, nfserr = nlm_do_fopen(rqstp, file, mode); if (nfserr) - goto out_unlock; + goto out_free; hlist_add_head(&file->f_list, &nlm_files[hash]); From 70a38f87bed7f0694fd07988b47b2db1e10d8df3 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 14 May 2026 16:56:06 -0400 Subject: [PATCH 090/106] lockd: Plug nlm_file refcount leak on cached nlm_do_fopen() failure The cached-file path in nlm_lookup_file() reaches the found: label unconditionally, even when nlm_do_fopen() fails. At that label *result and file->f_count are updated before the error is returned. The wrappers nlm3svc_lookup_file() and nlm4svc_lookup_file() then bail out of their switch without copying *result back to their caller, so the proc handler's local nlm_file pointer remains NULL and the cleanup path skips nlm_release_file(). The f_count increment is never released, and nlm_traverse_files() can no longer reap the file because its refcount never returns to zero between requests. Short-circuit the cached path so neither *result nor f_count is touched when nlm_do_fopen() fails on a hashed nlm_file. Fixes: 7f024fcd5c97 ("Keep read and write fds with each nlm_file") Cc: stable@vger.kernel.org Signed-off-by: Chuck Lever --- fs/lockd/svcsubs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index 0b81d8db0919..58b87ec52930 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -150,6 +150,8 @@ nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, mutex_lock(&file->f_mutex); nfserr = nlm_do_fopen(rqstp, file, mode); mutex_unlock(&file->f_mutex); + if (nfserr) + goto out_unlock; goto found; } nlm_debug_print_fh("creating file for", &lock->fh); From 6e4c62caecf792e8a15ad9bc7f371e57c17e3302 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 14 May 2026 16:56:07 -0400 Subject: [PATCH 091/106] lockd: Avoid hashing uninitialized bytes in nlm4svc_lookup_file() file_hash() digests the first LOCKD_FH_HASH_SIZE bytes of nfs_fh.data when bucketing nlm_files[], independent of fh.size. Commit 3de744ee4e45 ("lockd: Use xdrgen XDR functions for the NLMv4 TEST procedure") set .pc_argzero to zero for the converted procedures and moved file-handle population into nlm4svc_lookup_file(), which copies only xdr_lock->fh.len bytes into lock->fh.data. When an NLMv4 client presents a file handle shorter than LOCKD_FH_HASH_SIZE, bytes fh.len..31 retain whatever the argument buffer held from an earlier request. The same wire handle then hashes to different buckets across calls; nlm_lookup_file() misses the existing nlm_file entry, and lock-state lookups fail. Zero only the tail bytes that file_hash() would otherwise consume. Handles of LOCKD_FH_HASH_SIZE or larger already populate every byte that file_hash() reads. Reported-by: Jeff Layton Closes: https://lore.kernel.org/r/5229a9746d723a3f830120c0b966510f75badfc2.camel@kernel.org Fixes: 3de744ee4e45 ("lockd: Use xdrgen XDR functions for the NLMv4 TEST procedure") Signed-off-by: Chuck Lever --- fs/lockd/lockd.h | 8 ++++++++ fs/lockd/svc4proc.c | 3 +++ fs/lockd/svcsubs.c | 3 +-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/fs/lockd/lockd.h b/fs/lockd/lockd.h index 0be0dac59ea2..e418a50c4180 100644 --- a/fs/lockd/lockd.h +++ b/fs/lockd/lockd.h @@ -52,6 +52,14 @@ */ #define LOCKD_DFLT_TIMEO 10 +/* + * Number of leading bytes of nfs_fh.data that file_hash() + * digests when bucketing nlm_files[]. Sized for historical + * NFSv2 handles; nfs_fh.data must be initialized at least + * this far before lookup, regardless of fh.size. + */ +#define LOCKD_FH_HASH_SIZE 32 + /* error codes new to NLMv4 */ #define nlm4_deadlock cpu_to_be32(NLM_DEADLCK) #define nlm4_rofs cpu_to_be32(NLM_ROFS) diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 997f4f437997..78e675470c4b 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -156,6 +156,9 @@ nlm4svc_lookup_file(struct svc_rqst *rqstp, struct nlm_host *host, return nlm_lck_denied_nolocks; lock->fh.size = xdr_lock->fh.len; memcpy(lock->fh.data, xdr_lock->fh.data, xdr_lock->fh.len); + if (xdr_lock->fh.len < LOCKD_FH_HASH_SIZE) + memset(lock->fh.data + xdr_lock->fh.len, 0, + LOCKD_FH_HASH_SIZE - xdr_lock->fh.len); lock->oh.len = xdr_lock->oh.len; lock->oh.data = xdr_lock->oh.data; diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index 58b87ec52930..a0d1a6fbf61e 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -17,7 +17,6 @@ #include #include #include -#include #include "lockd.h" #include "share.h" @@ -67,7 +66,7 @@ static inline unsigned int file_hash(struct nfs_fh *f) { unsigned int tmp=0; int i; - for (i=0; idata[i]; return tmp & (FILE_NRHASH - 1); } From 30d55c8aabb261bc3f427d6b9aae7ef6206063f9 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Mon, 18 May 2026 13:16:36 -0700 Subject: [PATCH 092/106] nfsd: release layout stid on setlease failure nfs4_alloc_stid() publishes the new stid into cl->cl_stateids via idr_alloc_cyclic() under cl_lock before returning to nfsd4_alloc_layout_stateid(). When nfsd4_layout_setlease() then fails, the error path frees the layout stateid directly with kmem_cache_free() without ever calling idr_remove(), leaving the IDR slot pointing at freed slab memory. Any subsequent IDR walker (states_show, client teardown) dereferences the dangling pointer. The correct teardown for an IDR-published stid is nfs4_put_stid(), which removes the IDR slot under cl_lock, dispatches sc_free (nfsd4_free_layout_stateid) to release ls->ls_file via nfsd4_close_layout(), and drops the nfs4_file reference in its tail. A second issue blocks that switch: nfsd4_free_layout_stateid() unconditionally inspects ls->ls_fence_work via delayed_work_pending() under ls_lock, but INIT_DELAYED_WORK(&ls->ls_fence_work, ...) currently runs only after the setlease call. On the setlease-failure path the destructor would touch an uninitialized delayed_work. nfsd4_alloc_layout_stateid() nfs4_alloc_stid() /* idr_alloc_cyclic under cl_lock */ nfsd4_layout_setlease() /* fails */ nfs4_put_stid() nfsd4_free_layout_stateid() delayed_work_pending(&ls->ls_fence_work) /* needs INIT */ nfsd4_close_layout() /* nfsd_file_put(ls->ls_file) */ put_nfs4_file() Fix by hoisting the ls_fenced / ls_fence_delay / INIT_DELAYED_WORK initialization above the nfsd4_layout_setlease() call, and replace the manual nfsd_file_put + put_nfs4_file + kmem_cache_free cleanup with a single nfs4_put_stid(stp). Fixes: c5c707f96fc9 ("nfsd: implement pNFS layout recalls") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs4layouts.c | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/fs/nfsd/nfs4layouts.c b/fs/nfsd/nfs4layouts.c index c550b83f4432..f34320e4c2f4 100644 --- a/fs/nfsd/nfs4layouts.c +++ b/fs/nfsd/nfs4layouts.c @@ -253,10 +253,12 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate, ls->ls_file = find_any_file(fp); BUG_ON(!ls->ls_file); + ls->ls_fenced = false; + ls->ls_fence_delay = 0; + INIT_DELAYED_WORK(&ls->ls_fence_work, nfsd4_layout_fence_worker); + if (nfsd4_layout_setlease(ls)) { - nfsd_file_put(ls->ls_file); - put_nfs4_file(fp); - kmem_cache_free(nfs4_layout_stateid_cache, ls); + nfs4_put_stid(stp); return NULL; } @@ -269,10 +271,6 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate, list_add(&ls->ls_perfile, &fp->fi_lo_states); spin_unlock(&fp->fi_lock); - ls->ls_fenced = false; - ls->ls_fence_delay = 0; - INIT_DELAYED_WORK(&ls->ls_fence_work, nfsd4_layout_fence_worker); - trace_nfsd_layoutstate_alloc(&ls->ls_stid.sc_stateid); return ls; } From 42f5b80dda6b86e424054baf1475df686c403d5c Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 19 May 2026 09:34:21 -0400 Subject: [PATCH 093/106] SUNRPC: Bound-check xdr_buf_to_bvec() stores before writing xdr_buf_to_bvec() writes a bio_vec into the caller's array before testing whether that slot is in range, and the head branch performs the store with no check at all. When the caller's budget is exactly used up, the next store lands one element past the end of the array. The overflow label returns count - 1, which masks the surplus store but cannot undo it. rq_bvec, the array passed by nfsd_vfs_write(), is allocated to exactly rq_maxpages entries with no slack. The OOB store can land in adjacent slab memory; the bv_len and bv_offset fields written there are derived from client-supplied RPC payload sizes. Move the in-range check ahead of the store in the head, page-loop, and tail branches. With the check at the top of each sequence, count is incremented only after a successful store, so the overflow label can return count directly. Reported-by: Chris Mason Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- net/sunrpc/xdr.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index 6bd588dfbfc0..8f52782d8a37 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -152,6 +152,8 @@ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, unsigned int count = 0; if (head->iov_len) { + if (unlikely(count >= bvec_size)) + goto bvec_overflow; bvec_set_virt(bvec++, head->iov_base, head->iov_len); ++count; } @@ -165,25 +167,27 @@ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, while (remaining > 0) { len = min_t(unsigned int, remaining, PAGE_SIZE - offset); + if (unlikely(count >= bvec_size)) + goto bvec_overflow; bvec_set_page(bvec++, *pages++, len, offset); remaining -= len; offset = 0; - if (unlikely(++count > bvec_size)) - goto bvec_overflow; + ++count; } } if (tail->iov_len) { - bvec_set_virt(bvec, tail->iov_base, tail->iov_len); - if (unlikely(++count > bvec_size)) + if (unlikely(count >= bvec_size)) goto bvec_overflow; + bvec_set_virt(bvec, tail->iov_base, tail->iov_len); + ++count; } return count; bvec_overflow: pr_warn_once("%s: bio_vec array overflow\n", __func__); - return count - 1; + return count; } EXPORT_SYMBOL_GPL(xdr_buf_to_bvec); From 18c1cc69886192e33536498289d26dba6894e3d5 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Tue, 19 May 2026 09:34:22 -0400 Subject: [PATCH 094/106] SUNRPC: Return an error from xdr_buf_to_bvec() on overflow xdr_buf_to_bvec() returns a slot count even when the caller's bvec budget is exhausted partway through the xdr_buf. Callers feed that count into iov_iter_bvec() and continue as if the conversion had succeeded, silently sending or writing fewer bytes than the data length declares. For an NFS WRITE the server reports the truncated transfer to the client as full success. The overflow represents an internal invariant violation: a higher layer reserved a bvec budget too small for the xdr_buf it then asked the encoder to convert. That is a server-side fault, not a media I/O failure and not a malformed client argument. Change xdr_buf_to_bvec() to return a signed int and have the overflow label return -ESERVERFAULT. Update the three callers to detect the negative return and fail the request: nfsd_vfs_write() folds the error into host_err, which nfserrno() translates to nfserr_serverfault for the WRITE reply; svc_udp_sendto() and svc_tcp_sendmsg() propagate the error out of the send path. Reported-by: Chris Mason Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/vfs.c | 6 +++++- include/linux/sunrpc/xdr.h | 4 ++-- net/sunrpc/svcsock.c | 14 ++++++++++++-- net/sunrpc/xdr.c | 11 ++++++----- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index ba97e287c007..cba473969429 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1440,7 +1440,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, unsigned long exp_op_flags = 0; unsigned int pflags = current->flags; bool restore_flags = false; - unsigned int nvecs; + int nvecs; trace_nfsd_write_opened(rqstp, fhp, offset, *cnt); @@ -1480,6 +1480,10 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, } nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload); + if (nvecs < 0) { + host_err = nvecs; + goto out_nfserr; + } since = READ_ONCE(file->f_wb_err); if (verf) diff --git a/include/linux/sunrpc/xdr.h b/include/linux/sunrpc/xdr.h index 31971b01d962..b102b4f21e6b 100644 --- a/include/linux/sunrpc/xdr.h +++ b/include/linux/sunrpc/xdr.h @@ -138,8 +138,8 @@ void xdr_terminate_string(const struct xdr_buf *, const u32); size_t xdr_buf_pagecount(const struct xdr_buf *buf); int xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp); void xdr_free_bvec(struct xdr_buf *buf); -unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, - const struct xdr_buf *xdr); +int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, + const struct xdr_buf *xdr); int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset, unsigned int len, struct scatterlist *sg, unsigned int nsg); int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset, diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c index 7be3de1a1aed..c434b6a6637d 100644 --- a/net/sunrpc/svcsock.c +++ b/net/sunrpc/svcsock.c @@ -732,7 +732,7 @@ static int svc_udp_sendto(struct svc_rqst *rqstp) .msg_flags = MSG_SPLICE_PAGES, .msg_controllen = sizeof(buffer), }; - unsigned int count; + int count; int err; svc_udp_release_ctxt(xprt, rqstp->rq_xprt_ctxt); @@ -746,6 +746,10 @@ static int svc_udp_sendto(struct svc_rqst *rqstp) goto out_notconn; count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr); + if (count < 0) { + err = count; + goto out_trace; + } iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec, count, rqstp->rq_res.len); @@ -757,6 +761,7 @@ static int svc_udp_sendto(struct svc_rqst *rqstp) err = sock_sendmsg(svsk->sk_sock, &msg); } +out_trace: trace_svcsock_udp_send(xprt, err); mutex_unlock(&xprt->xpt_mutex); @@ -1237,7 +1242,7 @@ static int svc_tcp_sendmsg(struct svc_sock *svsk, struct svc_rqst *rqstp, struct msghdr msg = { .msg_flags = MSG_SPLICE_PAGES, }; - unsigned int count; + int count; void *buf; int ret; @@ -1253,10 +1258,15 @@ static int svc_tcp_sendmsg(struct svc_sock *svsk, struct svc_rqst *rqstp, count = xdr_buf_to_bvec(svsk->sk_bvec + 1, rqstp->rq_maxpages, &rqstp->rq_res); + if (count < 0) { + ret = count; + goto out; + } iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec, 1 + count, sizeof(marker) + rqstp->rq_res.len); ret = sock_sendmsg(svsk->sk_sock, &msg); +out: page_frag_free(buf); return ret; } diff --git a/net/sunrpc/xdr.c b/net/sunrpc/xdr.c index 8f52782d8a37..fa6a30b5f046 100644 --- a/net/sunrpc/xdr.c +++ b/net/sunrpc/xdr.c @@ -139,13 +139,14 @@ xdr_free_bvec(struct xdr_buf *buf) /** * xdr_buf_to_bvec - Copy components of an xdr_buf into a bio_vec array * @bvec: bio_vec array to populate - * @bvec_size: element count of @bio_vec + * @bvec_size: element count of @bvec * @xdr: xdr_buf to be copied * - * Returns the number of entries consumed in @bvec. + * Returns the number of entries consumed in @bvec on success, or + * -ESERVERFAULT when @xdr does not fit within @bvec_size entries. */ -unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, - const struct xdr_buf *xdr) +int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, + const struct xdr_buf *xdr) { const struct kvec *head = xdr->head; const struct kvec *tail = xdr->tail; @@ -187,7 +188,7 @@ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size, bvec_overflow: pr_warn_once("%s: bio_vec array overflow\n", __func__); - return count; + return -ESERVERFAULT; } EXPORT_SYMBOL_GPL(xdr_buf_to_bvec); From 18d216788bef06332ff8901670ecf1ed8f6eb614 Mon Sep 17 00:00:00 2001 From: Luxiao Xu Date: Thu, 21 May 2026 15:06:32 +0800 Subject: [PATCH 095/106] sunrpc: harden rq_procinfo lifecycle to prevent double-free The svc_release_rqst() function executes the callback inside rqstp->rq_procinfo->pc_release. However, if a worker thread begins processing a new request and encounters an early error path (e.g., unsupported protocol, short frame, or bad auth) before a valid rq_procinfo is installed, a stale release hook can be re-triggered against reused state from the previous RPC, resulting in a double-free or use-after-free vulnerability. Harden the lifecycle of rq_procinfo by: 1. Ensuring svc_release_rqst() always clears rq_procinfo after the optional pc_release() call, regardless of whether the hook exists. 2. Explicitly clearing rq_procinfo at request entry in svc_process() before any early decode or drop paths. 3. Ensuring svc_process_bc() does the same at backchannel entry. This guarantees that error flows will not encounter a non-NULL stale rq_procinfo pointer when there is nothing to release. Fixes: d9adbb6e10bf ("sunrpc: delay pc_release callback until after the reply is sent") Cc: stable@vger.kernel.org Reported-by: Yuan Tan Reported-by: Yifan Wu Reported-by: Juefei Pu Reported-by: Xin Liu Suggested-by: Chuck Lever Reviewed-by: Jeff Layton Signed-off-by: Luxiao Xu Signed-off-by: Ren Wei Signed-off-by: Chuck Lever --- net/sunrpc/svc.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/net/sunrpc/svc.c b/net/sunrpc/svc.c index 576fa42e7abf..ae9ec4bf34f7 100644 --- a/net/sunrpc/svc.c +++ b/net/sunrpc/svc.c @@ -1598,6 +1598,12 @@ static void svc_release_rqst(struct svc_rqst *rqstp) if (procp && procp->pc_release) procp->pc_release(rqstp); + + /* + * A subsequent svc_release_rqst() on this rqstp must not + * re-invoke pc_release against released state. + */ + rqstp->rq_procinfo = NULL; } /** @@ -1616,6 +1622,9 @@ void svc_process(struct svc_rqst *rqstp) svc_xprt_deferred_close(rqstp->rq_xprt); #endif + /* Discard a stale release hook from a previous RPC. */ + rqstp->rq_procinfo = NULL; + /* * Setup response xdr_buf. * Initially it has just one page @@ -1672,6 +1681,7 @@ void svc_process_bc(struct rpc_rqst *req, struct svc_rqst *rqstp) int proc_error; /* Build the svc_rqst used by the common processing routine */ + rqstp->rq_procinfo = NULL; rqstp->rq_xid = req->rq_xid; rqstp->rq_prot = req->rq_xprt->prot; rqstp->rq_bc_net = req->rq_xprt->xprt_net; From 9e18e83b8846a5c3fe13fc8a464b4865d33996c6 Mon Sep 17 00:00:00 2001 From: Guannan Wang Date: Thu, 21 May 2026 16:03:32 +0800 Subject: [PATCH 096/106] NFSD: Fix SECINFO_NO_NAME decode error cleanup nfsd4_decode_secinfo_no_name() currently initializes sin_exp after decoding sin_style. If the XDR stream is truncated, the decoder returns nfserr_bad_xdr before sin_exp is initialized. Since commit 3fdc54646234 ("NFSD: Reduce amount of struct nfsd4_compoundargs that needs clearing"), the inline iops array is not cleared between RPC calls. A failed SECINFO_NO_NAME decode can therefore leave sin_exp holding stale union contents from a previous operation. The error response path still invokes nfsd4_secinfo_no_name_release(), which calls exp_put() on a non-NULL sin_exp. Initialize sin_exp before the first failable decode step, matching nfsd4_decode_secinfo(). Fixes: 3fdc54646234 ("NFSD: Reduce amount of struct nfsd4_compoundargs that needs clearing") Cc: stable@vger.kernel.org Signed-off-by: Guannan Wang Signed-off-by: Chuck Lever --- fs/nfsd/nfs4xdr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4xdr.c b/fs/nfsd/nfs4xdr.c index 20355dc3f1d1..e17488a911f7 100644 --- a/fs/nfsd/nfs4xdr.c +++ b/fs/nfsd/nfs4xdr.c @@ -2008,10 +2008,11 @@ static __be32 nfsd4_decode_secinfo_no_name(struct nfsd4_compoundargs *argp, union nfsd4_op_u *u) { struct nfsd4_secinfo_no_name *sin = &u->secinfo_no_name; + + sin->sin_exp = NULL; if (xdr_stream_decode_u32(argp->xdr, &sin->sin_style) < 0) return nfserr_bad_xdr; - sin->sin_exp = NULL; return nfs_ok; } From a60f25a800846ab8e5a13f8a9d05111f2aee55a7 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 21 May 2026 07:50:21 -0400 Subject: [PATCH 097/106] nfsd: fix dead ACL conflict guard in nfsd4_create nfsd4_create() steals create->cr_dpacl/cr_pacl into the local nfsd_attrs via the designated initializer, then immediately sets the source pointers to NULL. The subsequent conflict guard tests the already-nilled source fields, making it permanently dead code: if (create->cr_acl) { if (create->cr_dpacl || create->cr_pacl) /* always false */ When a client encodes both FATTR4_WORD0_ACL and FATTR4_WORD2_POSIX_{DEFAULT,ACCESS}_ACL in the same CREATE fattr bitmap, nfsd4_acl_to_attr() overwrites attrs.na_pacl/na_dpacl without releasing the originals, leaking two posix_acl slab objects per request. Repeated requests cause unbounded slab exhaustion. Fix by checking attrs.na_dpacl/na_pacl (the stolen values) instead of the nilled create->cr_dpacl/cr_pacl, matching the correct pattern already used in nfsd4_setattr(). Reported-by: Chris Mason Assisted-by: kres:claude-opus-4-6 Fixes: d2ca50606f5f ("NFSD: Add support for POSIX draft ACLs for file creation") Cc: stable@vger.kernel.org Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index ab39ec885440..71bb2489e5a6 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -837,7 +837,7 @@ nfsd4_create(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, goto out_aftermask; if (create->cr_acl) { - if (create->cr_dpacl || create->cr_pacl) { + if (attrs.na_dpacl || attrs.na_pacl) { status = nfserr_inval; goto out_aftermask; } From 0150459b05490b88b7e7378a31550a9e07b5517c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 21 May 2026 09:25:40 -0400 Subject: [PATCH 098/106] nfsd: fix inverted cp_ttl check in async copy reaper nfsd4_async_copy_reaper() is supposed to keep completed async copy state around for NFSD_COPY_INITIAL_TTL (10) laundromat ticks so that OFFLOAD_STATUS can report the result, then reap the state once the countdown expires. The TTL predicate is inverted: `if (--copy->cp_ttl)` is true while ticks remain and false when the counter reaches zero. This causes the copy to be reaped on the very first tick (cp_ttl goes from 10 to 9, which is non-zero) instead of after all 10 ticks elapse. Once reaped, OFFLOAD_STATUS returns NFS4ERR_BAD_STATEID because the copy state has already been freed. Fix by negating the test so that cleanup runs when the TTL expires. Fixes: aa0ebd21df9c ("NFSD: Add nfsd4_copy time-to-live") Cc: stable@vger.kernel.org Reported-by: Chris Mason Assisted-by: kres:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 71bb2489e5a6..14e329cfdfa6 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -1470,7 +1470,7 @@ void nfsd4_async_copy_reaper(struct nfsd_net *nn) list_for_each_safe(pos, next, &clp->async_copies) { copy = list_entry(pos, struct nfsd4_copy, copies); if (test_bit(NFSD4_COPY_F_OFFLOAD_DONE, ©->cp_flags)) { - if (--copy->cp_ttl) { + if (!--copy->cp_ttl) { list_del_init(©->copies); list_add(©->copies, &reaplist); } From e186fa1c057f5eccb22afb1e83e34c0627085868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Wo=C5=BAniak?= Date: Thu, 21 May 2026 17:46:56 +0200 Subject: [PATCH 099/106] nfsd: check get_user() return when reading princhashlen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In __cld_pipe_inprogress_downcall(), the get_user() that reads princhashlen from the userspace cld_msg_v2 buffer does not check its return value. A failing copy leaves princhashlen with uninitialised stack contents, which are then used to drive memdup_user() and stored as princhash.len on the resulting reclaim record. The other get_user() calls in this function all check the return; only this one is missed, which is most likely a copy-paste oversight from when v2 upcalls were introduced. Mirror the existing pattern used a few lines above for namelen. namecopy is declared with __free(kfree) so the early return cleans up the already-allocated buffer automatically. Fixes: 6ee95d1c8991 ("nfsd: add support for upcall version 2") Cc: stable@vger.kernel.org Signed-off-by: Dominik Woźniak Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs4recover.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index b338473d6e52..6ea25a52d2f4 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -718,7 +718,8 @@ __cld_pipe_inprogress_downcall(const struct cld_msg_v2 __user *cmsg, return PTR_ERR(namecopy); name.data = namecopy; name.len = namelen; - get_user(princhashlen, &ci->cc_princhash.cp_len); + if (get_user(princhashlen, &ci->cc_princhash.cp_len)) + return -EFAULT; if (princhashlen > 0) { princhashcopy = memdup_user( &ci->cc_princhash.cp_data, From 24c975bbdd564d7d0ad90294bfa69729830345de Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 21 May 2026 12:37:33 -0400 Subject: [PATCH 100/106] nfsd: fix posix_acl leak and ignored error in nfsd4_create_file nfsd4_create_file() has two bugs in its ACL handling: The return value of nfsd4_acl_to_attr() is silently discarded. When the NFSv4-to-POSIX ACL conversion fails (e.g., -EINVAL for unsupported ACE types), the file is created without any ACL and the client receives NFS4_OK. This violates RFC 7530/8881 which require the server to reject unsupported attributes on CREATE. When start_creating() fails after ACL attributes have been populated in attrs (either via nfsd4_acl_to_attr or via ownership transfer from open->op_dpacl/op_pacl), the function jumps to out_write which skips nfsd_attrs_free(). The posix_acl allocations are leaked. A client can trigger this repeatedly with OPEN(CREATE), ACL attributes, and an invalid filename (e.g., longer than NAME_MAX). Fix both by capturing the nfsd4_acl_to_attr() return value and by changing the early error paths to jump to out instead of out_write. Initialize child to ERR_PTR(-EINVAL) so that end_creating() is safe to call even if start_creating() was never reached. Reported-by: Chris Mason Fixes: 7ab96df840e6 ("VFS/nfsd/cachefiles/ovl: add start_creating() and end_creating()") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs4proc.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/fs/nfsd/nfs4proc.c b/fs/nfsd/nfs4proc.c index 14e329cfdfa6..8561540ab2db 100644 --- a/fs/nfsd/nfs4proc.c +++ b/fs/nfsd/nfs4proc.c @@ -253,7 +253,7 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, .na_iattr = iap, .na_seclabel = &open->op_label, }; - struct dentry *parent, *child; + struct dentry *parent, *child = ERR_PTR(-EINVAL); __u32 v_mtime, v_atime; struct inode *inode; __be32 status; @@ -277,10 +277,14 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, if (open->op_acl) { if (open->op_dpacl || open->op_pacl) { status = nfserr_inval; - goto out_write; + goto out; + } + if (is_create_with_attrs(open)) { + status = nfsd4_acl_to_attr(NF4REG, open->op_acl, + &attrs); + if (status) + goto out; } - if (is_create_with_attrs(open)) - nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs); } else if (is_create_with_attrs(open)) { /* The dpacl and pacl will get released by nfsd_attrs_free(). */ attrs.na_dpacl = open->op_dpacl; @@ -293,7 +297,7 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, &QSTR_LEN(open->op_fname, open->op_fnamelen)); if (IS_ERR(child)) { status = nfserrno(PTR_ERR(child)); - goto out_write; + goto out; } if (d_really_is_negative(child)) { @@ -407,7 +411,6 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp, out: end_creating(child); nfsd_attrs_free(&attrs); -out_write: fh_drop_write(fhp); return status; } From 0853ac544c590880d797b04daa33fcb72b6be0e1 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 21 May 2026 13:51:43 -0400 Subject: [PATCH 101/106] nfsd: fix posix_acl leak on SETACL decode failure nfsaclsvc_decode_setaclargs() and nfs3svc_decode_setaclargs() each call nfs_stream_decode_acl() twice, first for NFS_ACL and then for NFS_DFACL. Each successful call transfers ownership of a freshly allocated posix_acl into argp->acl_access or argp->acl_default. If the first call succeeds but the second fails, the decoder returns false and argp->acl_access is left dangling. ACLPROC2_SETACL.pc_release was wired to nfssvc_release_attrstat and ACLPROC3_SETACL.pc_release was wired to nfs3svc_release_fhandle. Both only call fh_put() and have no knowledge of the ACL fields on argp. The posix_acl_release() pairs sat at the out: labels inside nfsacld_proc_setacl() and nfsd3_proc_setacl(), but svc_process() skips pc_func when pc_decode returns false, so that cleanup is unreachable on decode failure: svc_process_common() pc_decode() /* decode_setaclargs: false */ /* pc_func skipped */ pc_release() /* fh_put only -- ACLs leaked */ The orphaned posix_acl is leaked for the lifetime of the server. Fix by adding nfsaclsvc_release_setacl() and nfs3svc_release_setacl(), which release both argp->acl_access and argp->acl_default in addition to fh_put(), and wiring them as pc_release for their respective SETACL procedures. pc_release runs on every path svc_process() takes after decode, including decode failure, so the posix_acl_release() pairs are removed from the proc functions' out: labels to keep ownership in one place. This matches the existing release_getacl() pattern used by the sibling GETACL procedures. Fixes: a257cdd0e217 ("[PATCH] NFSD: Add server support for NFSv3 ACLs.") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-7 Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs2acl.c | 17 ++++++++++++----- fs/nfsd/nfs3acl.c | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c index 0ac538c76180..76305b86c1a9 100644 --- a/fs/nfsd/nfs2acl.c +++ b/fs/nfsd/nfs2acl.c @@ -131,10 +131,7 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp) resp->status = fh_getattr(fh, &resp->stat); out: - /* argp->acl_{access,default} may have been allocated in - nfssvc_decode_setaclargs. */ - posix_acl_release(argp->acl_access); - posix_acl_release(argp->acl_default); + /* argp->acl_{access,default} are released in nfsaclsvc_release_setacl. */ return rpc_success; out_drop_lock: @@ -310,6 +307,16 @@ static void nfsaclsvc_release_access(struct svc_rqst *rqstp) fh_put(&resp->fh); } +static void nfsaclsvc_release_setacl(struct svc_rqst *rqstp) +{ + struct nfsd3_setaclargs *argp = rqstp->rq_argp; + struct nfsd_attrstat *resp = rqstp->rq_resp; + + fh_put(&resp->fh); + posix_acl_release(argp->acl_access); + posix_acl_release(argp->acl_default); +} + #define ST 1 /* status*/ #define AT 21 /* attributes */ #define pAT (1+AT) /* post attributes - conditional */ @@ -343,7 +350,7 @@ static const struct svc_procedure nfsd_acl_procedures2[5] = { .pc_func = nfsacld_proc_setacl, .pc_decode = nfsaclsvc_decode_setaclargs, .pc_encode = nfssvc_encode_attrstatres, - .pc_release = nfssvc_release_attrstat, + .pc_release = nfsaclsvc_release_setacl, .pc_argsize = sizeof(struct nfsd3_setaclargs), .pc_argzero = sizeof(struct nfsd3_setaclargs), .pc_ressize = sizeof(struct nfsd_attrstat), diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c index 7b5433bd3019..e87731380be8 100644 --- a/fs/nfsd/nfs3acl.c +++ b/fs/nfsd/nfs3acl.c @@ -118,10 +118,7 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp) out_errno: resp->status = nfserrno(error); out: - /* argp->acl_{access,default} may have been allocated in - nfs3svc_decode_setaclargs. */ - posix_acl_release(argp->acl_access); - posix_acl_release(argp->acl_default); + /* argp->acl_{access,default} are released in nfs3svc_release_setacl. */ return rpc_success; } @@ -223,6 +220,16 @@ static void nfs3svc_release_getacl(struct svc_rqst *rqstp) posix_acl_release(resp->acl_default); } +static void nfs3svc_release_setacl(struct svc_rqst *rqstp) +{ + struct nfsd3_setaclargs *argp = rqstp->rq_argp; + struct nfsd3_attrstat *resp = rqstp->rq_resp; + + fh_put(&resp->fh); + posix_acl_release(argp->acl_access); + posix_acl_release(argp->acl_default); +} + #define ST 1 /* status*/ #define AT 21 /* attributes */ #define pAT (1+AT) /* post attributes - conditional */ @@ -256,7 +263,7 @@ static const struct svc_procedure nfsd_acl_procedures3[3] = { .pc_func = nfsd3_proc_setacl, .pc_decode = nfs3svc_decode_setaclargs, .pc_encode = nfs3svc_encode_setaclres, - .pc_release = nfs3svc_release_fhandle, + .pc_release = nfs3svc_release_setacl, .pc_argsize = sizeof(struct nfsd3_setaclargs), .pc_argzero = sizeof(struct nfsd3_setaclargs), .pc_ressize = sizeof(struct nfsd3_attrstat), From 4f988f3a2808fb659f3880c282041ff067acad78 Mon Sep 17 00:00:00 2001 From: Chris Mason Date: Fri, 22 May 2026 09:39:06 -0400 Subject: [PATCH 102/106] sunrpc: pin svc_xprt across the asynchronous TLS handshake callback svc_tcp_handshake() stores the raw svc_xprt pointer in tls_handshake_args.ta_data and submits the request through tls_server_hello_x509(). The handshake core takes only sock_hold(req->hr_sk); nothing references the embedding struct svc_sock that svc_tcp_handshake_done() reaches via container_of(). Two close races leave the in-flight callback writing through a freed svc_sock. svc_sock_free() calls tls_handshake_cancel() and discards its return value: a false return means handshake_complete() has already set HANDSHAKE_F_REQ_COMPLETED but hp_done() may not have finished, yet svc_sock_free() proceeds to kfree(svsk). The cancel-loser fall-through inside svc_tcp_handshake() itself produces the same window: when wait_for_completion_interruptible_timeout() returns <= 0 (timeout or signal) and tls_handshake_cancel() returns false, the function does not drain, returns, and svc_handle_xprt() calls svc_xprt_received(), which clears XPT_BUSY and can drop the last reference. A concurrent close then runs svc_sock_free() while svc_tcp_handshake_done() is still updating xpt_flags and walking svsk->sk_handshake_done. The corruption surfaces as set_bit/clear_bit RMW into the freed xpt_flags slab slot and as complete_all() walking and writing the freed wait_queue_head_t list embedded in sk_handshake_done -- a slab-corruption primitive, not a benign read. The path is reachable on any TLS-enabled NFS server whenever a connection close overlaps the tlshd downcall delivery window; the interruptible wait means signal delivery suffices, not just SVC_HANDSHAKE_TO expiry. Take svc_xprt_get(xprt) immediately before tls_server_hello_x509() so the in-flight callback owns its own reference. Release it on the two edges where the callback is guaranteed not to fire -- submission failure from tls_server_hello_x509() and a successful tls_handshake_cancel() -- and at the tail of svc_tcp_handshake_done() after complete_all(). Fixes: b3cbf98e2fdf ("SUNRPC: Support TLS handshake in the server-side TCP socket code") Cc: stable@vger.kernel.org Signed-off-by: Chris Mason Assisted-by: kres (claude-opus-4-7) [cel: rewrote commit message to describe the actual change] Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- net/sunrpc/svcsock.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c index c434b6a6637d..6a75ac4565db 100644 --- a/net/sunrpc/svcsock.c +++ b/net/sunrpc/svcsock.c @@ -471,6 +471,7 @@ static void svc_tcp_handshake_done(void *data, int status, key_serial_t peerid) } clear_bit(XPT_HANDSHAKE, &xprt->xpt_flags); complete_all(&svsk->sk_handshake_done); + svc_xprt_put(xprt); } /** @@ -494,9 +495,13 @@ static void svc_tcp_handshake(struct svc_xprt *xprt) clear_bit(XPT_TLS_SESSION, &xprt->xpt_flags); init_completion(&svsk->sk_handshake_done); + /* Pin the transport across the asynchronous handshake callback. */ + svc_xprt_get(xprt); + ret = tls_server_hello_x509(&args, GFP_KERNEL); if (ret) { trace_svc_tls_not_started(xprt); + svc_xprt_put(xprt); goto out_failed; } @@ -505,6 +510,7 @@ static void svc_tcp_handshake(struct svc_xprt *xprt) if (ret <= 0) { if (tls_handshake_cancel(sk)) { trace_svc_tls_timed_out(xprt); + svc_xprt_put(xprt); goto out_close; } } From d00e32f84ca1a77cb67a3fbf59f58dada95f5a21 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 22 May 2026 09:39:07 -0400 Subject: [PATCH 103/106] sunrpc: wait for in-flight TLS handshake callback when cancel loses race When wait_for_completion_interruptible_timeout() in svc_tcp_handshake() returns 0 (timeout) or -ERESTARTSYS (signal) and tls_handshake_cancel() then returns false, handshake_complete() has won the cancellation race: it has set HANDSHAKE_F_REQ_COMPLETED and is about to invoke svc_tcp_handshake_done(), but the callback's side effects on xpt_flags and on svsk->sk_handshake_done have not yet committed. The current code reads xpt_flags immediately to decide whether the session succeeded. Two races result. If the callback has executed set_bit(XPT_TLS_SESSION) but not yet clear_bit(XPT_HANDSHAKE), svc_tcp_handshake() sees a session, enqueues the transport, and returns. svc_xprt_received() then clears XPT_BUSY, a worker thread picks the transport up, the dispatcher in svc_handle_xprt() observes XPT_HANDSHAKE still set, and xpo_handshake is invoked a second time. That svc_tcp_handshake() calls init_completion(&svsk->sk_handshake_done) while the original callback concurrently calls complete_all() on it, corrupting the embedded swait_queue. If the callback has set HANDSHAKE_F_REQ_COMPLETED but not yet entered svc_tcp_handshake_done(), svc_tcp_handshake() reads XPT_TLS_SESSION as clear and tears the connection down even though the handshake is about to succeed. Wait for the callback to commit before inspecting xpt_flags. The completion is guaranteed to fire because handshake_complete() invokes svc_tcp_handshake_done() unconditionally once it has set HANDSHAKE_F_REQ_COMPLETED. Fixes: b3cbf98e2fdf ("SUNRPC: Support TLS handshake in the server-side TCP socket code") Cc: stable@vger.kernel.org Reviewed-by: Jeff Layton Signed-off-by: Chuck Lever --- net/sunrpc/svcsock.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c index 6a75ac4565db..50e5e7f5b762 100644 --- a/net/sunrpc/svcsock.c +++ b/net/sunrpc/svcsock.c @@ -513,6 +513,10 @@ static void svc_tcp_handshake(struct svc_xprt *xprt) svc_xprt_put(xprt); goto out_close; } + /* Cancellation lost to handshake_complete(): the + * callback is in flight and should finish quickly. + */ + wait_for_completion(&svsk->sk_handshake_done); } if (!test_bit(XPT_TLS_SESSION, &xprt->xpt_flags)) { From 57aee7a35bb12753057c5b65d72d1f46c0e95b07 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 22 May 2026 10:36:14 -0400 Subject: [PATCH 104/106] nfsd: avoid leaking pre-allocated openowner on unconfirmed retry race When find_or_alloc_open_stateowner() encounters an unconfirmed owner, it calls release_openowner() and sets oo = NULL. Control then falls through past the `if (oo)` guard -- which would have freed any pre-allocated `new` -- and unconditionally executes `new = alloc_stateowner(...)`. If `new` was already allocated on a prior iteration, the pointer is silently overwritten and the previous allocation (slab object + owner name buffer) is leaked. This requires a race: two NFSv4.0 OPEN threads with the same owner string, where a concurrent thread inserts a new unconfirmed owner into the hash between retry iterations. The window is narrow but repeatable under adversarial conditions. Fix by adding `goto retry` after `oo = NULL` so the already-allocated `new` is reused on the next iteration rather than overwritten. Reported-by: Chris Mason Fixes: 23df17788c62 ("nfsd: perform all find_openstateowner_str calls in the one place.") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/nfs4state.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 2cf021b202a6..a42f34842d77 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -5276,6 +5276,7 @@ find_or_alloc_open_stateowner(unsigned int strhashval, struct nfsd4_open *open, /* Replace unconfirmed owners without checking for replay. */ release_openowner(oo); oo = NULL; + goto retry; } if (oo) { if (new) From 2090b05803faab8a9fa62fbff871007862cac1b7 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 22 May 2026 12:44:19 -0400 Subject: [PATCH 105/106] nfsd: reset write verifier on deferred writeback errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nfsd_vfs_write() and nfsd_commit() both call filemap_check_wb_err() to detect deferred writeback errors, but neither rotates the server's write verifier (nn->writeverf) when this check fails. Every other durable-storage-failure path in these functions calls commit_reset_write_verifier() before returning an error. The missing rotation means clients holding UNSTABLE write data under the current verifier will COMMIT, receive the unchanged verifier back, and conclude their data is durable — silently dropping data that failed writeback. This violates the UNSTABLE+COMMIT durability contract (RFC 1813 §3.3.7, RFC 8881 §18.32). Add commit_reset_write_verifier() calls at both filemap_check_wb_err() error sites, matching the pattern used by adjacent error paths in the same functions. The helper already filters -EAGAIN and -ESTALE internally, so the calls are unconditionally safe. Reported-by: Chris Mason Fixes: 555dbf1a9aac ("nfsd: Replace use of rwsem with errseq_t") Cc: stable@vger.kernel.org Assisted-by: kres:claude-opus-4-6 Signed-off-by: Jeff Layton Signed-off-by: Chuck Lever --- fs/nfsd/vfs.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index cba473969429..7e6468bdc723 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1513,8 +1513,10 @@ nfsd_vfs_write(struct svc_rqst *rqstp, struct svc_fh *fhp, nfsd_stats_io_write_add(nn, exp, *cnt); fsnotify_modify(file); host_err = filemap_check_wb_err(file->f_mapping, since); - if (host_err < 0) + if (host_err < 0) { + commit_reset_write_verifier(nn, rqstp, host_err); goto out_nfserr; + } if (stable && fhp->fh_use_wgather) { host_err = wait_for_concurrent_writes(file); @@ -1694,6 +1696,8 @@ nfsd_commit(struct svc_rqst *rqstp, struct svc_fh *fhp, struct nfsd_file *nf, nfsd_copy_write_verifier(verf, nn); err2 = filemap_check_wb_err(nf->nf_file->f_mapping, since); + if (err2 < 0) + commit_reset_write_verifier(nn, rqstp, err2); err = nfserrno(err2); break; case -EINVAL: From e5248a7426030db1e126363f72afdb3b71339a5c Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 22 May 2026 14:13:57 -0400 Subject: [PATCH 106/106] svcrdma: wake sq waiters when the transport closes Threads parked in svc_rdma_sq_wait() on sc_sq_ticket_wait or sc_send_wait can hang indefinitely in TASK_UNINTERRUPTIBLE state across transport teardown, pinning svc_xprt references and blocking svc_rdma_free(). The close path sets XPT_CLOSE before invoking xpo_detach and both wait_event predicates include an XPT_CLOSE term, but the predicates are re-evaluated only on wakeup. sc_sq_ticket_wait has no completion-driven wake path; it is advanced solely by the chained ticket handoff inside svc_rdma_sq_wait() itself. Without an explicit wake at close, parked threads never observe XPT_CLOSE, hold their svc_xprt_get reference forever, and svc_rdma_free() blocks on xpt_ref dropping to zero. Two close entry points reach this transport. Local teardown runs svc_rdma_detach() from svc_handle_xprt() -> svc_delete_xprt() -> xpo_detach() on a worker thread. A remote disconnect arrives at svc_rdma_cma_handler(), which calls svc_xprt_deferred_close(): that sets XPT_CLOSE and enqueues the transport but does not access either RDMA waitqueue, so a worker already parked in svc_rdma_sq_wait() never re-evaluates its predicate. With every worker parked on this transport, no thread is available to run the local teardown either, and the wake site there is unreachable. Introduce svc_rdma_xprt_deferred_close(), a thin svcrdma wrapper that calls svc_xprt_deferred_close() and then wakes both sc_sq_ticket_wait and sc_send_wait. Convert the svcrdma producers that called svc_xprt_deferred_close() directly: svc_rdma_cma_handler(), qp_event_handler(), svc_rdma_post_send_err(), svc_rdma_wc_send(), the sendto drop path, the rw completion error paths, and the recvfrom flush and read-list error paths. Wake both waitqueues from svc_rdma_detach() as well. The synchronous svc_xprt_close() path (backchannel ENOTCONN, device removal via svc_rdma_xprt_done) reaches detach without flowing through svc_xprt_deferred_close() and therefore does not invoke the new helper. Fixes: ccc89b9d1ed2 ("svcrdma: Add fair queuing for Send Queue access") Cc: stable@vger.kernel.org Assisted-by: kres (claude-opus-4-7) Signed-off-by: Chris Mason [ cel: add svc_rdma_xprt_deferred_close() to complete the fix ] Signed-off-by: Chuck Lever --- include/linux/sunrpc/svc_rdma.h | 1 + net/sunrpc/xprtrdma/svc_rdma_recvfrom.c | 4 ++-- net/sunrpc/xprtrdma/svc_rdma_rw.c | 6 ++--- net/sunrpc/xprtrdma/svc_rdma_sendto.c | 6 ++--- net/sunrpc/xprtrdma/svc_rdma_transport.c | 30 ++++++++++++++++++++++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/include/linux/sunrpc/svc_rdma.h b/include/linux/sunrpc/svc_rdma.h index 4ba39f07371d..5aadb47b3b0e 100644 --- a/include/linux/sunrpc/svc_rdma.h +++ b/include/linux/sunrpc/svc_rdma.h @@ -328,6 +328,7 @@ extern int svc_rdma_result_payload(struct svc_rqst *rqstp, unsigned int offset, unsigned int length); /* svc_rdma_transport.c */ +extern void svc_rdma_xprt_deferred_close(struct svcxprt_rdma *rdma); extern struct svc_xprt_class svc_rdma_class; #ifdef CONFIG_SUNRPC_BACKCHANNEL extern struct svc_xprt_class svc_rdma_bc_class; diff --git a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c index 19503a12d0a2..fe9bf0371b6e 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c +++ b/net/sunrpc/xprtrdma/svc_rdma_recvfrom.c @@ -383,7 +383,7 @@ static void svc_rdma_wc_receive(struct ib_cq *cq, struct ib_wc *wc) trace_svcrdma_wc_recv_err(wc, &ctxt->rc_cid); dropped: svc_rdma_recv_ctxt_put(rdma, ctxt); - svc_xprt_deferred_close(&rdma->sc_xprt); + svc_rdma_xprt_deferred_close(rdma); } /** @@ -1010,7 +1010,7 @@ int svc_rdma_recvfrom(struct svc_rqst *rqstp) if (ret == -EINVAL) svc_rdma_send_error(rdma_xprt, ctxt, ret); svc_rdma_recv_ctxt_put(rdma_xprt, ctxt); - svc_xprt_deferred_close(xprt); + svc_rdma_xprt_deferred_close(rdma_xprt); return ret; } return 0; diff --git a/net/sunrpc/xprtrdma/svc_rdma_rw.c b/net/sunrpc/xprtrdma/svc_rdma_rw.c index 13554793b039..f7fd22cc4a59 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_rw.c +++ b/net/sunrpc/xprtrdma/svc_rdma_rw.c @@ -304,7 +304,7 @@ static void svc_rdma_reply_done(struct ib_cq *cq, struct ib_wc *wc) trace_svcrdma_wc_reply_err(wc, &cc->cc_cid); } - svc_xprt_deferred_close(&rdma->sc_xprt); + svc_rdma_xprt_deferred_close(rdma); } /** @@ -336,7 +336,7 @@ static void svc_rdma_write_done(struct ib_cq *cq, struct ib_wc *wc) * some of the outgoing RPC message. Signal the loss * to the client by closing the connection. */ - svc_xprt_deferred_close(&rdma->sc_xprt); + svc_rdma_xprt_deferred_close(rdma); } /** @@ -381,7 +381,7 @@ static void svc_rdma_wc_read_done(struct ib_cq *cq, struct ib_wc *wc) */ svc_rdma_cc_release(rdma, cc, DMA_FROM_DEVICE); svc_rdma_recv_ctxt_put(rdma, ctxt); - svc_xprt_deferred_close(&rdma->sc_xprt); + svc_rdma_xprt_deferred_close(rdma); } /* diff --git a/net/sunrpc/xprtrdma/svc_rdma_sendto.c b/net/sunrpc/xprtrdma/svc_rdma_sendto.c index eceefd21bec8..7f6d17bf8c1f 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_sendto.c +++ b/net/sunrpc/xprtrdma/svc_rdma_sendto.c @@ -438,7 +438,7 @@ int svc_rdma_post_send_err(struct svcxprt_rdma *rdma, int sqecount, int ret) { trace_svcrdma_sq_post_err(rdma, cid, ret); - svc_xprt_deferred_close(&rdma->sc_xprt); + svc_rdma_xprt_deferred_close(rdma); /* If even one WR was posted, a Send completion will * return the reserved SQ slots. @@ -480,7 +480,7 @@ static void svc_rdma_wc_send(struct ib_cq *cq, struct ib_wc *wc) else trace_svcrdma_wc_send_flush(wc, &ctxt->sc_cid); svc_rdma_send_ctxt_put(rdma, ctxt); - svc_xprt_deferred_close(&rdma->sc_xprt); + svc_rdma_xprt_deferred_close(rdma); } /** @@ -1201,7 +1201,7 @@ int svc_rdma_sendto(struct svc_rqst *rqstp) svc_rdma_send_ctxt_put(rdma, sctxt); drop_connection: trace_svcrdma_send_err(rqstp, ret); - svc_xprt_deferred_close(&rdma->sc_xprt); + svc_rdma_xprt_deferred_close(rdma); return -ENOTCONN; } diff --git a/net/sunrpc/xprtrdma/svc_rdma_transport.c b/net/sunrpc/xprtrdma/svc_rdma_transport.c index f99cd6177504..7ca71741106b 100644 --- a/net/sunrpc/xprtrdma/svc_rdma_transport.c +++ b/net/sunrpc/xprtrdma/svc_rdma_transport.c @@ -98,10 +98,27 @@ struct svc_xprt_class svc_rdma_class = { .xcl_ident = XPRT_TRANSPORT_RDMA, }; +/** + * svc_rdma_xprt_deferred_close - Close an RDMA transport (deferred) + * @rdma: transport to close + */ +void svc_rdma_xprt_deferred_close(struct svcxprt_rdma *rdma) +{ + svc_xprt_deferred_close(&rdma->sc_xprt); + + /* Release parked sc_sq_ticket_wait and sc_send_wait waiters. + * Once XPT_CLOSE is observed each returns -ENOTCONN. + */ + wake_up_all(&rdma->sc_sq_ticket_wait); + wake_up_all(&rdma->sc_send_wait); +} + /* QP event handler */ static void qp_event_handler(struct ib_event *event, void *context) { struct svc_xprt *xprt = context; + struct svcxprt_rdma *rdma = + container_of(xprt, struct svcxprt_rdma, sc_xprt); trace_svcrdma_qp_error(event, (struct sockaddr *)&xprt->xpt_remote); switch (event->event) { @@ -119,7 +136,7 @@ static void qp_event_handler(struct ib_event *event, void *context) case IB_EVENT_QP_ACCESS_ERR: case IB_EVENT_DEVICE_FATAL: default: - svc_xprt_deferred_close(xprt); + svc_rdma_xprt_deferred_close(rdma); break; } } @@ -341,7 +358,7 @@ static int svc_rdma_cma_handler(struct rdma_cm_id *cma_id, svc_xprt_enqueue(xprt); break; case RDMA_CM_EVENT_DISCONNECTED: - svc_xprt_deferred_close(xprt); + svc_rdma_xprt_deferred_close(rdma); break; default: break; @@ -598,6 +615,15 @@ static void svc_rdma_detach(struct svc_xprt *xprt) container_of(xprt, struct svcxprt_rdma, sc_xprt); rdma_disconnect(rdma->sc_cm_id); + + /* + * Most close paths go through svc_rdma_xprt_deferred_close(), + * which wakes the SQ waitqueues. svc_xprt_close() reaches + * detach without that helper, so wake any threads parked in + * svc_rdma_sq_wait() here as well. + */ + wake_up_all(&rdma->sc_sq_ticket_wait); + wake_up_all(&rdma->sc_send_wait); } /**