Skip to content

Commit 9d36c51

Browse files
committed
Merge patch series "fs: harden anon inodes"
Christian Brauner <[email protected]> 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/[email protected]: 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/[email protected] Signed-off-by: Christian Brauner <[email protected]>
2 parents 418556f + 25a6cc9 commit 9d36c51

File tree

7 files changed

+130
-26
lines changed

7 files changed

+130
-26
lines changed

fs/anon_inodes.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,50 @@
2424

2525
#include <linux/uaccess.h>
2626

27+
#include "internal.h"
28+
2729
static struct vfsmount *anon_inode_mnt __ro_after_init;
2830
static struct inode *anon_inode_inode __ro_after_init;
2931

32+
/*
33+
* User space expects anonymous inodes to have no file type in st_mode.
34+
*
35+
* In particular, 'lsof' has this legacy logic:
36+
*
37+
* type = s->st_mode & S_IFMT;
38+
* switch (type) {
39+
* ...
40+
* case 0:
41+
* if (!strcmp(p, "anon_inode"))
42+
* Lf->ntype = Ntype = N_ANON_INODE;
43+
*
44+
* to detect our old anon_inode logic.
45+
*
46+
* Rather than mess with our internal sane inode data, just fix it
47+
* up here in getattr() by masking off the format bits.
48+
*/
49+
int anon_inode_getattr(struct mnt_idmap *idmap, const struct path *path,
50+
struct kstat *stat, u32 request_mask,
51+
unsigned int query_flags)
52+
{
53+
struct inode *inode = d_inode(path->dentry);
54+
55+
generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
56+
stat->mode &= ~S_IFMT;
57+
return 0;
58+
}
59+
60+
int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
61+
struct iattr *attr)
62+
{
63+
return -EOPNOTSUPP;
64+
}
65+
66+
static const struct inode_operations anon_inode_operations = {
67+
.getattr = anon_inode_getattr,
68+
.setattr = anon_inode_setattr,
69+
};
70+
3071
/*
3172
* anon_inodefs_dname() is called from d_path().
3273
*/
@@ -45,6 +86,8 @@ static int anon_inodefs_init_fs_context(struct fs_context *fc)
4586
struct pseudo_fs_context *ctx = init_pseudo(fc, ANON_INODE_FS_MAGIC);
4687
if (!ctx)
4788
return -ENOMEM;
89+
fc->s_iflags |= SB_I_NOEXEC;
90+
fc->s_iflags |= SB_I_NODEV;
4891
ctx->dops = &anon_inodefs_dentry_operations;
4992
return 0;
5093
}
@@ -66,6 +109,7 @@ static struct inode *anon_inode_make_secure_inode(
66109
if (IS_ERR(inode))
67110
return inode;
68111
inode->i_flags &= ~S_PRIVATE;
112+
inode->i_op = &anon_inode_operations;
69113
error = security_inode_init_security_anon(inode, &QSTR(name),
70114
context_inode);
71115
if (error) {
@@ -313,6 +357,7 @@ static int __init anon_inode_init(void)
313357
anon_inode_inode = alloc_anon_inode(anon_inode_mnt->mnt_sb);
314358
if (IS_ERR(anon_inode_inode))
315359
panic("anon_inode_init() inode allocation failed (%ld)\n", PTR_ERR(anon_inode_inode));
360+
anon_inode_inode->i_op = &anon_inode_operations;
316361

317362
return 0;
318363
}

fs/internal.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,8 @@ static inline bool path_mounted(const struct path *path)
343343
void file_f_owner_release(struct file *file);
344344
bool file_seek_cur_needs_f_lock(struct file *file);
345345
int statmount_mnt_idmap(struct mnt_idmap *idmap, struct seq_file *seq, bool uid_map);
346+
int anon_inode_getattr(struct mnt_idmap *idmap, const struct path *path,
347+
struct kstat *stat, u32 request_mask,
348+
unsigned int query_flags);
349+
int anon_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
350+
struct iattr *attr);

fs/libfs.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1647,7 +1647,13 @@ struct inode *alloc_anon_inode(struct super_block *s)
16471647
* that it already _is_ on the dirty list.
16481648
*/
16491649
inode->i_state = I_DIRTY;
1650-
inode->i_mode = S_IRUSR | S_IWUSR;
1650+
/*
1651+
* Historically anonymous inodes didn't have a type at all and
1652+
* userspace has come to rely on this. Internally they're just
1653+
* regular files but S_IFREG is masked off when reporting
1654+
* information to userspace.
1655+
*/
1656+
inode->i_mode = S_IFREG | S_IRUSR | S_IWUSR;
16511657
inode->i_uid = current_fsuid();
16521658
inode->i_gid = current_fsgid();
16531659
inode->i_flags |= S_PRIVATE;

