[RFC][PATCH] vfs, afs: Pass the file from fstat()/statx() to the fs for auth purposes
From: David Howells
Date: Fri Sep 10 2021 - 06:54:51 EST
read(), write() and ftruncate() all have the file available from which they
can extract the information needed to perform authenticated operations to a
network filesystem server for that filesystem, but fstat() and statx() do
not.
This could lead to the situation where a read(), say, on a file descriptor
will work, but fstat() will fail because the calling process doesn't
intrinsically have the right to do that.
Change this by passing the file, if we have it, in struct kstat from which
the filesystem can pick it up and use it, similar to the way ftruncate()
passes the information in struct iattr.
Make use of this in the afs filesystem to pass to validation in case we
need to refetch the inode attributes from the server.
Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
cc: Alexander Viro <viro@xxxxxxxxxxxxxxxxxx>
cc: linux-fsdevel@xxxxxxxxxxxxxxx
cc: linux-afs@xxxxxxxxxxxxxxxxxxx
---
fs/afs/inode.c | 10 ++++++++-
fs/exportfs/expfs.c | 2 -
fs/stat.c | 56 ++++++++++++++++++++++++++++++++++++++-------------
include/linux/fs.h | 3 +-
include/linux/stat.h | 1
5 files changed, 55 insertions(+), 17 deletions(-)
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 8fcffea2daf5..7d732a38c739 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -670,6 +670,9 @@ int afs_validate(struct afs_vnode *vnode, struct key *key)
vnode->fid.vid, vnode->fid.vnode, vnode->flags,
key_serial(key));
+ if (!vnode->volume)
+ goto valid; /* Dynroot */
+
if (unlikely(test_bit(AFS_VNODE_DELETED, &vnode->flags))) {
if (vnode->vfs_inode.i_nlink)
clear_nlink(&vnode->vfs_inode);
@@ -728,10 +731,15 @@ int afs_getattr(struct user_namespace *mnt_userns, const struct path *path,
{
struct inode *inode = d_inode(path->dentry);
struct afs_vnode *vnode = AFS_FS_I(inode);
- int seq = 0;
+ struct afs_file *af = stat->file ? stat->file->private_data : NULL;
+ int ret, seq = 0;
_enter("{ ino=%lu v=%u }", inode->i_ino, inode->i_generation);
+ ret = afs_validate(vnode, af ? af->key : NULL);
+ if (ret < 0)
+ return ret;
+
do {
read_seqbegin_or_lock(&vnode->cb_lock, &seq);
generic_fillattr(&init_user_ns, inode, stat);
diff --git a/fs/exportfs/expfs.c b/fs/exportfs/expfs.c
index 0106eba46d5a..d3fba1aea432 100644
--- a/fs/exportfs/expfs.c
+++ b/fs/exportfs/expfs.c
@@ -303,7 +303,7 @@ static int get_name(const struct path *path, char *name, struct dentry *child)
* actually call ->getattr, not just read i_ino:
*/
error = vfs_getattr_nosec(&child_path, &stat,
- STATX_INO, AT_STATX_SYNC_AS_STAT);
+ STATX_INO, AT_STATX_SYNC_AS_STAT, NULL);
if (error)
return error;
buffer.ino = stat.ino;
diff --git a/fs/stat.c b/fs/stat.c
index 1fa38bdec1a6..c3410e809b4d 100644
--- a/fs/stat.c
+++ b/fs/stat.c
@@ -65,6 +65,7 @@ EXPORT_SYMBOL(generic_fillattr);
* @stat: structure to return attributes in
* @request_mask: STATX_xxx flags indicating what the caller wants
* @query_flags: Query mode (AT_STATX_SYNC_TYPE)
+ * @file: File with credential info or NULL
*
* Get attributes without calling security_inode_getattr.
*
@@ -73,12 +74,14 @@ EXPORT_SYMBOL(generic_fillattr);
* attributes to any user. Any other code probably wants vfs_getattr.
*/
int vfs_getattr_nosec(const struct path *path, struct kstat *stat,
- u32 request_mask, unsigned int query_flags)
+ u32 request_mask, unsigned int query_flags,
+ struct file *file)
{
struct user_namespace *mnt_userns;
struct inode *inode = d_backing_inode(path->dentry);
memset(stat, 0, sizeof(*stat));
+ stat->file = file;
stat->result_mask |= STATX_BASIC_STATS;
query_flags &= AT_STATX_SYNC_TYPE;
@@ -139,7 +142,7 @@ int vfs_getattr(const struct path *path, struct kstat *stat,
retval = security_inode_getattr(path);
if (retval)
return retval;
- return vfs_getattr_nosec(path, stat, request_mask, query_flags);
+ return vfs_getattr_nosec(path, stat, request_mask, query_flags, NULL);
}
EXPORT_SYMBOL(vfs_getattr);
@@ -161,7 +164,11 @@ int vfs_fstat(int fd, struct kstat *stat)
f = fdget_raw(fd);
if (!f.file)
return -EBADF;
- error = vfs_getattr(&f.file->f_path, stat, STATX_BASIC_STATS, 0);
+
+ error = security_inode_getattr(&f.file->f_path);
+ if (!error)
+ error = vfs_getattr_nosec(&f.file->f_path, stat,
+ STATX_BASIC_STATS, 0, f.file);
fdput(f);
return error;
}
@@ -185,7 +192,9 @@ static int vfs_statx(int dfd, const char __user *filename, int flags,
struct kstat *stat, u32 request_mask)
{
struct path path;
+ struct fd f;
unsigned lookup_flags = 0;
+ bool put_fd = false;
int error;
if (flags & ~(AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT | AT_EMPTY_PATH |
@@ -200,17 +209,36 @@ static int vfs_statx(int dfd, const char __user *filename, int flags,
lookup_flags |= LOOKUP_EMPTY;
retry:
- error = user_path_at(dfd, filename, lookup_flags, &path);
- if (error)
- goto out;
-
- error = vfs_getattr(&path, stat, request_mask, flags);
- stat->mnt_id = real_mount(path.mnt)->mnt_id;
- stat->result_mask |= STATX_MNT_ID;
- if (path.mnt->mnt_root == path.dentry)
- stat->attributes |= STATX_ATTR_MOUNT_ROOT;
- stat->attributes_mask |= STATX_ATTR_MOUNT_ROOT;
- path_put(&path);
+ if ((lookup_flags & LOOKUP_EMPTY) &&
+ dfd >= 0 &&
+ filename &&
+ strnlen_user(filename, 2) == 0) {
+ /* Should we use ESTALE retry for direct-fd? */
+ f = fdget_raw(dfd);
+ if (!f.file)
+ return -EBADF;
+ path = f.file->f_path;
+ put_fd = true;
+ } else {
+ f.file = NULL;
+ error = user_path_at(dfd, filename, lookup_flags, &path);
+ if (error)
+ goto out;
+ }
+
+ error = security_inode_getattr(&path);
+ if (!error) {
+ error = vfs_getattr_nosec(&path, stat, request_mask, flags, f.file);
+ stat->mnt_id = real_mount(path.mnt)->mnt_id;
+ stat->result_mask |= STATX_MNT_ID;
+ if (path.mnt->mnt_root == path.dentry)
+ stat->attributes |= STATX_ATTR_MOUNT_ROOT;
+ stat->attributes_mask |= STATX_ATTR_MOUNT_ROOT;
+ }
+ if (put_fd)
+ fdput(f);
+ else
+ path_put(&path);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
goto retry;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index c58c2611a195..3f31f739f9a6 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -3312,7 +3312,8 @@ extern int page_symlink(struct inode *inode, const char *symname, int len);
extern const struct inode_operations page_symlink_inode_operations;
extern void kfree_link(void *);
void generic_fillattr(struct user_namespace *, struct inode *, struct kstat *);
-extern int vfs_getattr_nosec(const struct path *, struct kstat *, u32, unsigned int);
+extern int vfs_getattr_nosec(const struct path *, struct kstat *, u32, unsigned int,
+ struct file *);
extern int vfs_getattr(const struct path *, struct kstat *, u32, unsigned int);
void __inode_add_bytes(struct inode *inode, loff_t bytes);
void inode_add_bytes(struct inode *inode, loff_t bytes);
diff --git a/include/linux/stat.h b/include/linux/stat.h
index fff27e603814..b9986688cc59 100644
--- a/include/linux/stat.h
+++ b/include/linux/stat.h
@@ -20,6 +20,7 @@
#include <linux/uidgid.h>
struct kstat {
+ struct file *file; /* File if called from fstat() equivalent or NULL */
u32 result_mask; /* What fields the user got */
umode_t mode;
unsigned int nlink;