]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
NFSD: Add a key for signing filehandles
authorBenjamin Coddington <bcodding@hammerspace.com>
Wed, 25 Feb 2026 12:51:36 +0000 (07:51 -0500)
committerChuck Lever <chuck.lever@oracle.com>
Mon, 30 Mar 2026 01:25:09 +0000 (21:25 -0400)
A future patch will enable NFSD to sign filehandles by appending a Message
Authentication Code(MAC).  To do this, NFSD requires a secret 128-bit key
that can persist across reboots.  A persisted key allows the server to
accept filehandles after a restart.  Enable NFSD to be configured with this
key via the netlink interface.

Link: https://lore.kernel.org/linux-nfs/cover.1772022373.git.bcodding@hammerspace.com
Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Documentation/netlink/specs/nfsd.yaml
fs/nfsd/netlink.c
fs/nfsd/netns.h
fs/nfsd/nfsctl.c
fs/nfsd/trace.h
include/uapi/linux/nfsd_netlink.h

index f87b5a05e5e987a2dfc474468ddadb5e1f935328..8ab43c8253b2e83bcc178c3f4fe8c41c2997d153 100644 (file)
@@ -81,6 +81,11 @@ attribute-sets:
       -
         name: min-threads
         type: u32
+      -
+        name: fh-key
+        type: binary
+        checks:
+            exact-len: 16
   -
     name: version
     attributes:
@@ -163,6 +168,7 @@ operations:
             - leasetime
             - scope
             - min-threads
+            - fh-key
     -
       name: threads-get
       doc: get the maximum number of running threads
index 887525964451e640304371e33aa4f415b4ff2848..81c943345d13db849483bf0d6773458115ff0134 100644 (file)
@@ -24,12 +24,13 @@ const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = {
 };
 
 /* NFSD_CMD_THREADS_SET - do */
