Expand the nfsd_net to hold a siphash_key_t value "fh_key". Expand the netlink server interface to allow the setting of the 128-bit fh_key value to be used as a signing key for filehandles. Add a file to the nfsd filesystem to set and read the 128-bit key, formatted as a uuid. Signed-off-by: Benjamin Coddington --- Documentation/netlink/specs/nfsd.yaml | 12 ++++ fs/nfsd/netlink.c | 15 +++++ fs/nfsd/netlink.h | 1 + fs/nfsd/netns.h | 2 + fs/nfsd/nfsctl.c | 85 +++++++++++++++++++++++++++ fs/nfsd/trace.h | 19 ++++++ include/uapi/linux/nfsd_netlink.h | 2 + 7 files changed, 136 insertions(+) diff --git a/Documentation/netlink/specs/nfsd.yaml b/Documentation/netlink/specs/nfsd.yaml index badb2fe57c98..a467888cfa62 100644 --- a/Documentation/netlink/specs/nfsd.yaml +++ b/Documentation/netlink/specs/nfsd.yaml @@ -81,6 +81,9 @@ attribute-sets: - name: min-threads type: u32 + - + name: fh-key + type: binary - name: version attributes: @@ -227,3 +230,12 @@ operations: attributes: - mode - npools + - + name: fh-key-set + doc: set encryption key for filehandles + attribute-set: server + flags: [admin-perm] + do: + request: + attributes: + - fh-key diff --git a/fs/nfsd/netlink.c b/fs/nfsd/netlink.c index 887525964451..98100ee4bcd6 100644 --- a/fs/nfsd/netlink.c +++ b/fs/nfsd/netlink.c @@ -47,6 +47,14 @@ static const struct nla_policy nfsd_pool_mode_set_nl_policy[NFSD_A_POOL_MODE_MOD [NFSD_A_POOL_MODE_MODE] = { .type = NLA_NUL_STRING, }, }; +/* NFSD_CMD_FH_KEY_SET - do */ +static const struct nla_policy nfsd_fh_key_set_nl_policy[NFSD_A_SERVER_FH_KEY + 1] = { + [NFSD_A_SERVER_FH_KEY] = { + .type = NLA_BINARY, + .len = 16 + }, +}; + /* Ops table for nfsd */ static const struct genl_split_ops nfsd_nl_ops[] = { { @@ -102,6 +110,13 @@ static const struct genl_split_ops nfsd_nl_ops[] = { .doit = nfsd_nl_pool_mode_get_doit, .flags = GENL_CMD_CAP_DO, }, + { + .cmd = NFSD_CMD_FH_KEY_SET, + .doit = nfsd_nl_fh_key_set_doit, + .policy = nfsd_fh_key_set_nl_policy, + .maxattr = NFSD_A_SERVER_FH_KEY, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; struct genl_family nfsd_nl_family __ro_after_init = { diff --git a/fs/nfsd/netlink.h b/fs/nfsd/netlink.h index 478117ff6b8c..84d578d628e8 100644 --- a/fs/nfsd/netlink.h +++ b/fs/nfsd/netlink.h @@ -26,6 +26,7 @@ int nfsd_nl_listener_set_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_listener_get_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_pool_mode_set_doit(struct sk_buff *skb, struct genl_info *info); int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info); +int nfsd_nl_fh_key_set_doit(struct sk_buff *skb, struct genl_info *info); extern struct genl_family nfsd_nl_family; 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 8ccc65bb09fd..aabd66468413 100644 --- a/fs/nfsd/nfsctl.c +++ b/fs/nfsd/nfsctl.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "idmap.h" #include "nfsd.h" @@ -49,6 +50,7 @@ enum { NFSD_Ports, NFSD_MaxBlkSize, NFSD_MinThreads, + NFSD_Fh_Key, NFSD_Filecache, NFSD_Leasetime, NFSD_Gracetime, @@ -69,6 +71,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 +91,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 +954,54 @@ 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 + * + * 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); + + if (size > 35 && size < 38) { + siphash_key_t *sip_fh_key; + uuid_t uuid_fh_key; + int ret; + + /* Is the key already set? */ + if (nn->fh_key) + return -EEXIST; + + ret = uuid_parse(buf, &uuid_fh_key); + if (ret) + return ret; + + sip_fh_key = kmalloc(sizeof(siphash_key_t), GFP_KERNEL); + if (!sip_fh_key) + return -ENOMEM; + + memcpy(sip_fh_key, &uuid_fh_key, sizeof(siphash_key_t)); + nn->fh_key = sip_fh_key; + + trace_nfsd_ctl_fh_key_set((const char *)sip_fh_key, ret); + } + + return scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, "%pUb\n", + nn->fh_key); +} + #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 +1395,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}, @@ -2199,6 +2252,37 @@ int nfsd_nl_pool_mode_get_doit(struct sk_buff *skb, struct genl_info *info) return err; } +int nfsd_nl_fh_key_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + siphash_key_t *fh_key; + struct nfsd_net *nn; + int fh_key_len; + int ret; + + if (GENL_REQ_ATTR_CHECK(info, NFSD_A_SERVER_FH_KEY)) + return -EINVAL; + + fh_key_len = nla_len(info->attrs[NFSD_A_SERVER_FH_KEY]); + if (fh_key_len != sizeof(siphash_key_t)) + return -EINVAL; + + /* Is the key already set? */ + nn = net_generic(genl_info_net(info), nfsd_net_id); + 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(info->attrs[NFSD_A_SERVER_FH_KEY]), sizeof(siphash_key_t)); + nn = net_generic(genl_info_net(info), nfsd_net_id); + nn->fh_key = fh_key; + + trace_nfsd_ctl_fh_key_set((const char *)fh_key, ret); + return ret; +} + /** * nfsd_net_init - Prepare the nfsd_net portion of a new net namespace * @net: a freshly-created network namespace @@ -2284,6 +2368,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..2e7d2a4cb7e7 100644 --- a/fs/nfsd/trace.h +++ b/fs/nfsd/trace.h @@ -2240,6 +2240,25 @@ 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) + ), + TP_fast_assign( + memcpy(__entry->key, key, 16); + __entry->result = result; + ), + TP_printk("key=%s result=%ld", __print_hex(__entry->key, 16), + __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..29e5d3d657ca 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) @@ -90,6 +91,7 @@ enum { NFSD_CMD_LISTENER_GET, NFSD_CMD_POOL_MODE_SET, NFSD_CMD_POOL_MODE_GET, + NFSD_CMD_FH_KEY_SET, __NFSD_CMD_MAX, NFSD_CMD_MAX = (__NFSD_CMD_MAX - 1) -- 2.50.1