114 lines
4.0 KiB
Diff
114 lines
4.0 KiB
Diff
From 13ccad7713b89e7693feb5346e7893dc8edce7a8 Mon Sep 17 00:00:00 2001
|
|
From: Christian Brauner <brauner@kernel.org>
|
|
Date: Mon, 7 Apr 2025 11:54:15 +0200
|
|
Subject: anon_inode: use a proper mode internally
|
|
|
|
This allows the VFS to not trip over anonymous inodes and we can add
|
|
asserts based on the mode into the vfs. When we report it to userspace
|
|
we can simply hide the mode to avoid regressions. I've audited all
|
|
direct callers of alloc_anon_inode() and only secretmen overrides i_mode
|
|
and i_op inode operations but it already uses a regular file.
|
|
|
|
Link: https://lore.kernel.org/20250407-work-anon_inode-v1-1-53a44c20d44e@kernel.org
|
|
Fixes: af153bb63a336 ("vfs: catch invalid modes in may_open()")
|
|
Reviewed-by: Jeff Layton <jlayton@kernel.org>
|
|
Cc: stable@vger.kernel.org # all LTS kernels
|
|
Reported-by: syzbot+5d8e79d323a13aa0b248@syzkaller.appspotmail.com
|
|
Closes: https://lore.kernel.org/all/67ed3fb3.050a0220.14623d.0009.GAE@google.com
|
|
Signed-off-by: Christian Brauner <brauner@kernel.org>
|
|
---
|
|
fs/anon_inodes.c | 36 ++++++++++++++++++++++++++++++++++++
|
|
fs/internal.h | 3 +++
|
|
fs/libfs.c | 8 +++++++-
|
|
3 files changed, 46 insertions(+), 1 deletion(-)
|
|
|
|
--- a/fs/anon_inodes.c
|
|
+++ b/fs/anon_inodes.c
|
|
@@ -24,10 +24,44 @@
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
+#include "internal.h"
|
|
+
|
|
static struct vfsmount *anon_inode_mnt __ro_after_init;
|
|
static struct inode *anon_inode_inode __ro_after_init;
|
|
|
|
/*
|
|
+ * User space expects anonymous inodes to have no file type in st_mode.
|
|
+ *
|
|
+ * In particular, 'lsof' has this legacy logic:
|
|
+ *
|
|
+ * type = s->st_mode & S_IFMT;
|
|
+ * switch (type) {
|
|
+ * ...
|
|
+ * case 0:
|
|
+ * if (!strcmp(p, "anon_inode"))
|
|
+ * Lf->ntype = Ntype = N_ANON_INODE;
|
|
+ *
|
|
+ * to detect our old anon_inode logic.
|
|
+ *
|
|
+ * Rather than mess with our internal sane inode data, just fix it
|
|
+ * up here in getattr() by masking off the format bits.
|
|
+ */
|
|
+int anon_inode_getattr(struct mnt_idmap *idmap, const struct path *path,
|
|
+ struct kstat *stat, u32 request_mask,
|
|
+ unsigned int query_flags)
|
|
+{
|
|
+ struct inode *inode = d_inode(path->dentry);
|
|
+
|
|
+ generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
|
|
+ stat->mode &= ~S_IFMT;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct inode_operations anon_inode_operations = {
|
|
+ .getattr = anon_inode_getattr,
|
|
+};
|
|
+
|
|
+/*
|
|
* anon_inodefs_dname() is called from d_path().
|
|
*/
|
|
static char *anon_inodefs_dname(struct dentry *dentry, char *buffer, int buflen)
|
|
@@ -66,6 +100,7 @@ static struct inode *anon_inode_make_sec
|
|
if (IS_ERR(inode))
|
|
return inode;
|
|
inode->i_flags &= ~S_PRIVATE;
|
|
+ inode->i_op = &anon_inode_operations;
|
|
error = security_inode_init_security_anon(inode, &QSTR(name),
|
|
context_inode);
|
|
if (error) {
|
|
@@ -313,6 +348,7 @@ static int __init anon_inode_init(void)
|
|
anon_inode_inode = alloc_anon_inode(anon_inode_mnt->mnt_sb);
|
|
if (IS_ERR(anon_inode_inode))
|
|
panic("anon_inode_init() inode allocation failed (%ld)\n", PTR_ERR(anon_inode_inode));
|
|
+ anon_inode_inode->i_op = &anon_inode_operations;
|
|
|
|
return 0;
|
|
}
|
|
--- a/fs/internal.h
|
|
+++ b/fs/internal.h
|
|
@@ -343,3 +343,6 @@ static inline bool path_mounted(const st
|
|
void file_f_owner_release(struct file *file);
|
|
bool file_seek_cur_needs_f_lock(struct file *file);
|
|
int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map);
|
|
+int anon_inode_getattr(struct mnt_idmap *idmap, const struct path *path,
|
|
+ struct kstat *stat, u32 request_mask,
|
|
+ unsigned int query_flags);
|
|
--- a/fs/libfs.c
|
|
+++ b/fs/libfs.c
|
|
@@ -1647,7 +1647,13 @@ struct inode *alloc_anon_inode(struct su
|
|
* that it already _is_ on the dirty list.
|
|
*/
|
|
inode->i_state = I_DIRTY;
|
|
- inode->i_mode = S_IRUSR | S_IWUSR;
|
|
+ /*
|
|
+ * Historically anonymous inodes didn't have a type at all and
|
|
+ * userspace has come to rely on this. Internally they're just
|
|
+ * regular files but S_IFREG is masked off when reporting
|
|
+ * information to userspace.
|
|
+ */
|
|
+ inode->i_mode = S_IFREG | S_IRUSR | S_IWUSR;
|
|
inode->i_uid = current_fsuid();
|
|
inode->i_gid = current_fsgid();
|
|
inode->i_flags |= S_PRIVATE;
|