-static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_MIN_THREADS + 1] = {
+static const struct nla_policy nfsd_threads_set_nl_policy[NFSD_A_SERVER_FH_KEY + 1] = {
        [NFSD_A_SERVER_THREADS] = { .type = NLA_U32, },
        [NFSD_A_SERVER_GRACETIME] = { .type = NLA_U32, },
        [NFSD_A_SERVER_LEASETIME] = { .type = NLA_U32, },
        [NFSD_A_SERVER_SCOPE] = { .type = NLA_NUL_STRING, },
        [NFSD_A_SERVER_MIN_THREADS] = { .type = NLA_U32, },
+       [NFSD_A_SERVER_FH_KEY] = NLA_POLICY_EXACT_LEN(16),
 };
 
 /* NFSD_CMD_VERSION_SET - do */
@@ -58,7 +59,7 @@ static const struct genl_split_ops nfsd_nl_ops[] = {
                .cmd            = NFSD_CMD_THREADS_SET,
                .doit           = nfsd_nl_threads_set_doit,
                .policy         = nfsd_threads_set_nl_policy,
-               .maxattr        = NFSD_A_SERVER_MIN_THREADS,
+               .maxattr        = NFSD_A_SERVER_FH_KEY,
                .flags          = GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
        },
        {
index 3a89d4708e8a9aa2e7ab3f84b81882eaa3b21446..6ad3fe5d7e1248dfbc9f8b67a5ab957eb1978e54 100644 (file)
@@ -227,6 +227,7 @@ struct nfsd_net {
        spinlock_t              local_clients_lock;
        struct list_head        local_clients;
 #endif
+       siphash_key_t           *fh_key;
 };
 
 /* Simple check to find out if a given net was properly initialized */
index 0bf01ae411c5639704f7ee40e435019a35765c87..20ec00f323b490e397c8d6907e7511bb24f94510 100644 (file)
@@ -1581,6 +1581,32 @@ out_unlock:
        return ret;
 }
 
+/**
+ * nfsd_nl_fh_key_set - helper to copy fh_key from userspace
+ * @attr: nlattr NFSD_A_SERVER_FH_KEY
+ * @nn: nfsd_net
+ *
+ * Callers should hold nfsd_mutex, returns 0 on success or negative errno.
+ * Callers must ensure the server is shut down (sv_nrthreads == 0),
+ * userspace documentation asserts the key may only be set when the server
+ * is not running.
+ */
+static int nfsd_nl_fh_key_set(const struct nlattr *attr, struct nfsd_net *nn)
+{
+       siphash_key_t *fh_key = nn->fh_key;
+
+       if (!fh_key) {
+               fh_key = kmalloc(sizeof(siphash_key_t), GFP_KERNEL);
+               if (!fh_key)
+                       return -ENOMEM;
+               nn->fh_key = fh_key;
+       }
+
+       fh_key->key[0] = get_unaligned_le64(nla_data(attr));
+       fh_key->key[1] = get_unaligned_le64(nla_data(attr) + 8);
+       return 0;
+}
+
 /**
  * nfsd_nl_threads_set_doit - set the number of running threads
  * @skb: reply buffer
@@ -1622,7 +1648,8 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info)
 
        if (info->attrs[NFSD_A_SERVER_GRACETIME] ||
            info->attrs[NFSD_A_SERVER_LEASETIME] ||
-           info->attrs[NFSD_A_SERVER_SCOPE]) {
+           info->attrs[NFSD_A_SERVER_SCOPE] ||
+           info->attrs[NFSD_A_SERVER_FH_KEY]) {
                ret = -EBUSY;
                if (nn->nfsd_serv && nn->nfsd_serv->sv_nrthreads)
                        goto out_unlock;
@@ -1651,6 +1678,14 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info)
                attr = info->attrs[NFSD_A_SERVER_SCOPE];
                if (attr)
                        scope = nla_data(attr);
+
+               attr = info->attrs[NFSD_A_SERVER_FH_KEY];
+               if (attr) {
+                       ret = nfsd_nl_fh_key_set(attr, nn);
+                       trace_nfsd_ctl_fh_key_set((const char *)nn->fh_key, ret);
+                       if (ret)
+                               goto out_unlock;
+               }
        }
 
        attr = info->attrs[NFSD_A_SERVER_MIN_THREADS];
@@ -2237,6 +2272,7 @@ static __net_exit void nfsd_net_exit(struct net *net)
 {
        struct nfsd_net *nn = net_generic(net, nfsd_net_id);
 
+       kfree_sensitive(nn->fh_key);
        nfsd_proc_stat_shutdown(net);
        percpu_counter_destroy_many(nn->counter, NFSD_STATS_COUNTERS_NUM);
        nfsd_idmap_shutdown(net);
index d1d0b0dd054588a8c20e3386356dfa4e9632b8e0..185a998996a03f4826c7e138465cfaa2be93e130 100644 (file)
@@ -2240,6 +2240,28 @@ TRACE_EVENT(nfsd_end_grace,
        )
 );
 
+TRACE_EVENT(nfsd_ctl_fh_key_set,
+       TP_PROTO(
+               const char *key,
+               int result
+       ),
+       TP_ARGS(key, result),
+       TP_STRUCT__entry(
+               __field(u32, key_hash)
+               __field(int, result)
+       ),
+       TP_fast_assign(
+               if (key)
+                       __entry->key_hash = ~crc32_le(0xFFFFFFFF, key, 16);
+               else
+                       __entry->key_hash = 0;
+               __entry->result = result;
+       ),
+       TP_printk("key=0x%08x result=%d",
+               __entry->key_hash, __entry->result
+       )
+);
+
 DECLARE_EVENT_CLASS(nfsd_copy_class,
        TP_PROTO(
                const struct nfsd4_copy *copy
index e9efbc9e63d83ed25fcd790b7a877c0023638f15..97c7447f4d14df97c1cba8cdf1f24fba0a7918b3 100644 (file)
@@ -36,6 +36,7 @@ enum {
        NFSD_A_SERVER_LEASETIME,
        NFSD_A_SERVER_SCOPE,
        NFSD_A_SERVER_MIN_THREADS,
+       NFSD_A_SERVER_FH_KEY,
 
        __NFSD_A_SERVER_MAX,
        NFSD_A_SERVER_MAX = (__NFSD_A_SERVER_MAX - 1)