From: Chuck Lever 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 --- 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 +#include /* 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 #include +#include /* 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