diff mbox series

[RFC,1/2] fs: name_to_handle_at() support for connectable file handles

Message ID 20240919140611.1771651-2-amir73il@gmail.com (mailing list archive)
State New
Headers show
Series API for exporting connectable file handles to userspace | expand

Commit Message

Amir Goldstein Sept. 19, 2024, 2:06 p.m. UTC
nfsd encodes "connectable" file handles for the subtree_check feature.
So far, userspace nfs server could not make use of this functionality.

Introduce a new flag AT_HANDLE_CONNECTABLE to name_to_handle_at(2).
When used, the encoded file handle is "connectable".

Note that decoding a "connectable" file handle with open_by_handle_at(2)
is not guarandteed to return a "connected" fd (i.e. fd with known path).
A new opt-in API would be needed to guarantee a "connected" fd.

Signed-off-by: Amir Goldstein <amir73il@gmail.com>
---
 fs/fhandle.c               | 24 ++++++++++++++++++++----
 include/uapi/linux/fcntl.h |  1 +
 2 files changed, 21 insertions(+), 4 deletions(-)

Comments

Jeff Layton Sept. 20, 2024, 7:13 a.m. UTC | #1
On Thu, 2024-09-19 at 16:06 +0200, Amir Goldstein wrote:
> nfsd encodes "connectable" file handles for the subtree_check feature.
> So far, userspace nfs server could not make use of this functionality.
> 
> Introduce a new flag AT_HANDLE_CONNECTABLE to name_to_handle_at(2).
> When used, the encoded file handle is "connectable".
> 
> Note that decoding a "connectable" file handle with open_by_handle_at(2)
> is not guarandteed to return a "connected" fd (i.e. fd with known path).
> A new opt-in API would be needed to guarantee a "connected" fd.
> 
> Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> ---
>  fs/fhandle.c               | 24 ++++++++++++++++++++----
>  include/uapi/linux/fcntl.h |  1 +
>  2 files changed, 21 insertions(+), 4 deletions(-)
> 
> diff --git a/fs/fhandle.c b/fs/fhandle.c
> index 8cb665629f4a..956d9b25d4f7 100644
> --- a/fs/fhandle.c
> +++ b/fs/fhandle.c
> @@ -31,6 +31,11 @@ static long do_sys_name_to_handle(const struct path *path,
>  	if (!exportfs_can_encode_fh(path->dentry->d_sb->s_export_op, fh_flags))
>  		return -EOPNOTSUPP;
>  
> +	/* Do not encode a connectable handle for a disconnected dentry */
> +	if (fh_flags & EXPORT_FH_CONNECTABLE &&
> +	    path->dentry->d_flags & DCACHE_DISCONNECTED)
> +		return -EACCES;
> +

I'm not sure about EACCES here. That implies that if you had the right
creds then this would work. DCACHE_DISCONNECTED has nothing to do with
permissions though. Maybe -EINVAL instead since getting a disconnected
dentry here would imply that @path is somehow bogus?

Given how this function is used, will we ever see a disconnected dentry
here? The path comes from userland in this case, so I don't think it
can ever be disconnected. Maybe a WARN_ON_ONCE or pr_warn would be
appropriate in this case too?

>  	if (copy_from_user(&f_handle, ufh, sizeof(struct file_handle)))
>  		return -EFAULT;
>  
> @@ -45,7 +50,7 @@ static long do_sys_name_to_handle(const struct path *path,
>  	/* convert handle size to multiple of sizeof(u32) */
>  	handle_dwords = f_handle.handle_bytes >> 2;
>  
> -	/* we ask for a non connectable maybe decodeable file handle */
> +	/* Encode a possibly decodeable/connectable file handle */
>  	retval = exportfs_encode_fh(path->dentry,
>  				    (struct fid *)handle->f_handle,
>  				    &handle_dwords, fh_flags);
> @@ -109,15 +114,26 @@ SYSCALL_DEFINE5(name_to_handle_at, int, dfd, const char __user *, name,
>  {
>  	struct path path;
>  	int lookup_flags;
> -	int fh_flags;
> +	int fh_flags = 0;
>  	int err;
>  
>  	if (flag & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH | AT_HANDLE_FID |
> -		     AT_HANDLE_MNT_ID_UNIQUE))
> +		     AT_HANDLE_MNT_ID_UNIQUE | AT_HANDLE_CONNECTABLE))
> +		return -EINVAL;
> +
> +	/*
> +	 * AT_HANDLE_FID means there is no intention to decode file handle
> +	 * AT_HANDLE_CONNECTABLE means there is an intention to decode a
> +	 * connected fd (with known path), so these flags are conflicting.
> +	 */
> +	if (flag & AT_HANDLE_CONNECTABLE && flag & AT_HANDLE_FID)
>  		return -EINVAL;
> +	else if (flag & AT_HANDLE_FID)
> +		fh_flags |= EXPORT_FH_FID;
> +	else if (flag & AT_HANDLE_CONNECTABLE)
> +		fh_flags |= EXPORT_FH_CONNECTABLE;
>  
>  	lookup_flags = (flag & AT_SYMLINK_FOLLOW) ? LOOKUP_FOLLOW : 0;
> -	fh_flags = (flag & AT_HANDLE_FID) ? EXPORT_FH_FID : 0;
>  	if (flag & AT_EMPTY_PATH)
>  		lookup_flags |= LOOKUP_EMPTY;
>  	err = user_path_at(dfd, name, lookup_flags, &path);
> diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> index 87e2dec79fea..56ff2100e021 100644
> --- a/include/uapi/linux/fcntl.h
> +++ b/include/uapi/linux/fcntl.h
> @@ -153,6 +153,7 @@
>  					   object identity and may not be
>  					   usable with open_by_handle_at(2). */
>  #define AT_HANDLE_MNT_ID_UNIQUE	0x001	/* Return the u64 unique mount ID. */
> +#define AT_HANDLE_CONNECTABLE	0x002	/* Request a connectable file handle */
>  
>  #if defined(__KERNEL__)
>  #define AT_GETATTR_NOSEC	0x80000000
Jeff Layton Sept. 20, 2024, 7:36 a.m. UTC | #2
On Fri, 2024-09-20 at 09:13 +0200, Jeff Layton wrote:
> On Thu, 2024-09-19 at 16:06 +0200, Amir Goldstein wrote:
> > nfsd encodes "connectable" file handles for the subtree_check feature.
> > So far, userspace nfs server could not make use of this functionality.
> > 
> > Introduce a new flag AT_HANDLE_CONNECTABLE to name_to_handle_at(2).
> > When used, the encoded file handle is "connectable".
> > 
> > Note that decoding a "connectable" file handle with open_by_handle_at(2)
> > is not guarandteed to return a "connected" fd (i.e. fd with known path).
> > A new opt-in API would be needed to guarantee a "connected" fd.
> > 
> > Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> > ---
> >  fs/fhandle.c               | 24 ++++++++++++++++++++----
> >  include/uapi/linux/fcntl.h |  1 +
> >  2 files changed, 21 insertions(+), 4 deletions(-)
> > 
> > diff --git a/fs/fhandle.c b/fs/fhandle.c
> > index 8cb665629f4a..956d9b25d4f7 100644
> > --- a/fs/fhandle.c
> > +++ b/fs/fhandle.c
> > @@ -31,6 +31,11 @@ static long do_sys_name_to_handle(const struct path *path,
> >  	if (!exportfs_can_encode_fh(path->dentry->d_sb->s_export_op, fh_flags))
> >  		return -EOPNOTSUPP;
> >  
> > +	/* Do not encode a connectable handle for a disconnected dentry */
> > +	if (fh_flags & EXPORT_FH_CONNECTABLE &&
> > +	    path->dentry->d_flags & DCACHE_DISCONNECTED)
> > +		return -EACCES;
> > +
> 
> I'm not sure about EACCES here. That implies that if you had the right
> creds then this would work. DCACHE_DISCONNECTED has nothing to do with
> permissions though. Maybe -EINVAL instead since getting a disconnected
> dentry here would imply that @path is somehow bogus?
> 
> Given how this function is used, will we ever see a disconnected dentry
> here? The path comes from userland in this case, so I don't think it
> can ever be disconnected. Maybe a WARN_ON_ONCE or pr_warn would be
> appropriate in this case too?
> 

Oh, I guess you can get a disconnected dentry here.

You could call open_by_handle_at() for a directory fh, and then pass
that into name_to_handle_at().

Either way, this API scares me since it seems like it can just randomly
fail, depending on the state of the dcache. That's the worst-case
scenario for an API.

If you want to go through with this, you'll need to carefully document
what's required to make this work properly, as this has some
significant footguns.

 
> >  	if (copy_from_user(&f_handle, ufh, sizeof(struct file_handle)))
> >  		return -EFAULT;
> >  
> > @@ -45,7 +50,7 @@ static long do_sys_name_to_handle(const struct path *path,
> >  	/* convert handle size to multiple of sizeof(u32) */
> >  	handle_dwords = f_handle.handle_bytes >> 2;
> >  
> > -	/* we ask for a non connectable maybe decodeable file handle */
> > +	/* Encode a possibly decodeable/connectable file handle */
> >  	retval = exportfs_encode_fh(path->dentry,
> >  				    (struct fid *)handle->f_handle,
> >  				    &handle_dwords, fh_flags);
> > @@ -109,15 +114,26 @@ SYSCALL_DEFINE5(name_to_handle_at, int, dfd, const char __user *, name,
> >  {
> >  	struct path path;
> >  	int lookup_flags;
> > -	int fh_flags;
> > +	int fh_flags = 0;
> >  	int err;
> >  
> >  	if (flag & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH | AT_HANDLE_FID |
> > -		     AT_HANDLE_MNT_ID_UNIQUE))
> > +		     AT_HANDLE_MNT_ID_UNIQUE | AT_HANDLE_CONNECTABLE))
> > +		return -EINVAL;
> > +
> > +	/*
> > +	 * AT_HANDLE_FID means there is no intention to decode file handle
> > +	 * AT_HANDLE_CONNECTABLE means there is an intention to decode a
> > +	 * connected fd (with known path), so these flags are conflicting.
> > +	 */
> > +	if (flag & AT_HANDLE_CONNECTABLE && flag & AT_HANDLE_FID)
> >  		return -EINVAL;
> > +	else if (flag & AT_HANDLE_FID)
> > +		fh_flags |= EXPORT_FH_FID;
> > +	else if (flag & AT_HANDLE_CONNECTABLE)
> > +		fh_flags |= EXPORT_FH_CONNECTABLE;
> >  
> >  	lookup_flags = (flag & AT_SYMLINK_FOLLOW) ? LOOKUP_FOLLOW : 0;
> > -	fh_flags = (flag & AT_HANDLE_FID) ? EXPORT_FH_FID : 0;
> >  	if (flag & AT_EMPTY_PATH)
> >  		lookup_flags |= LOOKUP_EMPTY;
> >  	err = user_path_at(dfd, name, lookup_flags, &path);
> > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > index 87e2dec79fea..56ff2100e021 100644
> > --- a/include/uapi/linux/fcntl.h
> > +++ b/include/uapi/linux/fcntl.h
> > @@ -153,6 +153,7 @@
> >  					   object identity and may not be
> >  					   usable with open_by_handle_at(2). */
> >  #define AT_HANDLE_MNT_ID_UNIQUE	0x001	/* Return the u64 unique mount ID. */
> > +#define AT_HANDLE_CONNECTABLE	0x002	/* Request a connectable file handle */
> >  
> >  #if defined(__KERNEL__)
> >  #define AT_GETATTR_NOSEC	0x80000000
> 
> -- 
> Jeff Layton <jlayton@kernel.org>
>
Amir Goldstein Sept. 20, 2024, 8:40 a.m. UTC | #3
On Fri, Sep 20, 2024 at 9:36 AM Jeff Layton <jlayton@kernel.org> wrote:
>
> On Fri, 2024-09-20 at 09:13 +0200, Jeff Layton wrote:
> > On Thu, 2024-09-19 at 16:06 +0200, Amir Goldstein wrote:
> > > nfsd encodes "connectable" file handles for the subtree_check feature.
> > > So far, userspace nfs server could not make use of this functionality.
> > >
> > > Introduce a new flag AT_HANDLE_CONNECTABLE to name_to_handle_at(2).
> > > When used, the encoded file handle is "connectable".
> > >
> > > Note that decoding a "connectable" file handle with open_by_handle_at(2)
> > > is not guarandteed to return a "connected" fd (i.e. fd with known path).
> > > A new opt-in API would be needed to guarantee a "connected" fd.
> > >
> > > Signed-off-by: Amir Goldstein <amir73il@gmail.com>
> > > ---
> > >  fs/fhandle.c               | 24 ++++++++++++++++++++----
> > >  include/uapi/linux/fcntl.h |  1 +
> > >  2 files changed, 21 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/fs/fhandle.c b/fs/fhandle.c
> > > index 8cb665629f4a..956d9b25d4f7 100644
> > > --- a/fs/fhandle.c
> > > +++ b/fs/fhandle.c
> > > @@ -31,6 +31,11 @@ static long do_sys_name_to_handle(const struct path *path,
> > >     if (!exportfs_can_encode_fh(path->dentry->d_sb->s_export_op, fh_flags))
> > >             return -EOPNOTSUPP;
> > >
> > > +   /* Do not encode a connectable handle for a disconnected dentry */
> > > +   if (fh_flags & EXPORT_FH_CONNECTABLE &&
> > > +       path->dentry->d_flags & DCACHE_DISCONNECTED)
> > > +           return -EACCES;
> > > +
> >
> > I'm not sure about EACCES here. That implies that if you had the right
> > creds then this would work. DCACHE_DISCONNECTED has nothing to do with
> > permissions though. Maybe -EINVAL instead since getting a disconnected
> > dentry here would imply that @path is somehow bogus?
> >
> > Given how this function is used, will we ever see a disconnected dentry
> > here? The path comes from userland in this case, so I don't think it
> > can ever be disconnected. Maybe a WARN_ON_ONCE or pr_warn would be
> > appropriate in this case too?
> >

Yes, I agree (with some additions below...)

>
> Oh, I guess you can get a disconnected dentry here.
>
> You could call open_by_handle_at() for a directory fh, and then pass
> that into name_to_handle_at().

Aha, but a disconnected directory dentry is fine, because it is
still "connectable", so we should not fail on it.

>
> Either way, this API scares me since it seems like it can just randomly
> fail, depending on the state of the dcache. That's the worst-case
> scenario for an API.
>
> If you want to go through with this, you'll need to carefully document
> what's required to make this work properly, as this has some
> significant footguns.
>

Agreed.

The correct statement is that we should not get a disconnected
non-dir dentry, as long as we do not allow AT_EMPTY_PATH,
so if we return EINVAL for the flag combination
AT_EMPTY_PATH | AT_HANDLE_CONNECTABLE
we should be back to a deterministic API.

As you wrote in the first email, we should never expect to
resolve a path to a dentry that is not "connectable". Right?

Thanks,
Amir.
diff mbox series

Patch

diff --git a/fs/fhandle.c b/fs/fhandle.c
index 8cb665629f4a..956d9b25d4f7 100644
--- a/fs/fhandle.c
+++ b/fs/fhandle.c
@@ -31,6 +31,11 @@  static long do_sys_name_to_handle(const struct path *path,
 	if (!exportfs_can_encode_fh(path->dentry->d_sb->s_export_op, fh_flags))
 		return -EOPNOTSUPP;
 
+	/* Do not encode a connectable handle for a disconnected dentry */
+	if (fh_flags & EXPORT_FH_CONNECTABLE &&
+	    path->dentry->d_flags & DCACHE_DISCONNECTED)
+		return -EACCES;
+
 	if (copy_from_user(&f_handle, ufh, sizeof(struct file_handle)))
 		return -EFAULT;
 
@@ -45,7 +50,7 @@  static long do_sys_name_to_handle(const struct path *path,
 	/* convert handle size to multiple of sizeof(u32) */
 	handle_dwords = f_handle.handle_bytes >> 2;
 
-	/* we ask for a non connectable maybe decodeable file handle */
+	/* Encode a possibly decodeable/connectable file handle */
 	retval = exportfs_encode_fh(path->dentry,
 				    (struct fid *)handle->f_handle,
 				    &handle_dwords, fh_flags);
@@ -109,15 +114,26 @@  SYSCALL_DEFINE5(name_to_handle_at, int, dfd, const char __user *, name,
 {
 	struct path path;
 	int lookup_flags;
-	int fh_flags;
+	int fh_flags = 0;
 	int err;
 
 	if (flag & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH | AT_HANDLE_FID |
-		     AT_HANDLE_MNT_ID_UNIQUE))
+		     AT_HANDLE_MNT_ID_UNIQUE | AT_HANDLE_CONNECTABLE))
+		return -EINVAL;
+
+	/*
+	 * AT_HANDLE_FID means there is no intention to decode file handle
+	 * AT_HANDLE_CONNECTABLE means there is an intention to decode a
+	 * connected fd (with known path), so these flags are conflicting.
+	 */
+	if (flag & AT_HANDLE_CONNECTABLE && flag & AT_HANDLE_FID)
 		return -EINVAL;
+	else if (flag & AT_HANDLE_FID)
+		fh_flags |= EXPORT_FH_FID;
+	else if (flag & AT_HANDLE_CONNECTABLE)
+		fh_flags |= EXPORT_FH_CONNECTABLE;
 
 	lookup_flags = (flag & AT_SYMLINK_FOLLOW) ? LOOKUP_FOLLOW : 0;
-	fh_flags = (flag & AT_HANDLE_FID) ? EXPORT_FH_FID : 0;
 	if (flag & AT_EMPTY_PATH)
 		lookup_flags |= LOOKUP_EMPTY;
 	err = user_path_at(dfd, name, lookup_flags, &path);
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index 87e2dec79fea..56ff2100e021 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -153,6 +153,7 @@ 
 					   object identity and may not be
 					   usable with open_by_handle_at(2). */
 #define AT_HANDLE_MNT_ID_UNIQUE	0x001	/* Return the u64 unique mount ID. */
+#define AT_HANDLE_CONNECTABLE	0x002	/* Request a connectable file handle */
 
 #if defined(__KERNEL__)
 #define AT_GETATTR_NOSEC	0x80000000