Unverified Commit 9d36c514 authored by Christian Brauner's avatar Christian Brauner
Browse files

Merge patch series "fs: harden anon inodes"

Christian Brauner <brauner@kernel.org> says:

* Anonymous inodes currently don't come with a proper mode causing
  issues in the kernel when we want to add useful VFS debug assert. Fix
  that by giving them a proper mode and masking it off when we report it
  to userspace which relies on them not having any mode.

* Anonymous inodes currently allow to change inode attributes because
  the VFS falls back to simple_setattr() if i_op->setattr isn't
  implemented. This means the ownership and mode for every single user
  of anon_inode_inode can be changed. Block that as it's either useless
  or actively harmful. If specific ownership is needed the respective
  subsystem should allocate anonymous inodes from their own private
  superblock.

* Port pidfs to the new anon_inode_{g,s}etattr() helpers.

* Add proper tests for anonymous inode behavior.

The anonymous inode specific fixes should ideally be backported to all
LTS kernels.

* patches from https://lore.kernel.org/20250407-work-anon_inode-v1-0-53a44c20d44e@kernel.org:
  selftests/filesystems: add fourth test for anonymous inodes
  selftests/filesystems: add third test for anonymous inodes
  selftests/filesystems: add second test for anonymous inodes
  selftests/filesystems: add first test for anonymous inodes
  anon_inode: raise SB_I_NODEV and SB_I_NOEXEC
  pidfs: use anon_inode_setattr()
  anon_inode: explicitly block ->setattr()
  pidfs: use anon_inode_getattr()
  anon_inode: use a proper mode internally

Link: https://lore.kernel.org/20250407-work-anon_inode-v1-0-53a44c20d44e@kernel.org


Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parents 418556fa 25a6cc9a
Loading
Loading
Loading
Loading
+45 −0
Original line number Diff line number Diff line
@@ -24,9 +24,50 @@

#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;
}

int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
		       struct iattr *attr)
{
	return -EOPNOTSUPP;
}

static const struct inode_operations anon_inode_operations = {
	.getattr = anon_inode_getattr,
	.setattr = anon_inode_setattr,
};

/*
 * anon_inodefs_dname() is called from d_path().
 */
@@ -45,6 +86,8 @@ static int anon_inodefs_init_fs_context(struct fs_context *fc)
	struct pseudo_fs_context *ctx = init_pseudo(fc, ANON_INODE_FS_MAGIC);
	if (!ctx)
		return -ENOMEM;
	fc->s_iflags |= SB_I_NOEXEC;
	fc->s_iflags |= SB_I_NODEV;
	ctx->dops = &anon_inodefs_dentry_operations;
	return 0;
}
@@ -66,6 +109,7 @@ static struct inode *anon_inode_make_secure_inode(
	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 +357,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;
}
+5 −0
Original line number Diff line number Diff line
@@ -343,3 +343,8 @@ static inline bool path_mounted(const struct path *path)
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);
int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
		       struct iattr *attr);
+7 −1
Original line number Diff line number Diff line
@@ -1647,7 +1647,13 @@ struct inode *alloc_anon_inode(struct super_block *s)
	 * 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;
+2 −24
Original line number Diff line number Diff line
@@ -569,36 +569,14 @@ static struct vfsmount *pidfs_mnt __ro_after_init;
static int pidfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
			 struct iattr *attr)
{
	return -EOPNOTSUPP;
	return anon_inode_setattr(idmap, dentry, attr);
}


/*
 * User space expects pidfs 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.
 */
static int pidfs_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;
	return anon_inode_getattr(idmap, path, stat, request_mask, query_flags);
}

static const struct inode_operations pidfs_inode_operations = {
+1 −0
Original line number Diff line number Diff line
@@ -2,3 +2,4 @@
dnotify_test
devpts_pts
file_stressor
anon_inode_test
Loading