* [PATCH RFC 1/5] NFS: name the init_nfs_fs() error labels
2026-09-18 14:05 [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Chuck Lever
@ 2026-09-18 14:05 ` Chuck Lever
2026-09-18 14:05 ` [PATCH RFC 2/5] NFS: allocate the .nfs keyring per network namespace Chuck Lever
` (4 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Chuck Lever @ 2026-09-18 14:05 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc,
Chuck Lever
The unwind labels in init_nfs_fs() are numbered, and the numbering
already skips out8, so a reader has to count the label block to
learn what each one undoes. Inserting an init step means either
renumbering every label below it or leaving the sequence out of
order, and a goto that picks the wrong number unwinds the wrong
step.
Name each label for the step it undoes, as coding-style.rst asks.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs/inode.c | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 3022454f7698..832923be43a9 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -2722,64 +2722,64 @@ static int __init init_nfs_fs(void)
err = nfs_sysfs_init();
if (err < 0)
- goto out10;
+ goto err_keyring;
err = register_pernet_subsys(&nfs_net_ops);
if (err < 0)
- goto out9;
+ goto err_sysfs;
err = nfsiod_start();
if (err)
- goto out7;
+ goto err_pernet;
err = nfs_fs_proc_init();
if (err)
- goto out6;
+ goto err_nfsiod;
err = nfs_init_nfspagecache();
if (err)
- goto out5;
+ goto err_proc;
err = nfs_init_inodecache();
if (err)
- goto out4;
+ goto err_nfspagecache;
err = nfs_init_readpagecache();
if (err)
- goto out3;
+ goto err_inodecache;
err = nfs_init_writepagecache();
if (err)
- goto out2;
+ goto err_readpagecache;
err = nfs_init_directcache();
if (err)
- goto out1;
+ goto err_writepagecache;
err = register_nfs_fs();
if (err)
- goto out0;
+ goto err_directcache;
return 0;
-out0:
+err_directcache:
nfs_destroy_directcache();
-out1:
+err_writepagecache:
nfs_destroy_writepagecache();
-out2:
+err_readpagecache:
nfs_destroy_readpagecache();
-out3:
+err_inodecache:
nfs_destroy_inodecache();
-out4:
+err_nfspagecache:
nfs_destroy_nfspagecache();
-out5:
+err_proc:
nfs_fs_proc_exit();
-out6:
+err_nfsiod:
nfsiod_stop();
-out7:
+err_pernet:
unregister_pernet_subsys(&nfs_net_ops);
-out9:
+err_sysfs:
nfs_sysfs_exit();
-out10:
+err_keyring:
nfs_exit_keyring();
return err;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH RFC 2/5] NFS: allocate the .nfs keyring per network namespace
2026-09-18 14:05 [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Chuck Lever
2026-09-18 14:05 ` [PATCH RFC 1/5] NFS: name the init_nfs_fs() error labels Chuck Lever
@ 2026-09-18 14:05 ` Chuck Lever
2026-09-18 14:44 ` Hannes Reinecke
2026-09-18 14:05 ` [PATCH RFC 3/5] SUNRPC: pass a keyring serial to the TLS handshake Chuck Lever
` (3 subsequent siblings)
5 siblings, 1 reply; 15+ messages in thread
From: Chuck Lever @ 2026-09-18 14:05 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc,
Chuck Lever
Commit 87268f7a4f1f ("nfs: create a kernel keyring") allocates one
.nfs keyring at module load, and nothing in the NFS client reads it.
One module-wide keyring also cannot isolate x.509 credentials
between network namespaces. Each tlshd instance services the
handshake socket of one network namespace, so a credential
provisioned for that namespace's mounts has to be reachable by that
tlshd and by no other.
Allocate one .nfs keyring per network namespace in nfs_net_init()
and release it in nfs_net_exit(). tlshd finds a keyring by name
through /proc/keys, which is not namespace scoped, so each handshake
request has to carry the keyring serial instead. Allocate the
keyring under a kernel credential rather than that of the task
creating the namespace, so an LSM labels every namespace's keyring
the same way.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs/inode.c | 81 +++++++++++++++++++++++++++++++---------------------------
fs/nfs/netns.h | 2 ++
2 files changed, 46 insertions(+), 37 deletions(-)
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 832923be43a9..bd327fbb12d8 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -2641,11 +2641,50 @@ static int nfsiod_start(void)
unsigned int nfs_net_id;
EXPORT_SYMBOL_GPL(nfs_net_id);
+#ifdef CONFIG_KEYS
+static int nfs_init_keyring(struct nfs_net *nn)
+{
+ struct cred *cred;
+ struct key *keyring;
+
+ cred = prepare_kernel_cred(&init_task);
+ if (!cred)
+ return -ENOMEM;
+ keyring = keyring_alloc(".nfs", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
+ (KEY_POS_ALL & ~KEY_POS_SETATTR) |
+ (KEY_USR_ALL & ~KEY_USR_SETATTR),
+ KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
+ put_cred(cred);
+ if (IS_ERR(keyring))
+ return PTR_ERR(keyring);
+ nn->nfs_keyring = keyring;
+ return 0;
+}
+
+static void nfs_exit_keyring(struct nfs_net *nn)
+{
+ key_put(nn->nfs_keyring);
+}
+#else
+static inline int nfs_init_keyring(struct nfs_net *nn)
+{
+ return 0;
+}
+
+static inline void nfs_exit_keyring(struct nfs_net *nn)
+{
+}
+#endif /* CONFIG_KEYS */
+
static int nfs_net_init(struct net *net)
{
struct nfs_net *nn = net_generic(net, nfs_net_id);
int err;
+ err = nfs_init_keyring(nn);
+ if (err)
+ return err;
+
nfs_clients_init(net);
if (!rpc_proc_register(net, &nn->rpcstats)) {
@@ -2663,14 +2702,18 @@ static int nfs_net_init(struct net *net)
rpc_proc_unregister(net, "nfs");
err_proc_rpc:
nfs_clients_exit(net);
+ nfs_exit_keyring(nn);
return err;
}
static void nfs_net_exit(struct net *net)
{
+ struct nfs_net *nn = net_generic(net, nfs_net_id);
+
rpc_proc_unregister(net, "nfs");
nfs_fs_proc_net_exit(net);
nfs_clients_exit(net);
+ nfs_exit_keyring(nn);
}
static struct pernet_operations nfs_net_ops = {
@@ -2680,35 +2723,6 @@ static struct pernet_operations nfs_net_ops = {
.size = sizeof(struct nfs_net),
};
-#ifdef CONFIG_KEYS
-static struct key *nfs_keyring;
-
-static int __init nfs_init_keyring(void)
-{
- nfs_keyring = keyring_alloc(".nfs",
- GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
- current_cred(),
- (KEY_POS_ALL & ~KEY_POS_SETATTR) |
- (KEY_USR_ALL & ~KEY_USR_SETATTR),
- KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
- return PTR_ERR_OR_ZERO(nfs_keyring);
-}
-
-static void nfs_exit_keyring(void)
-{
- key_put(nfs_keyring);
-}
-#else
-static inline int nfs_init_keyring(void)
-{
- return 0;
-}
-
-static inline void nfs_exit_keyring(void)
-{
-}
-#endif /* CONFIG_KEYS */
-
/*
* Initialize NFS
*/
@@ -2716,13 +2730,9 @@ static int __init init_nfs_fs(void)
{
int err;
- err = nfs_init_keyring();
- if (err)
- return err;
-
err = nfs_sysfs_init();
if (err < 0)
- goto err_keyring;
+ return err;
err = register_pernet_subsys(&nfs_net_ops);
if (err < 0)
@@ -2779,8 +2789,6 @@ static int __init init_nfs_fs(void)
unregister_pernet_subsys(&nfs_net_ops);
err_sysfs:
nfs_sysfs_exit();
-err_keyring:
- nfs_exit_keyring();
return err;
}
@@ -2796,7 +2804,6 @@ static void __exit exit_nfs_fs(void)
nfs_fs_proc_exit();
nfsiod_stop();
nfs_sysfs_exit();
- nfs_exit_keyring();
}
/* Not quite true; I just maintain it */
diff --git a/fs/nfs/netns.h b/fs/nfs/netns.h
index 36658579100d..da0854510404 100644
--- a/fs/nfs/netns.h
+++ b/fs/nfs/netns.h
@@ -16,6 +16,7 @@ struct bl_dev_msg {
uint32_t major, minor;
};
+struct key;
struct nfs_netns_client;
struct nfs_net {
@@ -36,6 +37,7 @@ struct nfs_net {
#endif /* CONFIG_NFS_V4 */
struct nfs_netns_client *nfs_client;
spinlock_t nfs_client_lock;
+ struct key *nfs_keyring;
ktime_t boot_time;
struct rpc_stat rpcstats;
#ifdef CONFIG_PROC_FS
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH RFC 2/5] NFS: allocate the .nfs keyring per network namespace
2026-09-18 14:05 ` [PATCH RFC 2/5] NFS: allocate the .nfs keyring per network namespace Chuck Lever
@ 2026-09-18 14:44 ` Hannes Reinecke
2026-09-18 15:15 ` Chuck Lever
2026-09-19 16:22 ` Chuck Lever
0 siblings, 2 replies; 15+ messages in thread
From: Hannes Reinecke @ 2026-09-18 14:44 UTC (permalink / raw)
To: Chuck Lever, Trond Myklebust, Anna Schumaker, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Christian Brauner,
David Howells, Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc
On 9/18/26 4:05 PM, Chuck Lever wrote:
> Commit 87268f7a4f1f ("nfs: create a kernel keyring") allocates one
> .nfs keyring at module load, and nothing in the NFS client reads it.
> One module-wide keyring also cannot isolate x.509 credentials
> between network namespaces. Each tlshd instance services the
> handshake socket of one network namespace, so a credential
> provisioned for that namespace's mounts has to be reachable by that
> tlshd and by no other.
>
> Allocate one .nfs keyring per network namespace in nfs_net_init()
> and release it in nfs_net_exit(). tlshd finds a keyring by name
> through /proc/keys, which is not namespace scoped, so each handshake
> request has to carry the keyring serial instead. Allocate the
> keyring under a kernel credential rather than that of the task
> creating the namespace, so an LSM labels every namespace's keyring
> the same way.
>
> Signed-off-by: Chuck Lever <cel@kernel.org>
> ---
> fs/nfs/inode.c | 81 +++++++++++++++++++++++++++++++---------------------------
> fs/nfs/netns.h | 2 ++
> 2 files changed, 46 insertions(+), 37 deletions(-)
>
> diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
> index 832923be43a9..bd327fbb12d8 100644
> --- a/fs/nfs/inode.c
> +++ b/fs/nfs/inode.c
> @@ -2641,11 +2641,50 @@ static int nfsiod_start(void)
> unsigned int nfs_net_id;
> EXPORT_SYMBOL_GPL(nfs_net_id);
>
> +#ifdef CONFIG_KEYS
> +static int nfs_init_keyring(struct nfs_net *nn)
> +{
> + struct cred *cred;
> + struct key *keyring;
> +
> + cred = prepare_kernel_cred(&init_task);
> + if (!cred)
> + return -ENOMEM;
> + keyring = keyring_alloc(".nfs", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, cred,
> + (KEY_POS_ALL & ~KEY_POS_SETATTR) |
> + (KEY_USR_ALL & ~KEY_USR_SETATTR),
> + KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
> + put_cred(cred);
> + if (IS_ERR(keyring))
> + return PTR_ERR(keyring);
> + nn->nfs_keyring = keyring;
> + return 0;
> +}
> +
> +static void nfs_exit_keyring(struct nfs_net *nn)
> +{
> + key_put(nn->nfs_keyring);
> +}
> +#else
> +static inline int nfs_init_keyring(struct nfs_net *nn)
> +{
> + return 0;
> +}
> +
> +static inline void nfs_exit_keyring(struct nfs_net *nn)
> +{
> +}
> +#endif /* CONFIG_KEYS */
> +
> static int nfs_net_init(struct net *net)
> {
> struct nfs_net *nn = net_generic(net, nfs_net_id);
> int err;
>
> + err = nfs_init_keyring(nn);
> + if (err)
> + return err;
> +
> nfs_clients_init(net);
>
> if (!rpc_proc_register(net, &nn->rpcstats)) {
> @@ -2663,14 +2702,18 @@ static int nfs_net_init(struct net *net)
> rpc_proc_unregister(net, "nfs");
> err_proc_rpc:
> nfs_clients_exit(net);
> + nfs_exit_keyring(nn);
> return err;
> }
>
> static void nfs_net_exit(struct net *net)
> {
> + struct nfs_net *nn = net_generic(net, nfs_net_id);
> +
> rpc_proc_unregister(net, "nfs");
> nfs_fs_proc_net_exit(net);
> nfs_clients_exit(net);
> + nfs_exit_keyring(nn);
> }
>
> static struct pernet_operations nfs_net_ops = {
> @@ -2680,35 +2723,6 @@ static struct pernet_operations nfs_net_ops = {
> .size = sizeof(struct nfs_net),
> };
>
> -#ifdef CONFIG_KEYS
> -static struct key *nfs_keyring;
> -
> -static int __init nfs_init_keyring(void)
> -{
> - nfs_keyring = keyring_alloc(".nfs",
> - GLOBAL_ROOT_UID, GLOBAL_ROOT_GID,
> - current_cred(),
> - (KEY_POS_ALL & ~KEY_POS_SETATTR) |
> - (KEY_USR_ALL & ~KEY_USR_SETATTR),
> - KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
> - return PTR_ERR_OR_ZERO(nfs_keyring);
> -}
> -
> -static void nfs_exit_keyring(void)
> -{
> - key_put(nfs_keyring);
> -}
> -#else
> -static inline int nfs_init_keyring(void)
> -{
> - return 0;
> -}
> -
> -static inline void nfs_exit_keyring(void)
> -{
> -}
> -#endif /* CONFIG_KEYS */
> -
> /*
> * Initialize NFS
> */
> @@ -2716,13 +2730,9 @@ static int __init init_nfs_fs(void)
> {
> int err;
>
> - err = nfs_init_keyring();
> - if (err)
> - return err;
> -
> err = nfs_sysfs_init();
> if (err < 0)
> - goto err_keyring;
> + return err;
>
> err = register_pernet_subsys(&nfs_net_ops);
> if (err < 0)
> @@ -2779,8 +2789,6 @@ static int __init init_nfs_fs(void)
> unregister_pernet_subsys(&nfs_net_ops);
> err_sysfs:
> nfs_sysfs_exit();
> -err_keyring:
> - nfs_exit_keyring();
> return err;
> }
>
> @@ -2796,7 +2804,6 @@ static void __exit exit_nfs_fs(void)
> nfs_fs_proc_exit();
> nfsiod_stop();
> nfs_sysfs_exit();
> - nfs_exit_keyring();
> }
>
> /* Not quite true; I just maintain it */
> diff --git a/fs/nfs/netns.h b/fs/nfs/netns.h
> index 36658579100d..da0854510404 100644
> --- a/fs/nfs/netns.h
> +++ b/fs/nfs/netns.h
> @@ -16,6 +16,7 @@ struct bl_dev_msg {
> uint32_t major, minor;
> };
>
> +struct key;
> struct nfs_netns_client;
>
> struct nfs_net {
> @@ -36,6 +37,7 @@ struct nfs_net {
> #endif /* CONFIG_NFS_V4 */
> struct nfs_netns_client *nfs_client;
> spinlock_t nfs_client_lock;
> + struct key *nfs_keyring;
> ktime_t boot_time;
> struct rpc_stat rpcstats;
> #ifdef CONFIG_PROC_FS
>
Curiously enough, I had been pondering a similar issue.
Thing is, when running within a container (eg a docker one) access
access to /proc/keys might be restricted, and from what I've
gathered each container gets its own, _empty_ keyring.
(certainly an empty session keyring ...).
So I wonder what'll happen with the predefined keyrings (like the
.nvme keyring); one possibility is surely to make them network
namespace aware.
But the alternative approach I'm exploring is to allow each container
to create its own (.nvme) keyring; that would have the advantage of
being more flexible and we wouldn't need to rely on 'magic' names.
Hmm?
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH RFC 2/5] NFS: allocate the .nfs keyring per network namespace
2026-09-18 14:44 ` Hannes Reinecke
@ 2026-09-18 15:15 ` Chuck Lever
2026-09-19 16:22 ` Chuck Lever
1 sibling, 0 replies; 15+ messages in thread
From: Chuck Lever @ 2026-09-18 15:15 UTC (permalink / raw)
To: Hannes Reinecke, Trond Myklebust, Anna Schumaker, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Christian Brauner,
David Howells, Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc
On Fri, Sep 18, 2026, at 10:44 AM, Hannes Reinecke wrote:
> On 9/18/26 4:05 PM, Chuck Lever wrote:
>> Commit 87268f7a4f1f ("nfs: create a kernel keyring") allocates one
>> .nfs keyring at module load, and nothing in the NFS client reads it.
>> One module-wide keyring also cannot isolate x.509 credentials
>> between network namespaces. Each tlshd instance services the
>> handshake socket of one network namespace, so a credential
>> provisioned for that namespace's mounts has to be reachable by that
>> tlshd and by no other.
>>
>> Allocate one .nfs keyring per network namespace in nfs_net_init()
>> and release it in nfs_net_exit(). tlshd finds a keyring by name
>> through /proc/keys, which is not namespace scoped, so each handshake
>> request has to carry the keyring serial instead. Allocate the
>> keyring under a kernel credential rather than that of the task
>> creating the namespace, so an LSM labels every namespace's keyring
>> the same way.
> Curiously enough, I had been pondering a similar issue.
> Thing is, when running within a container (eg a docker one) access
> access to /proc/keys might be restricted, and from what I've
> gathered each container gets its own, _empty_ keyring.
> (certainly an empty session keyring ...).
> So I wonder what'll happen with the predefined keyrings (like the
> .nvme keyring); one possibility is surely to make them network
> namespace aware.
> But the alternative approach I'm exploring is to allow each container
> to create its own (.nvme) keyring; that would have the advantage of
> being more flexible and we wouldn't need to rely on 'magic' names.
IMO we need to hear from the folks who have deep keyring expertise
to understand what is most flexible and most idiomatic. I'm certainly
not expert enough to make those calls.
It would be great if tlshd's NVMe and NFS/NFSD keyring support
behaved consistently with each other, but perhaps that's just my
compulsion for symmetry talking.
I'm willing to take a look at patches, if you have any.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH RFC 2/5] NFS: allocate the .nfs keyring per network namespace
2026-09-18 14:44 ` Hannes Reinecke
2026-09-18 15:15 ` Chuck Lever
@ 2026-09-19 16:22 ` Chuck Lever
1 sibling, 0 replies; 15+ messages in thread
From: Chuck Lever @ 2026-09-19 16:22 UTC (permalink / raw)
To: Hannes Reinecke
Cc: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg, linux-nfs, keyrings, kernel-tls-handshake, netdev,
linux-doc
On 9/18/26 10:44 AM, Hannes Reinecke wrote:
> Thing is, when running within a container (eg a docker one) access
> access to /proc/keys might be restricted, and from what I've
> gathered each container gets its own, _empty_ keyring.
> (certainly an empty session keyring ...).
IMO, neither one affects patch 5. The key type has its own request_key
handler, so construct_key() calls it in the requesting task's context.
There's no upcall, no /proc/keys. It reports the .nfs keyring of
current->nsproxy->net_ns, and KEY_TYPE_NET_DOMAIN keeps a key
instantiated in one netns from answering another. An empty session
keyring is a fine destination, and with no session keyring at all
look_up_user_keyrings() creates a per-user-namespace one on demand.
What does break is add_key() into the .nfs keyring from a container
with its own user namespace. Patch 2 allocates it owned by global
root with no OTH permissions, so a root mapped to a non-zero kuid
gets EACCES. A netns-only container (Docker's default) works end to
end. NFS is not FS_USERNS_MOUNT either, so whoever mounts on behalf
of a user-ns container can provision the keyring too. If a
netns-owned keyring is wanted anyway, patch 2 can allocate it with
net->user_ns->owner instead of GLOBAL_ROOT_UID. I'd like the
keyrings folks to weigh in on that, please.
Regarding the idea of letting each container create its own keyring,
the kernel still has to learn which keyring to link into tlshd for a
handshake, and the serial number in the handshake request already
carries that. The per-namespace allocation decides only who creates
the keyring and who owns it, not how it is named on the wire.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH RFC 3/5] SUNRPC: pass a keyring serial to the TLS handshake
2026-09-18 14:05 [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Chuck Lever
2026-09-18 14:05 ` [PATCH RFC 1/5] NFS: name the init_nfs_fs() error labels Chuck Lever
2026-09-18 14:05 ` [PATCH RFC 2/5] NFS: allocate the .nfs keyring per network namespace Chuck Lever
@ 2026-09-18 14:05 ` Chuck Lever
2026-09-18 14:05 ` [PATCH RFC 4/5] NFS: name the namespace .nfs keyring in the x509 handshake Chuck Lever
` (2 subsequent siblings)
5 siblings, 0 replies; 15+ messages in thread
From: Chuck Lever @ 2026-09-18 14:05 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc,
Chuck Lever
The handshake upcall links the keyring named in ta_keyring into
tlshd's process keyring before tlshd reads the client certificate and
private key. That link is how tlshd gains possession of keys that
grant no user read permission. xprtsock never fills the field, so an
x509 handshake can present only keys that tlshd's own credentials can
read.
Add a keyring serial to struct xprtsec_parms and pass it to the x509
handshake, so an RPC client can name the keyring that holds its
credentials.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs/fs_context.c | 1 +
fs/nfs/nfs3client.c | 1 +
fs/nfs/nfs4client.c | 1 +
include/linux/sunrpc/xprt.h | 1 +
net/sunrpc/xprtsock.c | 1 +
5 files changed, 5 insertions(+)
diff --git a/fs/nfs/fs_context.c b/fs/nfs/fs_context.c
index 1967de7d1dff..f8f5f8b2954e 100644
--- a/fs/nfs/fs_context.c
+++ b/fs/nfs/fs_context.c
@@ -1750,6 +1750,7 @@ static int nfs_init_fs_context(struct fs_context *fc)
ctx->minorversion = 0;
ctx->need_mount = true;
ctx->xprtsec.policy = RPC_XPRTSEC_NONE;
+ ctx->xprtsec.keyring_serial = TLS_NO_KEYRING;
ctx->xprtsec.cert_serial = TLS_NO_CERT;
ctx->xprtsec.privkey_serial = TLS_NO_PRIVKEY;
diff --git a/fs/nfs/nfs3client.c b/fs/nfs/nfs3client.c
index 5d97c1d38bb6..c5d8278838b2 100644
--- a/fs/nfs/nfs3client.c
+++ b/fs/nfs/nfs3client.c
@@ -101,6 +101,7 @@ struct nfs_client *nfs3_set_ds_client(struct nfs_server *mds_srv,
.cred = mds_srv->cred,
.xprtsec = {
.policy = RPC_XPRTSEC_NONE,
+ .keyring_serial = TLS_NO_KEYRING,
.cert_serial = TLS_NO_CERT,
.privkey_serial = TLS_NO_PRIVKEY,
},
diff --git a/fs/nfs/nfs4client.c b/fs/nfs/nfs4client.c
index b661f446ea49..05df0fcabfb5 100644
--- a/fs/nfs/nfs4client.c
+++ b/fs/nfs/nfs4client.c
@@ -809,6 +809,7 @@ struct nfs_client *nfs4_set_ds_client(struct nfs_server *mds_srv,
.cred = mds_srv->cred,
.xprtsec = {
.policy = RPC_XPRTSEC_NONE,
+ .keyring_serial = TLS_NO_KEYRING,
.cert_serial = TLS_NO_CERT,
.privkey_serial = TLS_NO_PRIVKEY,
},
diff --git a/include/linux/sunrpc/xprt.h b/include/linux/sunrpc/xprt.h
index a82045804d34..005730623f3b 100644
--- a/include/linux/sunrpc/xprt.h
+++ b/include/linux/sunrpc/xprt.h
@@ -145,6 +145,7 @@ struct xprtsec_parms {
enum xprtsec_policies policy;
/* authentication material */
+ key_serial_t keyring_serial;
key_serial_t cert_serial;
key_serial_t privkey_serial;
};
diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
index 7f60723fa64d..2825dec82d6e 100644
--- a/net/sunrpc/xprtsock.c
+++ b/net/sunrpc/xprtsock.c
@@ -2636,6 +2636,7 @@ static int xs_tls_handshake_sync(struct rpc_xprt *lower_xprt, struct xprtsec_par
goto out_put_xprt;
break;
case RPC_XPRTSEC_TLS_X509:
+ args.ta_keyring = xprtsec->keyring_serial;
args.ta_my_cert = xprtsec->cert_serial;
args.ta_my_privkey = xprtsec->privkey_serial;
rc = tls_client_hello_x509(&args, GFP_KERNEL);
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH RFC 4/5] NFS: name the namespace .nfs keyring in the x509 handshake
2026-09-18 14:05 [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Chuck Lever
` (2 preceding siblings ...)
2026-09-18 14:05 ` [PATCH RFC 3/5] SUNRPC: pass a keyring serial to the TLS handshake Chuck Lever
@ 2026-09-18 14:05 ` Chuck Lever
2026-09-18 14:05 ` [PATCH RFC 5/5] NFS: add a key type that reveals the namespace .nfs keyring serial Chuck Lever
2026-09-18 17:21 ` [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Benjamin Coddington
5 siblings, 0 replies; 15+ messages in thread
From: Chuck Lever @ 2026-09-18 14:05 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc,
Chuck Lever
Keys on the per-namespace .nfs keyring need not grant user read
permission once tlshd possesses the keyring, but nothing tells tlshd
which keyring to possess. An xprtsec=mtls mount can present only keys
that tlshd's own credentials can read, and one namespace's keys are
not isolated from another's.
Record the serial of the namespace's .nfs keyring in the nfs_client's
xprtsec parameters under the x509 policy, so tlshd possesses that
keyring when it reads the certificate and private key.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
fs/nfs/client.c | 9 ++++++++-
fs/nfs/netns.h | 7 +++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index 60386330aeec..bda1d19db527 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -191,6 +191,13 @@ struct nfs_client *nfs_alloc_client(const struct nfs_client_initdata *cl_init)
clp->cl_principal = "*";
clp->cl_xprtsec = cl_init->xprtsec;
+ /*
+ * Every client in a namespace names the same keyring, so
+ * nfs_match_client() does not compare keyring_serial.
+ */
+ if (clp->cl_xprtsec.policy == RPC_XPRTSEC_TLS_X509)
+ clp->cl_xprtsec.keyring_serial =
+ key_serial(nfs_net_keyring(clp->cl_net));
return clp;
error_cleanup:
@@ -549,7 +556,7 @@ int nfs_create_rpc_client(struct nfs_client *clp,
.version = clp->rpc_ops->version,
.authflavor = flavor,
.cred = cl_init->cred,
- .xprtsec = cl_init->xprtsec,
+ .xprtsec = clp->cl_xprtsec,
.connect_timeout = cl_init->connect_timeout,
.reconnect_timeout = cl_init->reconnect_timeout,
};
diff --git a/fs/nfs/netns.h b/fs/nfs/netns.h
index da0854510404..c8ca7b989f4b 100644
--- a/fs/nfs/netns.h
+++ b/fs/nfs/netns.h
@@ -47,4 +47,11 @@ struct nfs_net {
extern unsigned int nfs_net_id;
+static inline struct key *nfs_net_keyring(struct net *net)
+{
+ struct nfs_net *nn = net_generic(net, nfs_net_id);
+
+ return nn->nfs_keyring;
+}
+
#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH RFC 5/5] NFS: add a key type that reveals the namespace .nfs keyring serial
2026-09-18 14:05 [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Chuck Lever
` (3 preceding siblings ...)
2026-09-18 14:05 ` [PATCH RFC 4/5] NFS: name the namespace .nfs keyring in the x509 handshake Chuck Lever
@ 2026-09-18 14:05 ` Chuck Lever
2026-09-18 18:00 ` Randy Dunlap
2026-09-18 17:21 ` [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Benjamin Coddington
5 siblings, 1 reply; 15+ messages in thread
From: Chuck Lever @ 2026-09-18 14:05 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc,
Chuck Lever
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, 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 of the caller's namespace .nfs keyring. Userspace
reads the serial 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,
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>
---
Documentation/filesystems/nfs/index.rst | 1 +
Documentation/filesystems/nfs/keyring.rst | 67 ++++++++++++++++++++++
fs/nfs/inode.c | 94 ++++++++++++++++++++++++++++++-
3 files changed, 161 insertions(+), 1 deletion(-)
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..5b3d3d0f5f91
--- /dev/null
+++ b/Documentation/filesystems/nfs/keyring.rst
@@ -0,0 +1,67 @@
+.. 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 global root, 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
+==========================
+
+The .nfs keyring is not linked into any keyring that userspace
+possesses, so a serial is the only way to name it. The NFS client
+registers a key type, "nfs_keyring", whose sole purpose is to hand
+that serial out. Its request_key handler runs in the caller's context
+and does not upcall to /sbin/request-key.
+
+To obtain the serial, 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 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 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 of the namespace it is in at the time of the request.
+
+The serial 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 serials as
+cert_serial= and privkey_serial= mount options.
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index bd327fbb12d8..19e644746bab 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -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"
@@ -2665,6 +2671,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 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 nfs_net *nn)
{
@@ -2674,6 +2750,15 @@ static inline int nfs_init_keyring(struct nfs_net *nn)
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)
@@ -2738,10 +2823,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;
@@ -2785,6 +2874,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:
@@ -2799,6 +2890,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();
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH RFC 5/5] NFS: add a key type that reveals the namespace .nfs keyring serial
2026-09-18 14:05 ` [PATCH RFC 5/5] NFS: add a key type that reveals the namespace .nfs keyring serial Chuck Lever
@ 2026-09-18 18:00 ` Randy Dunlap
2026-09-19 15:59 ` Chuck Lever
0 siblings, 1 reply; 15+ messages in thread
From: Randy Dunlap @ 2026-09-18 18:00 UTC (permalink / raw)
To: Chuck Lever, Trond Myklebust, Anna Schumaker, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Jonathan Corbet, Shuah Khan, Christian Brauner, David Howells,
Sagi Grimberg
Cc: linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc
Hi,
On 9/18/26 7:05 AM, Chuck Lever wrote:
> 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, 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 of the caller's namespace .nfs keyring. Userspace
> reads the serial 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,
> 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>
> ---
> Documentation/filesystems/nfs/index.rst | 1 +
> Documentation/filesystems/nfs/keyring.rst | 67 ++++++++++++++++++++++
> fs/nfs/inode.c | 94 ++++++++++++++++++++++++++++++-
> 3 files changed, 161 insertions(+), 1 deletion(-)
>
This might be a locale thing, but using serial as a noun here seems
awkward to me. Is this like "serial number" with the "number" omitted?
Serial as a noun usually means a publication in a series (TV, comics, etc.).
Or is this some special security-related usage of the word serial?
> diff --git a/Documentation/filesystems/nfs/keyring.rst b/Documentation/filesystems/nfs/keyring.rst
> new file mode 100644
> index 000000000000..5b3d3d0f5f91
> --- /dev/null
> +++ b/Documentation/filesystems/nfs/keyring.rst
> @@ -0,0 +1,67 @@
> +.. 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 global root, 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
> +==========================
> +
> +The .nfs keyring is not linked into any keyring that userspace
> +possesses, so a serial is the only way to name it. The NFS client
> +registers a key type, "nfs_keyring", whose sole purpose is to hand
> +that serial out. Its request_key handler runs in the caller's context
> +and does not upcall to /sbin/request-key.
> +
> +To obtain the serial, 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 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 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 of the namespace it is in at the time of the request.
> +
> +The serial 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 serials as
> +cert_serial= and privkey_serial= mount options.
thanks.
--
~Randy
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH RFC 5/5] NFS: add a key type that reveals the namespace .nfs keyring serial
2026-09-18 18:00 ` Randy Dunlap
@ 2026-09-19 15:59 ` Chuck Lever
0 siblings, 0 replies; 15+ messages in thread
From: Chuck Lever @ 2026-09-19 15:59 UTC (permalink / raw)
To: Randy Dunlap
Cc: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Christian Brauner, David Howells, Sagi Grimberg,
linux-nfs, keyrings, kernel-tls-handshake, netdev, linux-doc
On 9/18/26 2:00 PM, Randy Dunlap wrote:
> This might be a locale thing, but using serial as a noun here seems
> awkward to me. Is this like "serial number" with the "number" omitted?
>
> Serial as a noun usually means a publication in a series (TV, comics, etc.).
>
> Or is this some special security-related usage of the word serial?
It's "serial number" with the "number" omitted, borrowed from the
keyring code. The struct member is key->serial, the type is
key_serial_t, and the accessor is key_serial(). The bare form has
crept into prose in a few places (Documentation/bpf/signing.rst,
Documentation/ABI/stable/sysfs-nvme), but the canonical documents,
Documentation/security/keys/core.rst and the keyrings(7) man page,
consistently say "serial number", and keyctl(1) says "key ID".
I agree that it's a little disorienting. I'll update the patch
description and keyring.rst to use "serial number".
The cert_serial= and privkey_serial= mount option names have to
stay as they are, since those are an already established part of
the kernel/user space API.
Thanks for your review!
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace
2026-09-18 14:05 [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Chuck Lever
` (4 preceding siblings ...)
2026-09-18 14:05 ` [PATCH RFC 5/5] NFS: add a key type that reveals the namespace .nfs keyring serial Chuck Lever
@ 2026-09-18 17:21 ` Benjamin Coddington
2026-09-19 15:46 ` Chuck Lever
2026-09-21 8:45 ` Hannes Reinecke
5 siblings, 2 replies; 15+ messages in thread
From: Benjamin Coddington @ 2026-09-18 17:21 UTC (permalink / raw)
To: Chuck Lever
Cc: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg, linux-nfs, keyrings, kernel-tls-handshake, netdev,
linux-doc
On 18 Sep 2026, at 10:05, Chuck Lever wrote:
> An xprtsec=mtls mount names its client certificate and private key
> by keyring serial, and tlshd reads those keys with its own
> credentials. Each key therefore has to grant user read permission,
> and any tlshd on the host that learns a serial can read it. Nothing
> separates one network namespace's credentials from another's.
>
> This series makes the network namespace the isolation domain. The
> RFC thread asked whether the user or mount namespace is the better
> binding. tlshd services the handshake socket of one network
> namespace, so that is the namespace it already lives in.
>
> https://lore.kernel.org/linux-nfs/20260602154740.49861-1-cel@kernel.org/
>
> The keyring is held in struct nfs_net rather than found by name,
> because /proc/keys is not namespace scoped. Its serial travels with
> each handshake instead (patches 3-4), and tlshd's possession of that
> keyring is what lets a provisioned key grant no user read permission
> at all. Both ends of the link exist today, in tls_handshake_accept()
> and in tlshd. The handshake genetlink ABI and the cert_serial= and
> privkey_serial= mount options do not change.
>
> Still open is how userspace names the kernel-held keyring. Patch 5
> prototypes a request_key type handled in the kernel and tagged
> KEY_TYPE_NET_DOMAIN. A keyctl modeled on KEYCTL_GET_PERSISTENT, or a
> read-only attribute on the netns-tagged nfs_client sysfs kobject,
> would do the same job if the keyrings maintainers prefer one. Keys
> that userspace adds are quota-charged by user namespace while the
> keyring lives in nfs_net. Confirmation that this is sane when the
> two boundaries differ would be welcome.
>
> nfstlskey, the provisioning tool that consumes the key type, is
> merged in https://github.com/oracle/ktls-utils/ .
>
> Tested on one Fedora VM acting as both NFS client and NFSD, with
> tlshd from ktls-utils 1.4.0: NFSv4.2 mounts with xprtsec=tls, with
> xprtsec=mtls using the identity in tlshd.conf, and with xprtsec=mtls
> using serials that nfstlskey provisioned.
I think this is the old upcall/namespace problem that's never been generally
solved (as far as I know). Here's a shameless plug to potentially revive
the original "key agent" concept which solves this in a general way.
The idea is - user space processes (tlshd) register themselves as key-agents
that can satisfy request-key. A key agent represents itself as a key-type,
and the appropriate key agent is consulted for request-key if the calling
process has that agent's key in its keyrings.
Otherwise, this solution looks good - but without a general solution to this
problem, other folks building on keyrings will keep trying to solve this
problem.
Ben
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace
2026-09-18 17:21 ` [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Benjamin Coddington
@ 2026-09-19 15:46 ` Chuck Lever
2026-09-21 8:45 ` Hannes Reinecke
1 sibling, 0 replies; 15+ messages in thread
From: Chuck Lever @ 2026-09-19 15:46 UTC (permalink / raw)
To: Benjamin Coddington
Cc: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg, linux-nfs, keyrings, kernel-tls-handshake, netdev,
linux-doc
On Fri, Sep 18, 2026, at 1:21 PM, Benjamin Coddington wrote:
> On 18 Sep 2026, at 10:05, Chuck Lever wrote:
>
>> An xprtsec=mtls mount names its client certificate and private key
>> by keyring serial, and tlshd reads those keys with its own
>> credentials. Each key therefore has to grant user read permission,
>> and any tlshd on the host that learns a serial can read it. Nothing
>> separates one network namespace's credentials from another's.
>>
>> This series makes the network namespace the isolation domain. The
>> RFC thread asked whether the user or mount namespace is the better
>> binding. tlshd services the handshake socket of one network
>> namespace, so that is the namespace it already lives in.
>>
>> https://lore.kernel.org/linux-nfs/20260602154740.49861-1-cel@kernel.org/
>>
>> The keyring is held in struct nfs_net rather than found by name,
>> because /proc/keys is not namespace scoped. Its serial travels with
>> each handshake instead (patches 3-4), and tlshd's possession of that
>> keyring is what lets a provisioned key grant no user read permission
>> at all. Both ends of the link exist today, in tls_handshake_accept()
>> and in tlshd. The handshake genetlink ABI and the cert_serial= and
>> privkey_serial= mount options do not change.
>>
>> Still open is how userspace names the kernel-held keyring. Patch 5
>> prototypes a request_key type handled in the kernel and tagged
>> KEY_TYPE_NET_DOMAIN. A keyctl modeled on KEYCTL_GET_PERSISTENT, or a
>> read-only attribute on the netns-tagged nfs_client sysfs kobject,
>> would do the same job if the keyrings maintainers prefer one. Keys
>> that userspace adds are quota-charged by user namespace while the
>> keyring lives in nfs_net. Confirmation that this is sane when the
>> two boundaries differ would be welcome.
>>
>> nfstlskey, the provisioning tool that consumes the key type, is
>> merged in https://github.com/oracle/ktls-utils/ .
>>
>> Tested on one Fedora VM acting as both NFS client and NFSD, with
>> tlshd from ktls-utils 1.4.0: NFSv4.2 mounts with xprtsec=tls, with
>> xprtsec=mtls using the identity in tlshd.conf, and with xprtsec=mtls
>> using serials that nfstlskey provisioned.
>
> I think this is the old upcall/namespace problem that's never been generally
> solved (as far as I know). Here's a shameless plug to potentially revive
> the original "key agent" concept which solves this in a general way.
>
> The idea is - user space processes (tlshd) register themselves as key-agents
> that can satisfy request-key. A key agent represents itself as a key-type,
> and the appropriate key agent is consulted for request-key if the calling
> process has that agent's key in its keyrings.
>
> Otherwise, this solution looks good - but without a general solution to this
> problem, other folks building on keyrings will keep trying to solve this
> problem.
Thanks for having a look! Your idea sounds a lot like how tlshd uses
netlink.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace
2026-09-18 17:21 ` [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace Benjamin Coddington
2026-09-19 15:46 ` Chuck Lever
@ 2026-09-21 8:45 ` Hannes Reinecke
2026-09-21 11:17 ` Benjamin Coddington
1 sibling, 1 reply; 15+ messages in thread
From: Hannes Reinecke @ 2026-09-21 8:45 UTC (permalink / raw)
To: Benjamin Coddington, Chuck Lever
Cc: Trond Myklebust, Anna Schumaker, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Jonathan Corbet,
Shuah Khan, Randy Dunlap, Christian Brauner, David Howells,
Sagi Grimberg, linux-nfs, keyrings, kernel-tls-handshake, netdev,
linux-doc
On 9/18/26 7:21 PM, Benjamin Coddington wrote:
> On 18 Sep 2026, at 10:05, Chuck Lever wrote:
>
>> An xprtsec=mtls mount names its client certificate and private key
>> by keyring serial, and tlshd reads those keys with its own
>> credentials. Each key therefore has to grant user read permission,
>> and any tlshd on the host that learns a serial can read it. Nothing
>> separates one network namespace's credentials from another's.
>>
>> This series makes the network namespace the isolation domain. The
>> RFC thread asked whether the user or mount namespace is the better
>> binding. tlshd services the handshake socket of one network
>> namespace, so that is the namespace it already lives in.
>>
>> https://lore.kernel.org/linux-nfs/20260602154740.49861-1-cel@kernel.org/
>>
>> The keyring is held in struct nfs_net rather than found by name,
>> because /proc/keys is not namespace scoped. Its serial travels with
>> each handshake instead (patches 3-4), and tlshd's possession of that
>> keyring is what lets a provisioned key grant no user read permission
>> at all. Both ends of the link exist today, in tls_handshake_accept()
>> and in tlshd. The handshake genetlink ABI and the cert_serial= and
>> privkey_serial= mount options do not change.
>>
>> Still open is how userspace names the kernel-held keyring. Patch 5
>> prototypes a request_key type handled in the kernel and tagged
>> KEY_TYPE_NET_DOMAIN. A keyctl modeled on KEYCTL_GET_PERSISTENT, or a
>> read-only attribute on the netns-tagged nfs_client sysfs kobject,
>> would do the same job if the keyrings maintainers prefer one. Keys
>> that userspace adds are quota-charged by user namespace while the
>> keyring lives in nfs_net. Confirmation that this is sane when the
>> two boundaries differ would be welcome.
>>
>> nfstlskey, the provisioning tool that consumes the key type, is
>> merged in https://github.com/oracle/ktls-utils/ .
>>
>> Tested on one Fedora VM acting as both NFS client and NFSD, with
>> tlshd from ktls-utils 1.4.0: NFSv4.2 mounts with xprtsec=tls, with
>> xprtsec=mtls using the identity in tlshd.conf, and with xprtsec=mtls
>> using serials that nfstlskey provisioned.
>
> I think this is the old upcall/namespace problem that's never been generally
> solved (as far as I know). Here's a shameless plug to potentially revive
> the original "key agent" concept which solves this in a general way.
>
> The idea is - user space processes (tlshd) register themselves as key-agents
> that can satisfy request-key. A key agent represents itself as a key-type,
> and the appropriate key agent is consulted for request-key if the calling
> process has that agent's key in its keyrings.
>
> Otherwise, this solution looks good - but without a general solution to this
> problem, other folks building on keyrings will keep trying to solve this
> problem.
>
Not sure. Basic problem with the 'upcall' mechanism is that it needs to
execute a userspace program by the time of the upcall.
And that (trivially) requires
a) a usable userspace to be present
and
b) the correct program to be be available
And that needs to happen at a time when the connection is _known_ to be
non-functional (otherwise you would not need to do an upcall).
So there's a likelyhood that this connection serves to userspace we are
about to access, and the whole thing stalls.
With tlshd we have the benefit that the daemon is always running, so
we don't have this issue.
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH RFC 0/5] NFS: isolate mTLS client credentials by network namespace
2026-09-21 8:45 ` Hannes Reinecke
@ 2026-09-21 11:17 ` Benjamin Coddington
0 siblings, 0 replies; 15+ messages in thread
From: Benjamin Coddington @ 2026-09-21 11:17 UTC (permalink / raw)
To: Hannes Reinecke
Cc: Benjamin Coddington, Chuck Lever, Trond Myklebust, Anna Schumaker,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Simon Horman, Jonathan Corbet, Shuah Khan, Randy Dunlap,
Christian Brauner, David Howells, Sagi Grimberg, linux-nfs,
keyrings, kernel-tls-handshake, netdev, linux-doc
On 21 Sep 2026, at 4:45, Hannes Reinecke wrote:
> On 9/18/26 7:21 PM, Benjamin Coddington wrote:
>> On 18 Sep 2026, at 10:05, Chuck Lever wrote:
>>
>>> An xprtsec=mtls mount names its client certificate and private key
>>> by keyring serial, and tlshd reads those keys with its own
>>> credentials. Each key therefore has to grant user read permission,
>>> and any tlshd on the host that learns a serial can read it. Nothing
>>> separates one network namespace's credentials from another's.
>>>
>>> This series makes the network namespace the isolation domain. The
>>> RFC thread asked whether the user or mount namespace is the better
>>> binding. tlshd services the handshake socket of one network
>>> namespace, so that is the namespace it already lives in.
>>>
>>> https://lore.kernel.org/linux-nfs/20260602154740.49861-1-cel@kernel.org/
>>>
>>> The keyring is held in struct nfs_net rather than found by name,
>>> because /proc/keys is not namespace scoped. Its serial travels with
>>> each handshake instead (patches 3-4), and tlshd's possession of that
>>> keyring is what lets a provisioned key grant no user read permission
>>> at all. Both ends of the link exist today, in tls_handshake_accept()
>>> and in tlshd. The handshake genetlink ABI and the cert_serial= and
>>> privkey_serial= mount options do not change.
>>>
>>> Still open is how userspace names the kernel-held keyring. Patch 5
>>> prototypes a request_key type handled in the kernel and tagged
>>> KEY_TYPE_NET_DOMAIN. A keyctl modeled on KEYCTL_GET_PERSISTENT, or a
>>> read-only attribute on the netns-tagged nfs_client sysfs kobject,
>>> would do the same job if the keyrings maintainers prefer one. Keys
>>> that userspace adds are quota-charged by user namespace while the
>>> keyring lives in nfs_net. Confirmation that this is sane when the
>>> two boundaries differ would be welcome.
>>>
>>> nfstlskey, the provisioning tool that consumes the key type, is
>>> merged in https://github.com/oracle/ktls-utils/ .
>>>
>>> Tested on one Fedora VM acting as both NFS client and NFSD, with
>>> tlshd from ktls-utils 1.4.0: NFSv4.2 mounts with xprtsec=tls, with
>>> xprtsec=mtls using the identity in tlshd.conf, and with xprtsec=mtls
>>> using serials that nfstlskey provisioned.
>>
>> I think this is the old upcall/namespace problem that's never been generally
>> solved (as far as I know). Here's a shameless plug to potentially revive
>> the original "key agent" concept which solves this in a general way.
>>
>> The idea is - user space processes (tlshd) register themselves as key-agents
>> that can satisfy request-key. A key agent represents itself as a key-type,
>> and the appropriate key agent is consulted for request-key if the calling
>> process has that agent's key in its keyrings.
>>
>> Otherwise, this solution looks good - but without a general solution to this
>> problem, other folks building on keyrings will keep trying to solve this
>> problem.
>>
> Not sure. Basic problem with the 'upcall' mechanism is that it needs to
> execute a userspace program by the time of the upcall.
> And that (trivially) requires
> a) a usable userspace to be present
> and
> b) the correct program to be be available
Yes..
The key agent worked similarly to tlshd - it wasn't the old request-key
upcall mech. In fact, tlshd and its netlink interface could easily be
adapted into this key-agent-as-a-key.
Its a trade-off that allows you to route the upcall via how keys are
linked in the calling process, rather than by some defined-in-code domain
like "same network namespace".
Ben
^ permalink raw reply [flat|nested] 15+ messages in thread