NFS: add a key type that reveals the namespace .nfs keyring serial number

The per-namespace .nfs keyring is linked into no keyring that
userspace possesses, so a tool that provisions x509 credentials, or a
mount helper that searches for them, cannot reach it. Every keyring
operation from userspace takes a serial number, and nothing hands
this one out.

Register an "nfs_keyring" key type whose request_key handler runs in
the caller's context, without an upcall, and instantiates the key
with the serial number of the caller's namespace .nfs keyring.
Userspace reads the serial number with one request_key() and one
keyctl_read().

A keyring search runs before the handler does, and a task keeps its
session keyring across setns(). The type carries KEY_TYPE_NET_DOMAIN
so a key instantiated in one namespace does not answer a request from
another. Its preparse accepts no payload but the caller's own serial
number, so a key planted by add_key() cannot misdirect a later
request.

Register the type after register_pernet_subsys() has installed
nfs_net_id, and unregister it before the per-namespace state goes
away. unregister_key_type() waits for in-flight request_key() calls
to drain, so the handler never runs against a freed struct nfs_net.

Signed-off-by: Chuck Lever <cel@kernel.org>
Signed-off-by: Anna Schumaker <anna.schumaker@hammerspace.com>
This commit is contained in:
Chuck Lever 2026-10-06 11:31:16 -04:00 committed by Anna Schumaker
parent e27b84e076
commit 4f6cd78de1
3 changed files with 163 additions and 1 deletions

View File

@ -7,6 +7,7 @@ NFS
:maxdepth: 1
client-identifier
keyring
exporting
localio
pnfs

View File

@ -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.

View File

@ -42,6 +42,12 @@
#include <linux/uaccess.h>
#include <linux/iversion.h>
#include <linux/fileattr.h>
#include <linux/nsproxy.h>
#include <linux/key-type.h>
#include <keys/user-type.h>
#ifdef CONFIG_KEYS
#include <keys/request_key_auth-type.h>
#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();