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 both the netlink and nfsd filesystem interfaces. Since key changes will break existing filehandles, the key can only be set once. After it has been set any attempts to set it will return -EEXIST. Link: https://lore.kernel.org/linux-nfs/cover.1769026777.git.bcodding@hammerspace.com Signed-off-by: Benjamin Coddington --- Documentation/netlink/specs/nfsd.yaml | 6 ++ fs/nfsd/netlink.c | 5 +- fs/nfsd/netns.h | 2 + fs/nfsd/nfsctl.c | 94 +++++++++++++++++++++++++++ fs/nfsd/trace.h | 25 +++++++ include/uapi/linux/nfsd_netlink.h | 1 + 6 files changed, 131 insertions(+), 2 deletions(-) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index badb2fe57c98..d348648033d9 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -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 number of running threads diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index 887525964451..81c943345d13 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -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, }, { diff --git a/fs/nfsd/netns.h b/fs/nfsd/netns.h index 9fa600602658..c8ed733240a0 100644 --- a/fs/nfsd/netns.h +++ b/fs/nfsd/netns.h @@ -16,6 +16,7 @@ #include #include #include +#include /* Hash tables for nfs4_clientid state */ #define CLIENT_HASH_BITS 4 @@ -224,6 +225,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 */ diff --git a/fs/nfsd/nfsctl.c b/fs/nfsd/nfsctl.c index 30caefb2522f..e59639efcf5c 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -49,6 +49,7 @@ enum { NFSD_Ports, NFSD_MaxBlkSize, NFSD_MinThreads, + NFSD_Fh_Key, NFSD_Filecache, NFSD_Leasetime, NFSD_Gracetime, @@ -69,6 +70,7 @@ static ssize_t write_versions(struct file *file, char *buf, size_t size); static ssize_t write_ports(struct file *file, char *buf, size_t size); static ssize_t write_maxblksize(struct file *file, char *buf, size_t size); static ssize_t write_minthreads(struct file *file, char *buf, size_t size); +static ssize_t write_fh_key(struct file *file, char *buf, size_t size); #ifdef CONFIG_NFSD_V4 static ssize_t write_leasetime(struct file *file, char *buf, size_t size); static ssize_t write_gracetime(struct file *file, char *buf, size_t size); @@ -88,6 +90,7 @@ static ssize_t (*const write_op[])(struct file *, char *, size_t) = { [NFSD_Ports] = write_ports, [NFSD_MaxBlkSize] = write_maxblksize, [NFSD_MinThreads] = write_minthreads, + [NFSD_Fh_Key] = write_fh_key, #ifdef CONFIG_NFSD_V4 [NFSD_Leasetime] = write_leasetime, [NFSD_Gracetime] = write_gracetime, @@ -950,6 +953,60 @@ static ssize_t write_minthreads(struct file *file, char *buf, size_t size) return scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, "%u\n", minthreads); } +/* + * write_fh_key - Set or report the current NFS filehandle key, the key + * can only be set once, else -EEXIST because changing the key + * will break existing filehandles. + * + * Input: + * buf: ignored + * size: zero + * OR + * + * Input: + * buf: C string containing a parseable UUID + * size: non-zero length of C string in @buf + * Output: + * On success: passed-in buffer filled with '\n'-terminated C string + * containing the standard UUID format of the server's fh_key + * return code is the size in bytes of the string + * On error: return code is zero or a negative errno value + */ +static ssize_t write_fh_key(struct file *file, char *buf, size_t size) +{ + struct nfsd_net *nn = net_generic(netns(file), nfsd_net_id); + int ret = -EEXIST; + + if (size > 35 && size < 38) { + siphash_key_t *sip_fh_key; + uuid_t uuid_fh_key; + + mutex_lock(&nfsd_mutex); + + /* Is the key already set? */ + if (nn->fh_key) + goto out; + + ret = uuid_parse(buf, &uuid_fh_key); + if (ret) + goto out; + + sip_fh_key = kmalloc(sizeof(siphash_key_t), GFP_KERNEL); + if (!sip_fh_key) { + ret = -ENOMEM; + goto out; + } + + memcpy(sip_fh_key, &uuid_fh_key, sizeof(siphash_key_t)); + nn->fh_key = sip_fh_key; + } + ret = scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, "%pUb\n", nn->fh_key); +out: + mutex_unlock(&nfsd_mutex); + trace_nfsd_ctl_fh_key_set((const char *)nn->fh_key, ret); + return ret; +} + #ifdef CONFIG_NFSD_V4 static ssize_t __nfsd4_write_time(struct file *file, char *buf, size_t size, time64_t *time, struct nfsd_net *nn) @@ -1343,6 +1400,7 @@ static int nfsd_fill_super(struct super_block *sb, struct fs_context *fc) [NFSD_Ports] = {"portlist", &transaction_ops, S_IWUSR|S_IRUGO}, [NFSD_MaxBlkSize] = {"max_block_size", &transaction_ops, S_IWUSR|S_IRUGO}, [NFSD_MinThreads] = {"min_threads", &transaction_ops, S_IWUSR|S_IRUGO}, + [NFSD_Fh_Key] = {"fh_key", &transaction_ops, S_IWUSR|S_IRUSR}, [NFSD_Filecache] = {"filecache", &nfsd_file_cache_stats_fops, S_IRUGO}, #ifdef CONFIG_NFSD_V4 [NFSD_Leasetime] = {"nfsv4leasetime", &transaction_ops, S_IWUSR|S_IRUSR}, @@ -1615,6 +1673,33 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb, 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. + */ +static int nfsd_nl_fh_key_set(const struct nlattr *attr, struct nfsd_net *nn) +{ + siphash_key_t *fh_key; + + if (nla_len(attr) != sizeof(siphash_key_t)) + return -EINVAL; + + /* Is the key already set? */ + if (nn->fh_key) + return -EEXIST; + + fh_key = kmalloc(sizeof(siphash_key_t), GFP_KERNEL); + if (!fh_key) + return -ENOMEM; + + memcpy(fh_key, nla_data(attr), sizeof(siphash_key_t)); + nn->fh_key = fh_key; + return 0; +} + /** * nfsd_nl_threads_set_doit - set the number of running threads * @skb: reply buffer @@ -1691,6 +1776,14 @@ int nfsd_nl_threads_set_doit(struct sk_buff *skb, struct genl_info *info) if (attr) nn->min_threads = nla_get_u32(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 && ret != -EEXIST) + goto out_unlock; + } + ret = nfsd_svc(nrpools, nthreads, net, get_current_cred(), scope); if (ret > 0) ret = 0; @@ -2284,6 +2377,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); diff --git a/fs/nfsd/trace.h b/fs/nfsd/trace.h index d1d0b0dd0545..c1a5f2fa44ab 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -2240,6 +2240,31 @@ 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( + __array(unsigned char, key, 16) + __field(unsigned long, result) + __field(bool, key_set) + ), + TP_fast_assign( + __entry->key_set = true; + if (!key) + __entry->key_set = false; + else + memcpy(__entry->key, key, 16); + __entry->result = result; + ), + TP_printk("key=%s result=%ld", + __entry->key_set ? __print_hex_str(__entry->key, 16) : "(null)", + __entry->result + ) +); + DECLARE_EVENT_CLASS(nfsd_copy_class, TP_PROTO( const struct nfsd4_copy *copy diff --git a/include/uapi/linux/nfsd_netlink.h b/include/uapi/linux/nfsd_netlink.h index e9efbc9e63d8..97c7447f4d14 100644 --- a/include/uapi/linux/nfsd_netlink.h +++ b/include/uapi/linux/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) -- 2.50.1