[PATCH 9/9] NFSD: Add allow_tags to the netlink export interface
Chuck Lever
cel at kernel.org
Fri Jun 5 10:34:43 PDT 2026
From: Chuck Lever <chuck.lever at oracle.com>
The legacy exportfs cache path accepts an allow_tags clause that
restricts an export to mTLS sessions carrying at least one matching
session tag. The netlink-based svc_export interface had no such
attribute, so administrators configuring exports via netlink could
not request tag enforcement: nfsd_nl_parse_one_export() always
left ex_allow_tags empty, and check_xprtsec_policy() then granted
any authenticated peer.
Extend the svc-export attribute set with allow-tags and parse it
in nfsd_nl_parse_one_export(). Apply the same xprtsec=mtls
consistency check as svc_export_parse() so the netlink path
refuses contradictory security policy rather than silently exposing
a tagged export to plaintext or anonymous-TLS peers.
Signed-off-by: Chuck Lever <chuck.lever at oracle.com>
---
Documentation/netlink/specs/nfsd.yaml | 10 ++++++
fs/nfsd/export.c | 68 +++++++++++++++++++++++++++++++++--
fs/nfsd/netlink.c | 4 ++-
fs/nfsd/netlink.h | 3 +-
include/uapi/linux/nfsd_netlink.h | 1 +
5 files changed, 82 insertions(+), 4 deletions(-)
diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml
index 8f36fadd68f7..5cbdc1dab7e3 100644
--- a/Documentation/netlink/specs/nfsd.yaml
+++ b/Documentation/netlink/specs/nfsd.yaml
@@ -7,6 +7,10 @@ uapi-header: linux/nfsd_netlink.h
doc: NFSD configuration over generic netlink.
definitions:
+ -
+ name: handshake-session-tag-max-len
+ type: const
+ header: uapi/linux/handshake.h
-
type: flags
name: cache-type
@@ -253,6 +257,12 @@ attribute-sets:
-
name: fsid
type: s32
+ -
+ name: allow-tags
+ type: string
+ checks:
+ max-len: handshake-session-tag-max-len
+ multi-attr: true
-
name: svc-export-reqs
attributes:
diff --git a/fs/nfsd/export.c b/fs/nfsd/export.c
index a2aaa3cd6c52..25802de2de40 100644
--- a/fs/nfsd/export.c
+++ b/fs/nfsd/export.c
@@ -831,6 +831,7 @@ static struct svc_export *svc_export_update(struct svc_export *new,
static struct svc_export *svc_export_lookup(struct svc_export *);
static int check_export(const struct path *path, int *flags,
unsigned char *uuid);
+static int check_allow_tags(const struct svc_export *exp);
/**
* nfsd_nl_parse_one_export - parse one svc_export entry from a netlink message
@@ -845,14 +846,14 @@ static int check_export(const struct path *path, int *flags,
static int nfsd_nl_parse_one_export(struct cache_detail *cd,
struct nlattr *attr)
{
- struct nlattr *tb[NFSD_A_SVC_EXPORT_FSID + 1];
+ struct nlattr *tb[NFSD_A_SVC_EXPORT_ALLOW_TAGS + 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,
+ err = nla_parse_nested(tb, NFSD_A_SVC_EXPORT_ALLOW_TAGS, attr,
nfsd_svc_export_nl_policy, NULL);
if (err)
return err;
@@ -993,6 +994,68 @@ static int nfsd_nl_parse_one_export(struct cache_detail *cd,
}
}
+ /* allow-tags (multi-attr string) */
+ if (tb[NFSD_A_SVC_EXPORT_ALLOW_TAGS]) {
+ struct nlattr *tag_attr;
+ unsigned int count = 0;
+
+ /*
+ * The NLA_STRING policy does not guarantee a
+ * terminating NUL, so each tag is copied with
+ * the length-aware nla_strdup(). Embedded NUL
+ * bytes are rejected here because the policy
+ * cannot express that check; a tag containing
+ * one could never match a handshake-supplied
+ * tag, which net/handshake rejects the same
+ * way.
+ */
+ nla_for_each_nested_type(tag_attr,
+ NFSD_A_SVC_EXPORT_ALLOW_TAGS,
+ attr, rem) {
+ const char *src = nla_data(tag_attr);
+ size_t srclen = nla_len(tag_attr);
+
+ if (srclen > 0 && src[srclen - 1] == '\0')
+ srclen--;
+ if (srclen == 0 ||
+ memchr(src, '\0', srclen)) {
+ err = -EINVAL;
+ goto out_uuid;
+ }
+ count++;
+ }
+ if (count > NFSD_MAX_ALLOW_TAGS) {
+ err = -EINVAL;
+ goto out_uuid;
+ }
+ if (!tagset_alloc(&exp.ex_allow_tags, count,
+ GFP_KERNEL)) {
+ err = -ENOMEM;
+ goto out_uuid;
+ }
+ nla_for_each_nested_type(tag_attr,
+ NFSD_A_SVC_EXPORT_ALLOW_TAGS,
+ attr, rem) {
+ char *tag;
+
+ tag = nla_strdup(tag_attr, GFP_KERNEL);
+ if (!tag) {
+ err = -ENOMEM;
+ goto out_uuid;
+ }
+ if (!tagset_add(&exp.ex_allow_tags, tag)) {
+ kfree(tag);
+ err = -ENOMEM;
+ goto out_uuid;
+ }
+ }
+ tagset_finalize(&exp.ex_allow_tags);
+ }
+
+ err = check_allow_tags(&exp);
+ if (err)
+ goto out_uuid;
+
err = check_export(&exp.ex_path, &exp.ex_flags,
exp.ex_uuid);
if (err)
@@ -1026,6 +1089,7 @@ static int nfsd_nl_parse_one_export(struct cache_detail *cd,
}
out_uuid:
+ tagset_destroy(&exp.ex_allow_tags);
kfree(exp.ex_uuid);
out_fslocs:
nfsd4_fslocs_free(&exp.ex_fslocs);
diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c
index fbee3676d253..4db094b1021f 100644
--- a/fs/nfsd/netlink.c
+++ b/fs/nfsd/netlink.c
@@ -10,6 +10,7 @@
#include "netlink.h"
#include <uapi/linux/nfsd_netlink.h>
+#include <uapi/linux/handshake.h>
/* Common nested types */
const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1] = {
@@ -41,7 +42,7 @@ const struct nla_policy nfsd_sock_nl_policy[NFSD_A_SOCK_TRANSPORT_NAME + 1] = {
[NFSD_A_SOCK_TRANSPORT_NAME] = { .type = NLA_NUL_STRING, },
};
-const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1] = {
+const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_ALLOW_TAGS + 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, },
@@ -55,6 +56,7 @@ const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1] =
[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, },
+ [NFSD_A_SVC_EXPORT_ALLOW_TAGS] = { .type = NLA_STRING, .len = HANDSHAKE_SESSION_TAG_MAX_LEN, },
};
const struct nla_policy nfsd_version_nl_policy[NFSD_A_VERSION_ENABLED + 1] = {
diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h
index af41aa0d4a65..133e99a0a3fc 100644
--- a/fs/nfsd/netlink.h
+++ b/fs/nfsd/netlink.h
@@ -11,6 +11,7 @@
#include <net/genetlink.h>
#include <uapi/linux/nfsd_netlink.h>
+#include <uapi/linux/handshake.h>
/* Common nested types */
extern const struct nla_policy nfsd_auth_flavor_nl_policy[NFSD_A_AUTH_FLAVOR_FLAGS + 1];
@@ -18,7 +19,7 @@ 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];
-extern const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_FSID + 1];
+extern const struct nla_policy nfsd_svc_export_nl_policy[NFSD_A_SVC_EXPORT_ALLOW_TAGS + 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,
diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h
index f5b75d5caba9..23a42c26ede0 100644
--- a/include/uapi/linux/nfsd_netlink.h
+++ b/include/uapi/linux/nfsd_netlink.h
@@ -165,6 +165,7 @@ enum {
NFSD_A_SVC_EXPORT_XPRTSEC,
NFSD_A_SVC_EXPORT_FLAGS,
NFSD_A_SVC_EXPORT_FSID,
+ NFSD_A_SVC_EXPORT_ALLOW_TAGS,
__NFSD_A_SVC_EXPORT_MAX,
NFSD_A_SVC_EXPORT_MAX = (__NFSD_A_SVC_EXPORT_MAX - 1)
--
2.54.0
More information about the Linux-nvme
mailing list