diff --git a/Documentation/filesystems/nfs/index.rst b/Documentation/filesystems/nfs/index.rst index a29a212b5b4d..dab5f16aaad1 100644 --- a/Documentation/filesystems/nfs/index.rst +++ b/Documentation/filesystems/nfs/index.rst @@ -7,6 +7,7 @@ NFS :maxdepth: 1 client-identifier + keyring exporting localio pnfs diff --git a/Documentation/filesystems/nfs/keyring.rst b/Documentation/filesystems/nfs/keyring.rst new file mode 100644 index 000000000000..29367e71a801 --- /dev/null +++ b/Documentation/filesystems/nfs/keyring.rst @@ -0,0 +1,69 @@ +.. SPDX-License-Identifier: GPL-2.0 + +==================================== +The per-namespace NFS client keyring +==================================== + +The NFS client holds one keyring, named ".nfs", per network +namespace. An xprtsec=mtls mount presents the client certificate and +private key named by its cert_serial= and privkey_serial= mount +options, and the handshake links the mount's namespace .nfs keyring +into tlshd before tlshd reads those keys. Keys placed on the .nfs +keyring therefore need not grant user read permission: tlshd reaches +them as a possessor, and a tlshd in another network namespace never +possesses them. + +The keyring is owned by the owner of the network namespace's user +namespace, with KEY_POS_ALL and KEY_USR_ALL less SETATTR. It is not +charged to any quota. Keys added to it by userspace are charged to +the user that adds them. + +Finding the keyring serial number +================================= + +The .nfs keyring is not linked into any keyring that userspace +possesses, so a serial number is the only way to name it. The NFS +client registers a key type, "nfs_keyring", whose sole purpose is to +hand that serial number out. Its request_key handler runs in the +caller's context and does not upcall to /sbin/request-key. + +To obtain the serial number, request a key of type "nfs_keyring" with +the description ".nfs" and read its payload:: + + id=$(keyctl request2 nfs_keyring .nfs "" @s) + serial=$(keyctl print $id) + +The contract of the key type is: + + * The description is the string ".nfs". Any other description is + rejected with EINVAL before a key is allocated. + + * The callout info must be present. request_key() with a NULL + callout_info never invokes a handler and returns ENOKEY when no + matching key exists, so use request_key() with an empty string, or + ``keyctl request2`` rather than ``keyctl request``. The content of + the callout info is ignored. + + * The payload is the keyring serial number as decimal ASCII digits + with no terminating NUL. The return value of keyctl_read() gives + the length; a buffer of 12 bytes is sufficient. + + * add_key() with this type accepts only that payload. A key carrying + any other value is rejected with EINVAL, so a key found in the + caller's keyrings always holds the serial number of the caller's + namespace. + + * The key type carries KEY_TYPE_NET_DOMAIN. A key instantiated in one + network namespace does not answer a request made in another, so a + process that keeps its session keyring across setns() receives the + serial number of the namespace it is in at the time of the request. + +The serial number is not a secret. The keyring's own permissions +decide what a caller can do with it. + +Provisioning credentials +======================== + +Add the client certificate and private key to the .nfs keyring as +keys that grant no user read permission, then pass their serial +numbers as cert_serial= and privkey_serial= mount options. diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index 2b494fa5ecc0..fe7a05dec02f 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -42,6 +42,12 @@ #include #include #include +#include +#include +#include +#ifdef CONFIG_KEYS +#include +#endif #include "nfs4_fs.h" #include "callback.h" @@ -2667,6 +2673,76 @@ static void nfs_exit_keyring(struct nfs_net *nn) { key_put(nn->nfs_keyring); } + +static int nfs_keyring_vet_description(const char *desc) +{ + return strcmp(desc, ".nfs") ? -EINVAL : 0; +} + +static int nfs_keyring_format_serial(char *buf, size_t len) +{ + struct key *keyring = nfs_net_keyring(current->nsproxy->net_ns); + + return snprintf(buf, len, "%d", key_serial(keyring)); +} + +/* + * A key planted by add_key() answers request_key() before the handler + * runs. Accept only the serial number the handler would produce. + */ +static int nfs_keyring_preparse(struct key_preparsed_payload *prep) +{ + char serial[12]; + int len; + + len = nfs_keyring_format_serial(serial, sizeof(serial)); + if (prep->datalen != len || !prep->data || + memcmp(prep->data, serial, len)) + return -EINVAL; + return user_preparse(prep); +} + +static int nfs_keyring_request_key(struct key *authkey, void *aux) +{ + struct request_key_auth *rka = get_request_key_auth(authkey); + char serial[12]; + int len, ret; + + len = nfs_keyring_format_serial(serial, sizeof(serial)); + ret = key_instantiate_and_link(rka->target_key, serial, len, + rka->dest_keyring, authkey); + if (ret < 0) + complete_request_key(authkey, ret); + return ret; +} + +/* + * A session keyring survives setns(). Without the net domain tag, a + * key instantiated in one namespace answers a request from another. + */ +static struct key_type key_type_nfs_keyring = { + .name = "nfs_keyring", + .flags = KEY_TYPE_NET_DOMAIN, + .vet_description = nfs_keyring_vet_description, + .preparse = nfs_keyring_preparse, + .free_preparse = user_free_preparse, + .instantiate = generic_key_instantiate, + .revoke = user_revoke, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, + .request_key = nfs_keyring_request_key, +}; + +static int __init nfs_register_key_type(void) +{ + return register_key_type(&key_type_nfs_keyring); +} + +static void nfs_unregister_key_type(void) +{ + unregister_key_type(&key_type_nfs_keyring); +} #else static inline int nfs_init_keyring(struct net *net) { @@ -2676,6 +2752,15 @@ static inline int nfs_init_keyring(struct net *net) static inline void nfs_exit_keyring(struct nfs_net *nn) { } + +static inline int nfs_register_key_type(void) +{ + return 0; +} + +static inline void nfs_unregister_key_type(void) +{ +} #endif /* CONFIG_KEYS */ static int nfs_net_init(struct net *net) @@ -2740,10 +2825,14 @@ static int __init init_nfs_fs(void) if (err < 0) goto err_sysfs; - err = nfsiod_start(); + err = nfs_register_key_type(); if (err) goto err_pernet; + err = nfsiod_start(); + if (err) + goto err_keytype; + err = nfs_fs_proc_init(); if (err) goto err_nfsiod; @@ -2787,6 +2876,8 @@ static int __init init_nfs_fs(void) nfs_fs_proc_exit(); err_nfsiod: nfsiod_stop(); +err_keytype: + nfs_unregister_key_type(); err_pernet: unregister_pernet_subsys(&nfs_net_ops); err_sysfs: @@ -2801,6 +2892,7 @@ static void __exit exit_nfs_fs(void) nfs_destroy_readpagecache(); nfs_destroy_inodecache(); nfs_destroy_nfspagecache(); + nfs_unregister_key_type(); unregister_pernet_subsys(&nfs_net_ops); unregister_nfs_fs(); nfs_fs_proc_exit();