public inbox for linux-nfs@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/2] nfs-utils: signed filehandle support
@ 2026-02-06 15:14 Benjamin Coddington
  2026-02-06 15:15 ` [PATCH v4 1/2] exportfs: Add support for export option sign_fh Benjamin Coddington
  2026-02-06 15:15 ` [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key Benjamin Coddington
  0 siblings, 2 replies; 6+ messages in thread
From: Benjamin Coddington @ 2026-02-06 15:14 UTC (permalink / raw)
  To: Steve Dickson, Benjamin Coddington
  Cc: linux-nfs, Chuck Lever, NeilBrown, Jeff Layton

Here are two patches allowing userspace to set a secret key for kNFSD to
sign filehandles, and also set the option to sign filehandles for an
export.

The secret key passed to the server is the first 128 bits of a sha1 hash of
the contents of a file configured via the nfs.conf server section
"fh_key_file".  Exports that have the option "sign_fh" set will cause the
server to use this key to append an 8-byte siphash of the filehandle onto
each filehandle.

This version of the userspace patches correspond with the v4 of the kernel
changes which have been posted here:
https://lore.kernel.org/linux-nfs/cover.1770390036.git.bcodding@hammerspace.com

This work is based on a branch that includes Jeff Layton's patch for
min-threads:
https://lore.kernel.org/linux-nfs/20260112-minthreads-v1-1-30c5f4113720@kernel.org/

.. and Jeff Layton's v2 series to fix up userspace handling of kernels that
may not support the most recent netlink attributes:
https://lore.kernel.org/linux-nfs/20260204-minthreads-v2-0-a7eba34201e9@kernel.org/

Comments and critique welcomed.

Benjamin Coddington (2):
  exportfs: Add support for export option sign_fh
  nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key

 nfs.conf                     |  1 +
 support/include/nfs/export.h |  2 +-
 support/include/nfslib.h     |  2 +
 support/nfs/Makefile.am      |  4 +-
 support/nfs/exports.c        |  4 ++
 support/nfs/fh_key_file.c    | 79 ++++++++++++++++++++++++++++++++++++
 systemd/nfs.conf.man         |  1 +
 utils/exportfs/exportfs.c    |  2 +
 utils/exportfs/exports.man   |  9 ++++
 utils/nfsd/nfssvc.h          |  1 +
 utils/nfsdctl/nfsd_netlink.h |  1 +
 utils/nfsdctl/nfsdctl.c      | 39 ++++++++++++++++--
 12 files changed, 138 insertions(+), 7 deletions(-)
 create mode 100644 support/nfs/fh_key_file.c


base-commit: 8f54511aefe1455161a6c4406ed8c770139f61e3
prerequisite-patch-id: b0d57152c98d360daa9a71e6fa9759b7eb9de348
prerequisite-patch-id: 99680869954aef9f878c24ec9ee1302ab7f24b1a
prerequisite-patch-id: 2ab31271461352d11bcce760e45573f9e6459553
prerequisite-patch-id: 22c392c9dba2e63916af6cd8fbf4e9d6bce9d01c
prerequisite-patch-id: 50f6f48932426f89060eb77de58718baa80b979f
-- 
2.50.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v4 1/2] exportfs: Add support for export option sign_fh
  2026-02-06 15:14 [PATCH v4 0/2] nfs-utils: signed filehandle support Benjamin Coddington
