mirror of
https://github.com/torvalds/linux.git
synced 2026-05-27 16:44:58 +02:00
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: Christian Brauner <brauner@kernel.org>
This commit is contained in:
commit
9d36c5145a
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
26
fs/pidfs.c
26
fs/pidfs.c
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
dnotify_test
|
||||
devpts_pts
|
||||
file_stressor
|
||||
anon_inode_test
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
CFLAGS += $(KHDR_INCLUDES)
|
||||
TEST_GEN_PROGS := devpts_pts file_stressor
|
||||
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test
|
||||
TEST_GEN_PROGS_EXTENDED := dnotify_test
|
||||
|
||||
include ../lib.mk
|
||||
|
|
|
|||
69
tools/testing/selftests/filesystems/anon_inode_test.c
Normal file
69
tools/testing/selftests/filesystems/anon_inode_test.c
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#define _GNU_SOURCE
|
||||
#define __SANE_USERSPACE_TYPES__
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
#include "overlayfs/wrappers.h"
|
||||
|
||||
TEST(anon_inode_no_chown)
|
||||
{
|
||||
int fd_context;
|
||||
|
||||
fd_context = sys_fsopen("tmpfs", 0);
|
||||
ASSERT_GE(fd_context, 0);
|
||||
|
||||
ASSERT_LT(fchown(fd_context, 1234, 5678), 0);
|
||||
ASSERT_EQ(errno, EOPNOTSUPP);
|
||||
|
||||
EXPECT_EQ(close(fd_context), 0);
|
||||
}
|
||||
|
||||
TEST(anon_inode_no_chmod)
|
||||
{
|
||||
int fd_context;
|
||||
|
||||
fd_context = sys_fsopen("tmpfs", 0);
|
||||
ASSERT_GE(fd_context, 0);
|
||||
|
||||
ASSERT_LT(fchmod(fd_context, 0777), 0);
|
||||
ASSERT_EQ(errno, EOPNOTSUPP);
|
||||
|
||||
EXPECT_EQ(close(fd_context), 0);
|
||||
}
|
||||
|
||||
TEST(anon_inode_no_exec)
|
||||
{
|
||||
int fd_context;
|
||||
|
||||
fd_context = sys_fsopen("tmpfs", 0);
|
||||
ASSERT_GE(fd_context, 0);
|
||||
|
||||
ASSERT_LT(execveat(fd_context, "", NULL, NULL, AT_EMPTY_PATH), 0);
|
||||
ASSERT_EQ(errno, EACCES);
|
||||
|
||||
EXPECT_EQ(close(fd_context), 0);
|
||||
}
|
||||
|
||||
TEST(anon_inode_no_open)
|
||||
{
|
||||
int fd_context;
|
||||
|
||||
fd_context = sys_fsopen("tmpfs", 0);
|
||||
ASSERT_GE(fd_context, 0);
|
||||
|
||||
ASSERT_GE(dup2(fd_context, 500), 0);
|
||||
ASSERT_EQ(close(fd_context), 0);
|
||||
fd_context = 500;
|
||||
|
||||
ASSERT_LT(open("/proc/self/fd/500", 0), 0);
|
||||
ASSERT_EQ(errno, ENXIO);
|
||||
|
||||
EXPECT_EQ(close(fd_context), 0);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
||||
Loading…
Reference in New Issue
Block a user