fs/pidfs.c

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -569,36 +569,14 @@ static struct vfsmount *pidfs_mnt __ro_after_init;
569569
static int pidfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
570570
struct iattr *attr)
571571
{
572-
return -EOPNOTSUPP;
572+
return anon_inode_setattr(idmap, dentry, attr);
573573
}
574574

575-
576-
/*
577-
* User space expects pidfs inodes to have no file type in st_mode.
578-
*
579-
* In particular, 'lsof' has this legacy logic:
580-
*
581-
* type = s->st_mode & S_IFMT;
582-
* switch (type) {
583-
* ...
584-
* case 0:
585-
* if (!strcmp(p, "anon_inode"))
586-
* Lf->ntype = Ntype = N_ANON_INODE;
587-
*
588-
* to detect our old anon_inode logic.
589-
*
590-
* Rather than mess with our internal sane inode data, just fix it
591-
* up here in getattr() by masking off the format bits.
592-
*/
593575
static int pidfs_getattr(struct mnt_idmap *idmap, const struct path *path,
594576
struct kstat *stat, u32 request_mask,
595577
unsigned int query_flags)
596578
{
597-
struct inode *inode = d_inode(path->dentry);
598-
599-
generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat);
600-
stat->mode &= ~S_IFMT;
601-
return 0;
579+
return anon_inode_getattr(idmap, path, stat, request_mask, query_flags);
602580
}
603581

604582
static const struct inode_operations pidfs_inode_operations = {

tools/testing/selftests/filesystems/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
dnotify_test
33
devpts_pts
44
file_stressor
5+
anon_inode_test
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# SPDX-License-Identifier: GPL-2.0
22

33
CFLAGS += $(KHDR_INCLUDES)
4-
TEST_GEN_PROGS := devpts_pts file_stressor
4+
TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test
55
TEST_GEN_PROGS_EXTENDED := dnotify_test
66

77
include ../lib.mk
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
#define _GNU_SOURCE
3+
#define __SANE_USERSPACE_TYPES__
4+
5+
#include <fcntl.h>
6+
#include <stdio.h>
7+
#include <sys/stat.h>
8+
9+
#include "../kselftest_harness.h"
10+
#include "overlayfs/wrappers.h"
11+
12+
TEST(anon_inode_no_chown)
13+
{
14+
int fd_context;
15+
16+
fd_context = sys_fsopen("tmpfs", 0);
17+
ASSERT_GE(fd_context, 0);
18+
19+
ASSERT_LT(fchown(fd_context, 1234, 5678), 0);
20+
ASSERT_EQ(errno, EOPNOTSUPP);
21+
22+
EXPECT_EQ(close(fd_context), 0);
23+
}
24+
25+
TEST(anon_inode_no_chmod)
26+
{
27+
int fd_context;
28+
29+
fd_context = sys_fsopen("tmpfs", 0);
30+
ASSERT_GE(fd_context, 0);
31+
32+
ASSERT_LT(fchmod(fd_context, 0777), 0);
33+
ASSERT_EQ(errno, EOPNOTSUPP);
34+
35+
EXPECT_EQ(close(fd_context), 0);
36+
}
37+
38+
TEST(anon_inode_no_exec)
39+
{
40+
int fd_context;
41+
42+
fd_context = sys_fsopen("tmpfs", 0);
43+
ASSERT_GE(fd_context, 0);
44+
45+
ASSERT_LT(execveat(fd_context, "", NULL, NULL, AT_EMPTY_PATH), 0);
46+
ASSERT_EQ(errno, EACCES);
47+
48+
EXPECT_EQ(close(fd_context), 0);
49+
}
50+
51+
TEST(anon_inode_no_open)
52+
{
53+
int fd_context;
54+
55+
fd_context = sys_fsopen("tmpfs", 0);
56+
ASSERT_GE(fd_context, 0);
57+
58+
ASSERT_GE(dup2(fd_context, 500), 0);
59+
ASSERT_EQ(close(fd_context), 0);
60+
fd_context = 500;
61+
62+
ASSERT_LT(open("/proc/self/fd/500", 0), 0);
63+
ASSERT_EQ(errno, ENXIO);
64+
65+
EXPECT_EQ(close(fd_context), 0);
66+
}
67+
68+
TEST_HARNESS_MAIN
69+

0 commit comments

Comments
 (0)