@ 2026-02-06 15:15 ` Benjamin Coddington
  2026-02-06 15:15 ` [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key Benjamin Coddington
  1 sibling, 0 replies; 6+ messages in thread
From: Benjamin Coddington @ 2026-02-06 15:15 UTC (permalink / raw)
  To: Steve Dickson, Benjamin Coddington
  Cc: linux-nfs, Chuck Lever, NeilBrown, Jeff Layton

If configured with "sign_fh", exports will be flagged to signal that
filehandles should be signed with a Message Authentication Code (MAC).

Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 support/include/nfs/export.h | 2 +-
 support/nfs/exports.c        | 4 ++++
 utils/exportfs/exportfs.c    | 2 ++
 utils/exportfs/exports.man   | 9 +++++++++
 4 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/support/include/nfs/export.h b/support/include/nfs/export.h
index be5867cffc3c..ef3f3e7ea684 100644
--- a/support/include/nfs/export.h
+++ b/support/include/nfs/export.h
@@ -19,7 +19,7 @@
 #define NFSEXP_GATHERED_WRITES	0x0020
 #define NFSEXP_NOREADDIRPLUS	0x0040
 #define NFSEXP_SECURITY_LABEL	0x0080
-/* 0x100 unused */
+#define NFSEXP_SIGN_FH		0x0100
 #define NFSEXP_NOHIDE		0x0200
 #define NFSEXP_NOSUBTREECHECK	0x0400
 #define NFSEXP_NOAUTHNLM	0x0800
diff --git a/support/nfs/exports.c b/support/nfs/exports.c
index 21ec6486ba3d..6b4ca87ee957 100644
--- a/support/nfs/exports.c
+++ b/support/nfs/exports.c
@@ -310,6 +310,8 @@ putexportent(struct exportent *ep)
 		fprintf(fp, "nordirplus,");
 	if (ep->e_flags & NFSEXP_SECURITY_LABEL)
 		fprintf(fp, "security_label,");
+	if (ep->e_flags & NFSEXP_SIGN_FH)
+		fprintf(fp, "sign_fh,");
 	fprintf(fp, "%spnfs,", (ep->e_flags & NFSEXP_PNFS)? "" : "no_");
 	if (ep->e_flags & NFSEXP_FSID) {
 		fprintf(fp, "fsid=%d,", ep->e_fsid);
@@ -676,6 +678,8 @@ parseopts(char *cp, struct exportent *ep, int *had_subtree_opt_ptr)
 			setflags(NFSEXP_NOREADDIRPLUS, active, ep);
 		else if (!strcmp(opt, "security_label"))
 			setflags(NFSEXP_SECURITY_LABEL, active, ep);
+		else if (!strcmp(opt, "sign_fh"))
+			setflags(NFSEXP_SIGN_FH, active, ep);
 		else if (!strcmp(opt, "nohide"))
 			setflags(NFSEXP_NOHIDE, active, ep);
 		else if (!strcmp(opt, "hide"))
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
index 748c38e3e966..54ce62c5ce9a 100644
--- a/utils/exportfs/exportfs.c
+++ b/utils/exportfs/exportfs.c
@@ -718,6 +718,8 @@ dump(int verbose, int export_format)
 				c = dumpopt(c, "nordirplus");
 			if (ep->e_flags & NFSEXP_SECURITY_LABEL)
 				c = dumpopt(c, "security_label");
+			if (ep->e_flags & NFSEXP_SIGN_FH)
+				c = dumpopt(c, "sign_fh");
 			if (ep->e_flags & NFSEXP_NOACL)
 				c = dumpopt(c, "no_acl");
 			if (ep->e_flags & NFSEXP_PNFS)
diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man
index efbc6ef65c5f..7f0278fb8d33 100644
--- a/utils/exportfs/exports.man
+++ b/utils/exportfs/exports.man
@@ -351,6 +351,15 @@ file.  If you put neither option,
 .B exportfs
 will warn you that the change has occurred.
 
+.TP
+.IR sign_fh
+This option enforces signing filehandles on the export.  If the server has
+been configured with a secret key for such purpose, filehandles will include
+a hash to verify the filehandle was created by the server in order to guard
+against filehandle guessing attacks which can bypass path-name based access
+restrictions.  Note that for NFSv3 some exported filesystems may exceed the
+maximum filehandle size when the signing hash is added.
+
 .TP
 .IR insecure_locks
 .TP
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key
  2026-02-06 15:14 [PATCH v4 0/2] nfs-utils: signed filehandle support Benjamin Coddington
  2026-02-06 15:15 ` [PATCH v4 1/2] exportfs: Add support for export option sign_fh Benjamin Coddington
@ 2026-02-06 15:15 ` Benjamin Coddington
  2026-02-06 15:58   ` Jeff Layton
  1 sibling, 1 reply; 6+ messages in thread
From: Benjamin Coddington @ 2026-02-06 15:15 UTC (permalink / raw)
  To: Steve Dickson, Benjamin Coddington
  Cc: linux-nfs, Chuck Lever, NeilBrown, Jeff Layton

If fh-key-file=<path> is set in the nfsd section of nfs.conf, the "nfsdctl
autostart" command will hash the contents of the file with libuuid's
uuid_generate_sha1 and send the first 16 bytes into the kernel on
NFSD_A_SERVER_FH_KEY.

A compatible kernel can use this key to sign filehandles.

Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
---
 nfs.conf                     |  1 +
 support/include/nfslib.h     |  2 +
 support/nfs/Makefile.am      |  4 +-
 support/nfs/fh_key_file.c    | 79 ++++++++++++++++++++++++++++++++++++
 systemd/nfs.conf.man         |  1 +
 utils/nfsd/nfssvc.h          |  1 +
 utils/nfsdctl/nfsd_netlink.h |  1 +
 utils/nfsdctl/nfsdctl.c      | 39 ++++++++++++++++--
 8 files changed, 122 insertions(+), 6 deletions(-)
 create mode 100644 support/nfs/fh_key_file.c

diff --git a/nfs.conf b/nfs.conf
index 3cca68c3530d..39068c19d7df 100644
--- a/nfs.conf
+++ b/nfs.conf
@@ -76,6 +76,7 @@
 # vers4.2=y
 rdma=y
 rdma-port=20049
+# fh-key-file=/etc/nfs_fh.key
 
 [statd]
 # debug=0
diff --git a/support/include/nfslib.h b/support/include/nfslib.h
index eff2a486307f..c8601a156cba 100644
--- a/support/include/nfslib.h
+++ b/support/include/nfslib.h
@@ -22,6 +22,7 @@
 #include <paths.h>
 #include <rpcsvc/nfs_prot.h>
 #include <nfs/nfs.h>
+#include <uuid/uuid.h>
 #include "xlog.h"
 
 #ifndef _PATH_EXPORTS
@@ -132,6 +133,7 @@ struct rmtabent *	fgetrmtabent(FILE *fp, int log, long *pos);
 void			fputrmtabent(FILE *fp, struct rmtabent *xep, long *pos);
 void			fendrmtabent(FILE *fp);
 void			frewindrmtabent(FILE *fp);
+int				hash_fh_key_file(const char *fh_key_file, uuid_t hash);
 
 _Bool state_setup_basedir(const char *, const char *);
 int setup_state_path_names(const char *, const char *, const char *, const char *, struct state_paths *);
diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
index 2e1577cc12df..775bccc6c5ea 100644
--- a/support/nfs/Makefile.am
+++ b/support/nfs/Makefile.am
@@ -7,8 +7,8 @@ libnfs_la_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
 		   xcommon.c wildmat.c mydaemon.c \
 		   rpc_socket.c getport.c \
 		   svc_socket.c cacheio.c closeall.c nfs_mntent.c \
-		   svc_create.c atomicio.c strlcat.c strlcpy.c
-libnfs_la_LIBADD = libnfsconf.la
+		   svc_create.c atomicio.c strlcat.c strlcpy.c fh_key_file.c
+libnfs_la_LIBADD = libnfsconf.la -luuid
 libnfs_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport
 
 libnfsconf_la_SOURCES = conffile.c xlog.c
diff --git a/support/nfs/fh_key_file.c b/support/nfs/fh_key_file.c
new file mode 100644
index 000000000000..ee26df5b70bd
--- /dev/null
+++ b/support/nfs/fh_key_file.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2025 Benjamin Coddington <bcodding@hammerspace.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <uuid/uuid.h>
+
+#include "nfslib.h"
+
+#define HASH_BLOCKSIZE  256
+int hash_fh_key_file(const char *fh_key_file, uuid_t uuid)
+{
+	const char seed_s[] = "8fc57f1b-1a6f-482f-af92-d2e007c1ae58";
+	FILE *sfile = NULL;
+	char buf[HASH_BLOCKSIZE];
+	size_t pos;
+	int ret = 0;
+
+	sfile = fopen(fh_key_file, "r");
+	if (!sfile) {
+		ret = errno;
+		xlog(L_ERROR, "Unable to read fh-key-file %s: %s", fh_key_file, strerror(errno));
+		goto out;
+	}
+
+	uuid_parse(seed_s, uuid);
+
+	while (1) {
+		size_t sread;
+		pos = 0;
+
+		if (feof(sfile))
+			goto finish_block;
+
+		sread = fread(buf + pos, 1, HASH_BLOCKSIZE - pos, sfile);
+		pos += sread;
+
+		if (pos == HASH_BLOCKSIZE)
+			break;
+
+		if (sread != HASH_BLOCKSIZE) {
+			ret = ferror(sfile);
+			if (ret)
+				goto out;
+			goto finish_block;
+		}
+		uuid_generate_sha1(uuid, uuid, buf, HASH_BLOCKSIZE);
+	}
+finish_block:
+	if (pos)
+		uuid_generate_sha1(uuid, uuid, buf, pos);
+out:
+	if (sfile)
+		fclose(sfile);
+	return ret;
+}
diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man
index ecdc4fc90327..a6b5c907b457 100644
--- a/systemd/nfs.conf.man
+++ b/systemd/nfs.conf.man
@@ -176,6 +176,7 @@ Recognized values:
 .BR vers4.1 ,
 .BR vers4.2 ,
 .BR rdma ,
+.BR fh-key-file ,
 
 Version and protocol values are Boolean values as described above,
 and are also used by
diff --git a/utils/nfsd/nfssvc.h b/utils/nfsd/nfssvc.h
index 4d53af1a8bc3..463110cac804 100644
--- a/utils/nfsd/nfssvc.h
+++ b/utils/nfsd/nfssvc.h
@@ -30,3 +30,4 @@ void	nfssvc_setvers(unsigned int ctlbits, unsigned int minorvers4,
 		       unsigned int minorvers4set, int force4dot0);
 int	nfssvc_threads(int nrservs);
 void	nfssvc_get_minormask(unsigned int *mask);
+int nfssvc_setfh_key(const char *fh_key_file);
diff --git a/utils/nfsdctl/nfsd_netlink.h b/utils/nfsdctl/nfsd_netlink.h
index e9efbc9e63d8..97c7447f4d14 100644
--- a/utils/nfsdctl/nfsd_netlink.h
+++ b/utils/nfsdctl/nfsd_netlink.h
@@ -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)
diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
index 6a20d180a81e..2369a8495954 100644
--- a/utils/nfsdctl/nfsdctl.c
+++ b/utils/nfsdctl/nfsdctl.c
@@ -29,6 +29,7 @@
 
 #include <readline/readline.h>
 #include <readline/history.h>
+#include <uuid/uuid.h>
 
 #ifdef USE_SYSTEM_NFSD_NETLINK_H
 #include <linux/nfsd_netlink.h>
@@ -42,6 +43,7 @@
 #include "lockd_netlink.h"
 #endif
 
+#include "nfslib.h"
 #include "nfsdctl.h"
 #include "conffile.h"
 #include "xlog.h"
@@ -636,8 +638,10 @@ out:
 }
 
 static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
-			int pool_count, int *pool_threads, char *scope, int minthreads)
+			int pool_count, int *pool_threads, char *scope, int minthreads,
+			uuid_t fh_key)
 {
+	struct nl_data *fh_key_data = NULL;
 	struct genlmsghdr *ghdr;
 	struct nlmsghdr *nlh;
 	struct nl_msg *msg;
@@ -663,6 +667,19 @@ static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
 			nla_put_string(msg, NFSD_A_SERVER_SCOPE, scope);
 		if (minthreads >= 0)
 			nla_put_u32(msg, NFSD_A_SERVER_MIN_THREADS, minthreads);
+		if (!uuid_is_null(fh_key)) {
+			if (nfsd_threads_max_nlattr < NFSD_A_SERVER_FH_KEY) {
+				xlog(L_ERROR, "This kernel does not support signed filehandles.");
+			} else {
+				fh_key_data = nl_data_alloc(fh_key, sizeof(uuid_t));
+				if (!fh_key_data) {
+					xlog(L_ERROR, "failed to allocate netlink data");
+					ret = 1;
+					goto out;
+				}
+				nla_put_data(msg, NFSD_A_SERVER_FH_KEY, fh_key_data);
+			}
+		}
 		for (i = 0; i < pool_count; ++i)
 			nla_put_u32(msg, NFSD_A_SERVER_THREADS, pool_threads[i]);
 	}
@@ -697,6 +714,8 @@ static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
 out_cb:
 	nl_cb_put(cb);
 out:
+	if (fh_key_data)
+		nl_data_free(fh_key_data);
 	nlmsg_free(msg);
 	return ret;
 }
@@ -721,6 +740,7 @@ static int threads_func(struct nl_sock *sock, int argc, char **argv)
 	int *pool_threads = NULL;
 	int minthreads = -1;
 	int opt, pools = 0;
+	uuid_t fh_key;
 
 	optind = 1;
 	while ((opt = getopt_long(argc, argv, "hm:", threads_options, NULL)) != -1) {
@@ -768,7 +788,9 @@ static int threads_func(struct nl_sock *sock, int argc, char **argv)
 			}
 		}
 	}
-	return threads_doit(sock, cmd, 0, 0, pools, pool_threads, NULL, minthreads);
+	uuid_clear(fh_key);
+	return threads_doit(sock, cmd, 0, 0, pools, pool_threads, NULL,
+				minthreads, fh_key);
 }
 
 /*
@@ -1760,8 +1782,9 @@ static int autostart_func(struct nl_sock *sock, int argc, char ** argv)
 	int *threads, grace, lease, idx, ret, opt, pools, minthreads;
 	struct conf_list *thread_str;
 	struct conf_list_node *n;
-	char *scope, *pool_mode;
+	char *scope, *pool_mode, *fh_key_file;
 	bool failed_listeners = false;
+	uuid_t fh_key;
 
 	optind = 1;
 	while ((opt = getopt_long(argc, argv, "h", help_only_options, NULL)) != -1) {
@@ -1836,6 +1859,14 @@ static int autostart_func(struct nl_sock *sock, int argc, char ** argv)
 		threads[0] = DEFAULT_AUTOSTART_THREADS;
 	}
 
+	uuid_clear(fh_key);
+	fh_key_file = conf_get_str("nfsd", "fh-key-file");
+	if (fh_key_file) {
+		ret = hash_fh_key_file(fh_key_file, fh_key);
+		if (ret)
+			return ret;
+	}
+
 	lease = conf_get_num("nfsd", "lease-time", 0);
 	scope = conf_get_str("nfsd", "scope");
 	minthreads = conf_get_num("nfsd", "min-threads", -1);
@@ -1846,7 +1877,7 @@ static int autostart_func(struct nl_sock *sock, int argc, char ** argv)
 	}
 
 	ret = threads_doit(sock, NFSD_CMD_THREADS_SET, grace, lease, pools,
-			   threads, scope, minthreads);
+			   threads, scope, minthreads, fh_key);
 out:
 	free(threads);
 	return ret;
-- 
2.50.1


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key
  2026-02-06 15:15 ` [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key Benjamin Coddington
@ 2026-02-06 15:58   ` Jeff Layton
  2026-02-06 16:09     ` Benjamin Coddington
  0 siblings, 1 reply; 6+ messages in thread
From: Jeff Layton @ 2026-02-06 15:58 UTC (permalink / raw)
  To: Benjamin Coddington, Steve Dickson; +Cc: linux-nfs, Chuck Lever, NeilBrown

On Fri, 2026-02-06 at 10:15 -0500, Benjamin Coddington wrote:
> If fh-key-file=<path> is set in the nfsd section of nfs.conf, the "nfsdctl
> autostart" command will hash the contents of the file with libuuid's
> uuid_generate_sha1 and send the first 16 bytes into the kernel on
> NFSD_A_SERVER_FH_KEY.
> 
> A compatible kernel can use this key to sign filehandles.
> 
> Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
> ---
>  nfs.conf                     |  1 +
>  support/include/nfslib.h     |  2 +
>  support/nfs/Makefile.am      |  4 +-
>  support/nfs/fh_key_file.c    | 79 ++++++++++++++++++++++++++++++++++++
>  systemd/nfs.conf.man         |  1 +
>  utils/nfsd/nfssvc.h          |  1 +
>  utils/nfsdctl/nfsd_netlink.h |  1 +
>  utils/nfsdctl/nfsdctl.c      | 39 ++++++++++++++++--
>  8 files changed, 122 insertions(+), 6 deletions(-)
>  create mode 100644 support/nfs/fh_key_file.c
> 
> diff --git a/nfs.conf b/nfs.conf
> index 3cca68c3530d..39068c19d7df 100644
> --- a/nfs.conf
> +++ b/nfs.conf
> @@ -76,6 +76,7 @@
>  # vers4.2=y
>  rdma=y
>  rdma-port=20049
> +# fh-key-file=/etc/nfs_fh.key
>  
>  [statd]
>  # debug=0
> diff --git a/support/include/nfslib.h b/support/include/nfslib.h
> index eff2a486307f..c8601a156cba 100644
> --- a/support/include/nfslib.h
> +++ b/support/include/nfslib.h
> @@ -22,6 +22,7 @@
>  #include <paths.h>
>  #include <rpcsvc/nfs_prot.h>
>  #include <nfs/nfs.h>
> +#include <uuid/uuid.h>
>  #include "xlog.h"
>  
>  #ifndef _PATH_EXPORTS
> @@ -132,6 +133,7 @@ struct rmtabent *	fgetrmtabent(FILE *fp, int log, long *pos);
>  void			fputrmtabent(FILE *fp, struct rmtabent *xep, long *pos);
>  void			fendrmtabent(FILE *fp);
>  void			frewindrmtabent(FILE *fp);
> +int				hash_fh_key_file(const char *fh_key_file, uuid_t hash);
>  
>  _Bool state_setup_basedir(const char *, const char *);
>  int setup_state_path_names(const char *, const char *, const char *, const char *, struct state_paths *);
> diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
> index 2e1577cc12df..775bccc6c5ea 100644
> --- a/support/nfs/Makefile.am
> +++ b/support/nfs/Makefile.am
> @@ -7,8 +7,8 @@ libnfs_la_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
>  		   xcommon.c wildmat.c mydaemon.c \
>  		   rpc_socket.c getport.c \
>  		   svc_socket.c cacheio.c closeall.c nfs_mntent.c \
> -		   svc_create.c atomicio.c strlcat.c strlcpy.c
> -libnfs_la_LIBADD = libnfsconf.la
> +		   svc_create.c atomicio.c strlcat.c strlcpy.c fh_key_file.c
> +libnfs_la_LIBADD = libnfsconf.la -luuid
>  libnfs_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport
>  
>  libnfsconf_la_SOURCES = conffile.c xlog.c
> diff --git a/support/nfs/fh_key_file.c b/support/nfs/fh_key_file.c
> new file mode 100644
> index 000000000000..ee26df5b70bd
> --- /dev/null
> +++ b/support/nfs/fh_key_file.c
> @@ -0,0 +1,79 @@
> +/*
> + * Copyright (c) 2025 Benjamin Coddington <bcodding@hammerspace.com>
> + * All rights reserved.
> + *
> + * Redistribution and use in source and binary forms, with or without
> + * modification, are permitted provided that the following conditions
> + * are met:
> + * 1. Redistributions of source code must retain the above copyright
> + *    notice, this list of conditions and the following disclaimer.
> + * 2. Redistributions in binary form must reproduce the above copyright
> + *    notice, this list of conditions and the following disclaimer in the
> + *    documentation and/or other materials provided with the distribution.
> + *
> + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
> + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
> + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
> + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
> + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> + */
> +
> +#include <sys/types.h>
> +#include <unistd.h>
> +#include <errno.h>
> +#include <uuid/uuid.h>
> +
> +#include "nfslib.h"
> +
> +#define HASH_BLOCKSIZE  256
> +int hash_fh_key_file(const char *fh_key_file, uuid_t uuid)
> +{
> +	const char seed_s[] = "8fc57f1b-1a6f-482f-af92-d2e007c1ae58";
> +	FILE *sfile = NULL;
> +	char buf[HASH_BLOCKSIZE];
> +	size_t pos;
> +	int ret = 0;
> +
> +	sfile = fopen(fh_key_file, "r");
> +	if (!sfile) {
> +		ret = errno;
> +		xlog(L_ERROR, "Unable to read fh-key-file %s: %s", fh_key_file, strerror(errno));
> +		goto out;
> +	}
> +
> +	uuid_parse(seed_s, uuid);
> +
> +	while (1) {
> +		size_t sread;
> +		pos = 0;
> +
> +		if (feof(sfile))
> +			goto finish_block;
> +
> +		sread = fread(buf + pos, 1, HASH_BLOCKSIZE - pos, sfile);
> +		pos += sread;
> +
> +		if (pos == HASH_BLOCKSIZE)
> +			break;
> +
> +		if (sread != HASH_BLOCKSIZE) {
> +			ret = ferror(sfile);
> +			if (ret)
> +				goto out;
> +			goto finish_block;
> +		}
> +		uuid_generate_sha1(uuid, uuid, buf, HASH_BLOCKSIZE);
> +	}
> +finish_block:
> +	if (pos)
> +		uuid_generate_sha1(uuid, uuid, buf, pos);
> +out:
> +	if (sfile)
> +		fclose(sfile);
> +	return ret;
> +}
> diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man
> index ecdc4fc90327..a6b5c907b457 100644
> --- a/systemd/nfs.conf.man
> +++ b/systemd/nfs.conf.man
> @@ -176,6 +176,7 @@ Recognized values:
>  .BR vers4.1 ,
>  .BR vers4.2 ,
>  .BR rdma ,
> +.BR fh-key-file ,
>  
>  Version and protocol values are Boolean values as described above,
>  and are also used by
> diff --git a/utils/nfsd/nfssvc.h b/utils/nfsd/nfssvc.h
> index 4d53af1a8bc3..463110cac804 100644
> --- a/utils/nfsd/nfssvc.h
> +++ b/utils/nfsd/nfssvc.h
> @@ -30,3 +30,4 @@ void	nfssvc_setvers(unsigned int ctlbits, unsigned int minorvers4,
>  		       unsigned int minorvers4set, int force4dot0);
>  int	nfssvc_threads(int nrservs);
>  void	nfssvc_get_minormask(unsigned int *mask);
> +int nfssvc_setfh_key(const char *fh_key_file);
> diff --git a/utils/nfsdctl/nfsd_netlink.h b/utils/nfsdctl/nfsd_netlink.h
> index e9efbc9e63d8..97c7447f4d14 100644
> --- a/utils/nfsdctl/nfsd_netlink.h
> +++ b/utils/nfsdctl/nfsd_netlink.h
> @@ -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)
> diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
> index 6a20d180a81e..2369a8495954 100644
> --- a/utils/nfsdctl/nfsdctl.c
> +++ b/utils/nfsdctl/nfsdctl.c
> @@ -29,6 +29,7 @@
>  
>  #include <readline/readline.h>
>  #include <readline/history.h>
> +#include <uuid/uuid.h>
>  
>  #ifdef USE_SYSTEM_NFSD_NETLINK_H
>  #include <linux/nfsd_netlink.h>
> @@ -42,6 +43,7 @@
>  #include "lockd_netlink.h"
>  #endif
>  
> +#include "nfslib.h"
>  #include "nfsdctl.h"
>  #include "conffile.h"
>  #include "xlog.h"
> @@ -636,8 +638,10 @@ out:
>  }
>  
>  static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
> -			int pool_count, int *pool_threads, char *scope, int minthreads)
> +			int pool_count, int *pool_threads, char *scope, int minthreads,
> +			uuid_t fh_key)
>  {
> +	struct nl_data *fh_key_data = NULL;
>  	struct genlmsghdr *ghdr;
>  	struct nlmsghdr *nlh;
>  	struct nl_msg *msg;

I think you should add a --fh-key-file= option to the "threads" command
too.

> @@ -663,6 +667,19 @@ static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
>  			nla_put_string(msg, NFSD_A_SERVER_SCOPE, scope);
>  		if (minthreads >= 0)
>  			nla_put_u32(msg, NFSD_A_SERVER_MIN_THREADS, minthreads);
> +		if (!uuid_is_null(fh_key)) {
> +			if (nfsd_threads_max_nlattr < NFSD_A_SERVER_FH_KEY) {

I would move this check into the caller, so you can handle errors
differently. The "threads" command should fail if someone specifies
--fh-key-file and it can't be set. That command is intended to be
interactive.

For "autostart", I'm not sure:

Wouldn't it be better to fail to start if the kernel doesn't support
setting a key? The clients are going to end up with stale filehandles
in that case, no?

I'd hate to mistakenly boot into an old kernel and end up with all of
my clients falling over.

> +				xlog(L_ERROR, "This kernel does not support signed filehandles.");
> +			} else {
> +				fh_key_data = nl_data_alloc(fh_key, sizeof(uuid_t));
> +				if (!fh_key_data) {
> +					xlog(L_ERROR, "failed to allocate netlink data");
> +					ret = 1;
> +					goto out;
> +				}
> +				nla_put_data(msg, NFSD_A_SERVER_FH_KEY, fh_key_data);
> +			}
> +		}

I think it would be best if the "threads" command failed with a hard
error if the key can't be set, but I'm OK with the 

>  		for (i = 0; i < pool_count; ++i)
>  			nla_put_u32(msg, NFSD_A_SERVER_THREADS, pool_threads[i]);
>  	}
> @@ -697,6 +714,8 @@ static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
>  out_cb:
>  	nl_cb_put(cb);
>  out:
> +	if (fh_key_data)
> +		nl_data_free(fh_key_data);
>  	nlmsg_free(msg);
>  	return ret;
>  }
> @@ -721,6 +740,7 @@ static int threads_func(struct nl_sock *sock, int argc, char **argv)
>  	int *pool_threads = NULL;
>  	int minthreads = -1;
>  	int opt, pools = 0;
> +	uuid_t fh_key;
>  
>  	optind = 1;
>  	while ((opt = getopt_long(argc, argv, "hm:", threads_options, NULL)) != -1) {
> @@ -768,7 +788,9 @@ static int threads_func(struct nl_sock *sock, int argc, char **argv)
>  			}
>  		}
>  	}
> -	return threads_doit(sock, cmd, 0, 0, pools, pool_threads, NULL, minthreads);
> +	uuid_clear(fh_key);
> +	return threads_doit(sock, cmd, 0, 0, pools, pool_threads, NULL,
> +				minthreads, fh_key);
>  }
>  
>  /*
> @@ -1760,8 +1782,9 @@ static int autostart_func(struct nl_sock *sock, int argc, char ** argv)
>  	int *threads, grace, lease, idx, ret, opt, pools, minthreads;
>  	struct conf_list *thread_str;
>  	struct conf_list_node *n;
> -	char *scope, *pool_mode;
> +	char *scope, *pool_mode, *fh_key_file;
>  	bool failed_listeners = false;
> +	uuid_t fh_key;
>  
>  	optind = 1;
>  	while ((opt = getopt_long(argc, argv, "h", help_only_options, NULL)) != -1) {
> @@ -1836,6 +1859,14 @@ static int autostart_func(struct nl_sock *sock, int argc, char ** argv)
>  		threads[0] = DEFAULT_AUTOSTART_THREADS;
>  	}
>  
> +	uuid_clear(fh_key);
> +	fh_key_file = conf_get_str("nfsd", "fh-key-file");
> +	if (fh_key_file) {
> +		ret = hash_fh_key_file(fh_key_file, fh_key);
> +		if (ret)
> +			return ret;
> +	}
> +
>  	lease = conf_get_num("nfsd", "lease-time", 0);
>  	scope = conf_get_str("nfsd", "scope");
>  	minthreads = conf_get_num("nfsd", "min-threads", -1);
> @@ -1846,7 +1877,7 @@ static int autostart_func(struct nl_sock *sock, int argc, char ** argv)
>  	}
>  
>  	ret = threads_doit(sock, NFSD_CMD_THREADS_SET, grace, lease, pools,
> -			   threads, scope, minthreads);
> +			   threads, scope, minthreads, fh_key);
>  out:
>  	free(threads);
>  	return ret;

-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key
  2026-02-06 15:58   ` Jeff Layton
@ 2026-02-06 16:09     ` Benjamin Coddington
  2026-02-06 16:31       ` Jeff Layton
  0 siblings, 1 reply; 6+ messages in thread
From: Benjamin Coddington @ 2026-02-06 16:09 UTC (permalink / raw)
  To: Jeff Layton; +Cc: Steve Dickson, linux-nfs, Chuck Lever, NeilBrown

On 6 Feb 2026, at 10:58, Jeff Layton wrote:

> On Fri, 2026-02-06 at 10:15 -0500, Benjamin Coddington wrote:
>> If fh-key-file=<path> is set in the nfsd section of nfs.conf, the "nfsdctl
>> autostart" command will hash the contents of the file with libuuid's
>> uuid_generate_sha1 and send the first 16 bytes into the kernel on
>> NFSD_A_SERVER_FH_KEY.
>>
>> A compatible kernel can use this key to sign filehandles.
>>
>> Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
>> ---
>>  nfs.conf                     |  1 +
>>  support/include/nfslib.h     |  2 +
>>  support/nfs/Makefile.am      |  4 +-
>>  support/nfs/fh_key_file.c    | 79 ++++++++++++++++++++++++++++++++++++
>>  systemd/nfs.conf.man         |  1 +
>>  utils/nfsd/nfssvc.h          |  1 +
>>  utils/nfsdctl/nfsd_netlink.h |  1 +
>>  utils/nfsdctl/nfsdctl.c      | 39 ++++++++++++++++--
>>  8 files changed, 122 insertions(+), 6 deletions(-)
>>  create mode 100644 support/nfs/fh_key_file.c
>>
>> diff --git a/nfs.conf b/nfs.conf
>> index 3cca68c3530d..39068c19d7df 100644
>> --- a/nfs.conf
>> +++ b/nfs.conf
>> @@ -76,6 +76,7 @@
>>  # vers4.2=y
>>  rdma=y
>>  rdma-port=20049
>> +# fh-key-file=/etc/nfs_fh.key
>>
>>  [statd]
>>  # debug=0
>> diff --git a/support/include/nfslib.h b/support/include/nfslib.h
>> index eff2a486307f..c8601a156cba 100644
>> --- a/support/include/nfslib.h
>> +++ b/support/include/nfslib.h
>> @@ -22,6 +22,7 @@
>>  #include <paths.h>
>>  #include <rpcsvc/nfs_prot.h>
>>  #include <nfs/nfs.h>
>> +#include <uuid/uuid.h>
>>  #include "xlog.h"
>>
>>  #ifndef _PATH_EXPORTS
>> @@ -132,6 +133,7 @@ struct rmtabent *	fgetrmtabent(FILE *fp, int log, long *pos);
>>  void			fputrmtabent(FILE *fp, struct rmtabent *xep, long *pos);
>>  void			fendrmtabent(FILE *fp);
>>  void			frewindrmtabent(FILE *fp);
>> +int				hash_fh_key_file(const char *fh_key_file, uuid_t hash);
>>
>>  _Bool state_setup_basedir(const char *, const char *);
>>  int setup_state_path_names(const char *, const char *, const char *, const char *, struct state_paths *);
>> diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
>> index 2e1577cc12df..775bccc6c5ea 100644
>> --- a/support/nfs/Makefile.am
>> +++ b/support/nfs/Makefile.am
>> @@ -7,8 +7,8 @@ libnfs_la_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
>>  		   xcommon.c wildmat.c mydaemon.c \
>>  		   rpc_socket.c getport.c \
>>  		   svc_socket.c cacheio.c closeall.c nfs_mntent.c \
>> -		   svc_create.c atomicio.c strlcat.c strlcpy.c
>> -libnfs_la_LIBADD = libnfsconf.la
>> +		   svc_create.c atomicio.c strlcat.c strlcpy.c fh_key_file.c
>> +libnfs_la_LIBADD = libnfsconf.la -luuid
>>  libnfs_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport
>>
>>  libnfsconf_la_SOURCES = conffile.c xlog.c
>> diff --git a/support/nfs/fh_key_file.c b/support/nfs/fh_key_file.c
>> new file mode 100644
>> index 000000000000..ee26df5b70bd
>> --- /dev/null
>> +++ b/support/nfs/fh_key_file.c
>> @@ -0,0 +1,79 @@
>> +/*
>> + * Copyright (c) 2025 Benjamin Coddington <bcodding@hammerspace.com>
>> + * All rights reserved.
>> + *
>> + * Redistribution and use in source and binary forms, with or without
>> + * modification, are permitted provided that the following conditions
>> + * are met:
>> + * 1. Redistributions of source code must retain the above copyright
>> + *    notice, this list of conditions and the following disclaimer.
>> + * 2. Redistributions in binary form must reproduce the above copyright
>> + *    notice, this list of conditions and the following disclaimer in the
>> + *    documentation and/or other materials provided with the distribution.
>> + *
>> + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
>> + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
>> + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
>> + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
>> + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
>> + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
>> + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
>> + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
>> + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
>> + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
>> + */
>> +
>> +#include <sys/types.h>
>> +#include <unistd.h>
>> +#include <errno.h>
>> +#include <uuid/uuid.h>
>> +
>> +#include "nfslib.h"
>> +
>> +#define HASH_BLOCKSIZE  256
>> +int hash_fh_key_file(const char *fh_key_file, uuid_t uuid)
>> +{
>> +	const char seed_s[] = "8fc57f1b-1a6f-482f-af92-d2e007c1ae58";
>> +	FILE *sfile = NULL;
>> +	char buf[HASH_BLOCKSIZE];
>> +	size_t pos;
>> +	int ret = 0;
>> +
>> +	sfile = fopen(fh_key_file, "r");
>> +	if (!sfile) {
>> +		ret = errno;
>> +		xlog(L_ERROR, "Unable to read fh-key-file %s: %s", fh_key_file, strerror(errno));
>> +		goto out;
>> +	}
>> +
>> +	uuid_parse(seed_s, uuid);
>> +
>> +	while (1) {
>> +		size_t sread;
>> +		pos = 0;
>> +
>> +		if (feof(sfile))
>> +			goto finish_block;
>> +
>> +		sread = fread(buf + pos, 1, HASH_BLOCKSIZE - pos, sfile);
>> +		pos += sread;
>> +
>> +		if (pos == HASH_BLOCKSIZE)
>> +			break;
>> +
>> +		if (sread != HASH_BLOCKSIZE) {
>> +			ret = ferror(sfile);
>> +			if (ret)
>> +				goto out;
>> +			goto finish_block;
>> +		}
>> +		uuid_generate_sha1(uuid, uuid, buf, HASH_BLOCKSIZE);
>> +	}
>> +finish_block:
>> +	if (pos)
>> +		uuid_generate_sha1(uuid, uuid, buf, pos);
>> +out:
>> +	if (sfile)
>> +		fclose(sfile);
>> +	return ret;
>> +}
>> diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man
>> index ecdc4fc90327..a6b5c907b457 100644
>> --- a/systemd/nfs.conf.man
>> +++ b/systemd/nfs.conf.man
>> @@ -176,6 +176,7 @@ Recognized values:
>>  .BR vers4.1 ,
>>  .BR vers4.2 ,
>>  .BR rdma ,
>> +.BR fh-key-file ,
>>
>>  Version and protocol values are Boolean values as described above,
>>  and are also used by
>> diff --git a/utils/nfsd/nfssvc.h b/utils/nfsd/nfssvc.h
>> index 4d53af1a8bc3..463110cac804 100644
>> --- a/utils/nfsd/nfssvc.h
>> +++ b/utils/nfsd/nfssvc.h
>> @@ -30,3 +30,4 @@ void	nfssvc_setvers(unsigned int ctlbits, unsigned int minorvers4,
>>  		       unsigned int minorvers4set, int force4dot0);
>>  int	nfssvc_threads(int nrservs);
>>  void	nfssvc_get_minormask(unsigned int *mask);
>> +int nfssvc_setfh_key(const char *fh_key_file);
>> diff --git a/utils/nfsdctl/nfsd_netlink.h b/utils/nfsdctl/nfsd_netlink.h
>> index e9efbc9e63d8..97c7447f4d14 100644
>> --- a/utils/nfsdctl/nfsd_netlink.h
>> +++ b/utils/nfsdctl/nfsd_netlink.h
>> @@ -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)
>> diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
>> index 6a20d180a81e..2369a8495954 100644
>> --- a/utils/nfsdctl/nfsdctl.c
>> +++ b/utils/nfsdctl/nfsdctl.c
>> @@ -29,6 +29,7 @@
>>
>>  #include <readline/readline.h>
>>  #include <readline/history.h>
>> +#include <uuid/uuid.h>
>>
>>  #ifdef USE_SYSTEM_NFSD_NETLINK_H
>>  #include <linux/nfsd_netlink.h>
>> @@ -42,6 +43,7 @@
>>  #include "lockd_netlink.h"
>>  #endif
>>
>> +#include "nfslib.h"
>>  #include "nfsdctl.h"
>>  #include "conffile.h"
>>  #include "xlog.h"
>> @@ -636,8 +638,10 @@ out:
>>  }
>>
>>  static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
>> -			int pool_count, int *pool_threads, char *scope, int minthreads)
>> +			int pool_count, int *pool_threads, char *scope, int minthreads,
>> +			uuid_t fh_key)
>>  {
>> +	struct nl_data *fh_key_data = NULL;
>>  	struct genlmsghdr *ghdr;
>>  	struct nlmsghdr *nlh;
>>  	struct nl_msg *msg;
>
> I think you should add a --fh-key-file= option to the "threads" command
> too.
>
>> @@ -663,6 +667,19 @@ static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
>>  			nla_put_string(msg, NFSD_A_SERVER_SCOPE, scope);
>>  		if (minthreads >= 0)
>>  			nla_put_u32(msg, NFSD_A_SERVER_MIN_THREADS, minthreads);
>> +		if (!uuid_is_null(fh_key)) {
>> +			if (nfsd_threads_max_nlattr < NFSD_A_SERVER_FH_KEY) {
>
> I would move this check into the caller, so you can handle errors
> differently. The "threads" command should fail if someone specifies
> --fh-key-file and it can't be set. That command is intended to be
> interactive.

Ok - I can do this.

> For "autostart", I'm not sure:
>
> Wouldn't it be better to fail to start if the kernel doesn't support
> setting a key? The clients are going to end up with stale filehandles
> in that case, no?

No.. if the kernel doesn't support setting a key its not giving out any
signed filehandles.  I guess you're thinking about the "downgraded kernel"
case - while I'm thinking about the "upgraded nfs-utils" case.  I'm not sure
we can handle them both in userspace (nfs-utils can't tell), and the kernel
makes sure not to give out filehandles for exports that have "sign_fh" but
no key, and an export with "sign_fh" on a downgraded kernel won't export
because the option returns an error.

> I'd hate to mistakenly boot into an old kernel and end up with all of
> my clients falling over.

Right - ok, you're definitely thinking about downgraded kernel.  For this
case, the exportfs will reject the export config that has "sign_fh".

>
>> +				xlog(L_ERROR, "This kernel does not support signed filehandles.");
>> +			} else {
>> +				fh_key_data = nl_data_alloc(fh_key, sizeof(uuid_t));
>> +				if (!fh_key_data) {
>> +					xlog(L_ERROR, "failed to allocate netlink data");
>> +					ret = 1;
>> +					goto out;
>> +				}
>> +				nla_put_data(msg, NFSD_A_SERVER_FH_KEY, fh_key_data);
>> +			}
>> +		}
>
> I think it would be best if the "threads" command failed with a hard
> error if the key can't be set, but I'm OK with the

lost a part here - you're ok with autostart succeeding?

Thanks for the looks!
Ben

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key
  2026-02-06 16:09     ` Benjamin Coddington
@ 2026-02-06 16:31       ` Jeff Layton
  0 siblings, 0 replies; 6+ messages in thread
From: Jeff Layton @ 2026-02-06 16:31 UTC (permalink / raw)
  To: Benjamin Coddington; +Cc: Steve Dickson, linux-nfs, Chuck Lever, NeilBrown

On Fri, 2026-02-06 at 11:09 -0500, Benjamin Coddington wrote:
> On 6 Feb 2026, at 10:58, Jeff Layton wrote:
> 
> > On Fri, 2026-02-06 at 10:15 -0500, Benjamin Coddington wrote:
> > > If fh-key-file=<path> is set in the nfsd section of nfs.conf, the "nfsdctl
> > > autostart" command will hash the contents of the file with libuuid's
> > > uuid_generate_sha1 and send the first 16 bytes into the kernel on
> > > NFSD_A_SERVER_FH_KEY.
> > > 
> > > A compatible kernel can use this key to sign filehandles.
> > > 
> > > Signed-off-by: Benjamin Coddington <bcodding@hammerspace.com>
> > > ---
> > >  nfs.conf                     |  1 +
> > >  support/include/nfslib.h     |  2 +
> > >  support/nfs/Makefile.am      |  4 +-
> > >  support/nfs/fh_key_file.c    | 79 ++++++++++++++++++++++++++++++++++++
> > >  systemd/nfs.conf.man         |  1 +
> > >  utils/nfsd/nfssvc.h          |  1 +
> > >  utils/nfsdctl/nfsd_netlink.h |  1 +
> > >  utils/nfsdctl/nfsdctl.c      | 39 ++++++++++++++++--
> > >  8 files changed, 122 insertions(+), 6 deletions(-)
> > >  create mode 100644 support/nfs/fh_key_file.c
> > > 
> > > diff --git a/nfs.conf b/nfs.conf
> > > index 3cca68c3530d..39068c19d7df 100644
> > > --- a/nfs.conf
> > > +++ b/nfs.conf
> > > @@ -76,6 +76,7 @@
> > >  # vers4.2=y
> > >  rdma=y
> > >  rdma-port=20049
> > > +# fh-key-file=/etc/nfs_fh.key
> > > 
> > >  [statd]
> > >  # debug=0
> > > diff --git a/support/include/nfslib.h b/support/include/nfslib.h
> > > index eff2a486307f..c8601a156cba 100644
> > > --- a/support/include/nfslib.h
> > > +++ b/support/include/nfslib.h
> > > @@ -22,6 +22,7 @@
> > >  #include <paths.h>
> > >  #include <rpcsvc/nfs_prot.h>
> > >  #include <nfs/nfs.h>
> > > +#include <uuid/uuid.h>
> > >  #include "xlog.h"
> > > 
> > >  #ifndef _PATH_EXPORTS
> > > @@ -132,6 +133,7 @@ struct rmtabent *	fgetrmtabent(FILE *fp, int log, long *pos);
> > >  void			fputrmtabent(FILE *fp, struct rmtabent *xep, long *pos);
> > >  void			fendrmtabent(FILE *fp);
> > >  void			frewindrmtabent(FILE *fp);
> > > +int				hash_fh_key_file(const char *fh_key_file, uuid_t hash);
> > > 
> > >  _Bool state_setup_basedir(const char *, const char *);
> > >  int setup_state_path_names(const char *, const char *, const char *, const char *, struct state_paths *);
> > > diff --git a/support/nfs/Makefile.am b/support/nfs/Makefile.am
> > > index 2e1577cc12df..775bccc6c5ea 100644
> > > --- a/support/nfs/Makefile.am
> > > +++ b/support/nfs/Makefile.am
> > > @@ -7,8 +7,8 @@ libnfs_la_SOURCES = exports.c rmtab.c xio.c rpcmisc.c rpcdispatch.c \
> > >  		   xcommon.c wildmat.c mydaemon.c \
> > >  		   rpc_socket.c getport.c \
> > >  		   svc_socket.c cacheio.c closeall.c nfs_mntent.c \
> > > -		   svc_create.c atomicio.c strlcat.c strlcpy.c
> > > -libnfs_la_LIBADD = libnfsconf.la
> > > +		   svc_create.c atomicio.c strlcat.c strlcpy.c fh_key_file.c
> > > +libnfs_la_LIBADD = libnfsconf.la -luuid
> > >  libnfs_la_CPPFLAGS = $(AM_CPPFLAGS) $(CPPFLAGS) -I$(top_srcdir)/support/reexport
> > > 
> > >  libnfsconf_la_SOURCES = conffile.c xlog.c
> > > diff --git a/support/nfs/fh_key_file.c b/support/nfs/fh_key_file.c
> > > new file mode 100644
> > > index 000000000000..ee26df5b70bd
> > > --- /dev/null
> > > +++ b/support/nfs/fh_key_file.c
> > > @@ -0,0 +1,79 @@
> > > +/*
> > > + * Copyright (c) 2025 Benjamin Coddington <bcodding@hammerspace.com>
> > > + * All rights reserved.
> > > + *
> > > + * Redistribution and use in source and binary forms, with or without
> > > + * modification, are permitted provided that the following conditions
> > > + * are met:
> > > + * 1. Redistributions of source code must retain the above copyright
> > > + *    notice, this list of conditions and the following disclaimer.
> > > + * 2. Redistributions in binary form must reproduce the above copyright
> > > + *    notice, this list of conditions and the following disclaimer in the
> > > + *    documentation and/or other materials provided with the distribution.
> > > + *
> > > + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
> > > + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
> > > + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
> > > + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
> > > + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
> > > + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
> > > + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
> > > + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
> > > + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
> > > + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
> > > + */
> > > +
> > > +#include <sys/types.h>
> > > +#include <unistd.h>
> > > +#include <errno.h>
> > > +#include <uuid/uuid.h>
> > > +
> > > +#include "nfslib.h"
> > > +
> > > +#define HASH_BLOCKSIZE  256
> > > +int hash_fh_key_file(const char *fh_key_file, uuid_t uuid)
> > > +{
> > > +	const char seed_s[] = "8fc57f1b-1a6f-482f-af92-d2e007c1ae58";
> > > +	FILE *sfile = NULL;
> > > +	char buf[HASH_BLOCKSIZE];
> > > +	size_t pos;
> > > +	int ret = 0;
> > > +
> > > +	sfile = fopen(fh_key_file, "r");
> > > +	if (!sfile) {
> > > +		ret = errno;
> > > +		xlog(L_ERROR, "Unable to read fh-key-file %s: %s", fh_key_file, strerror(errno));
> > > +		goto out;
> > > +	}
> > > +
> > > +	uuid_parse(seed_s, uuid);
> > > +
> > > +	while (1) {
> > > +		size_t sread;
> > > +		pos = 0;
> > > +
> > > +		if (feof(sfile))
> > > +			goto finish_block;
> > > +
> > > +		sread = fread(buf + pos, 1, HASH_BLOCKSIZE - pos, sfile);
> > > +		pos += sread;
> > > +
> > > +		if (pos == HASH_BLOCKSIZE)
> > > +			break;
> > > +
> > > +		if (sread != HASH_BLOCKSIZE) {
> > > +			ret = ferror(sfile);
> > > +			if (ret)
> > > +				goto out;
> > > +			goto finish_block;
> > > +		}
> > > +		uuid_generate_sha1(uuid, uuid, buf, HASH_BLOCKSIZE);
> > > +	}
> > > +finish_block:
> > > +	if (pos)
> > > +		uuid_generate_sha1(uuid, uuid, buf, pos);
> > > +out:
> > > +	if (sfile)
> > > +		fclose(sfile);
> > > +	return ret;
> > > +}
> > > diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man
> > > index ecdc4fc90327..a6b5c907b457 100644
> > > --- a/systemd/nfs.conf.man
> > > +++ b/systemd/nfs.conf.man
> > > @@ -176,6 +176,7 @@ Recognized values:
> > >  .BR vers4.1 ,
> > >  .BR vers4.2 ,
> > >  .BR rdma ,
> > > +.BR fh-key-file ,
> > > 
> > >  Version and protocol values are Boolean values as described above,
> > >  and are also used by
> > > diff --git a/utils/nfsd/nfssvc.h b/utils/nfsd/nfssvc.h
> > > index 4d53af1a8bc3..463110cac804 100644
> > > --- a/utils/nfsd/nfssvc.h
> > > +++ b/utils/nfsd/nfssvc.h
> > > @@ -30,3 +30,4 @@ void	nfssvc_setvers(unsigned int ctlbits, unsigned int minorvers4,
> > >  		       unsigned int minorvers4set, int force4dot0);
> > >  int	nfssvc_threads(int nrservs);
> > >  void	nfssvc_get_minormask(unsigned int *mask);
> > > +int nfssvc_setfh_key(const char *fh_key_file);
> > > diff --git a/utils/nfsdctl/nfsd_netlink.h b/utils/nfsdctl/nfsd_netlink.h
> > > index e9efbc9e63d8..97c7447f4d14 100644
> > > --- a/utils/nfsdctl/nfsd_netlink.h
> > > +++ b/utils/nfsdctl/nfsd_netlink.h
> > > @@ -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)
> > > diff --git a/utils/nfsdctl/nfsdctl.c b/utils/nfsdctl/nfsdctl.c
> > > index 6a20d180a81e..2369a8495954 100644
> > > --- a/utils/nfsdctl/nfsdctl.c
> > > +++ b/utils/nfsdctl/nfsdctl.c
> > > @@ -29,6 +29,7 @@
> > > 
> > >  #include <readline/readline.h>
> > >  #include <readline/history.h>
> > > +#include <uuid/uuid.h>
> > > 
> > >  #ifdef USE_SYSTEM_NFSD_NETLINK_H
> > >  #include <linux/nfsd_netlink.h>
> > > @@ -42,6 +43,7 @@
> > >  #include "lockd_netlink.h"
> > >  #endif
> > > 
> > > +#include "nfslib.h"
> > >  #include "nfsdctl.h"
> > >  #include "conffile.h"
> > >  #include "xlog.h"
> > > @@ -636,8 +638,10 @@ out:
> > >  }
> > > 
> > >  static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
> > > -			int pool_count, int *pool_threads, char *scope, int minthreads)
> > > +			int pool_count, int *pool_threads, char *scope, int minthreads,
> > > +			uuid_t fh_key)
> > >  {
> > > +	struct nl_data *fh_key_data = NULL;
> > >  	struct genlmsghdr *ghdr;
> > >  	struct nlmsghdr *nlh;
> > >  	struct nl_msg *msg;
> > 
> > I think you should add a --fh-key-file= option to the "threads" command
> > too.
> > 
> > > @@ -663,6 +667,19 @@ static int threads_doit(struct nl_sock *sock, int cmd, int grace, int lease,
> > >  			nla_put_string(msg, NFSD_A_SERVER_SCOPE, scope);
> > >  		if (minthreads >= 0)
> > >  			nla_put_u32(msg, NFSD_A_SERVER_MIN_THREADS, minthreads);
> > > +		if (!uuid_is_null(fh_key)) {
> > > +			if (nfsd_threads_max_nlattr < NFSD_A_SERVER_FH_KEY) {
> > 
> > I would move this check into the caller, so you can handle errors
> > differently. The "threads" command should fail if someone specifies
> > --fh-key-file and it can't be set. That command is intended to be
> > interactive.
> 
> Ok - I can do this.
> 
> > For "autostart", I'm not sure:
> > 
> > Wouldn't it be better to fail to start if the kernel doesn't support
> > setting a key? The clients are going to end up with stale filehandles
> > in that case, no?
> 
> No.. if the kernel doesn't support setting a key its not giving out any
> signed filehandles.  I guess you're thinking about the "downgraded kernel"
> case - while I'm thinking about the "upgraded nfs-utils" case.  I'm not sure
> we can handle them both in userspace (nfs-utils can't tell), and the kernel
> makes sure not to give out filehandles for exports that have "sign_fh" but
> no key, and an export with "sign_fh" on a downgraded kernel won't export
> because the option returns an error.
> 
> > I'd hate to mistakenly boot into an old kernel and end up with all of
> > my clients falling over.
> 
> Right - ok, you're definitely thinking about downgraded kernel.  For this
> case, the exportfs will reject the export config that has "sign_fh".
> 
> > 
> > > +				xlog(L_ERROR, "This kernel does not support signed filehandles.");
> > > +			} else {
> > > +				fh_key_data = nl_data_alloc(fh_key, sizeof(uuid_t));
> > > +				if (!fh_key_data) {
> > > +					xlog(L_ERROR, "failed to allocate netlink data");
> > > +					ret = 1;
> > > +					goto out;
> > > +				}
> > > +				nla_put_data(msg, NFSD_A_SERVER_FH_KEY, fh_key_data);
> > > +			}
> > > +		}
> > 
> > I think it would be best if the "threads" command failed with a hard
> > error if the key can't be set, but I'm OK with the
> 
> lost a part here - you're ok with autostart succeeding?
> 

You can disregard that. I started typing a comment there and then moved
it higher. Yes, I'm OK with autostart succeeding as long as you have a
coherent story for the behavior in the case of a kernel downgrade.

-- 
Jeff Layton <jlayton@kernel.org>

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-02-06 16:31 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-06 15:14 [PATCH v4 0/2] nfs-utils: signed filehandle support Benjamin Coddington
2026-02-06 15:15 ` [PATCH v4 1/2] exportfs: Add support for export option sign_fh Benjamin Coddington
2026-02-06 15:15 ` [PATCH v4 2/2] nfsdctl/rpc.nfsd: Add support for passing encrypted filehandle key Benjamin Coddington
2026-02-06 15:58   ` Jeff Layton
2026-02-06 16:09     ` Benjamin Coddington
2026-02-06 16:31       ` Jeff Layton

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox