vfs-6.14-rc2.fixes

-----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCZ6XhrQAKCRCRxhvAZXjc
 oujrAQCpGmhvh2jGIKcSmEigNHOGCUXDG+1QsVpnCeP9OaUrkAEA+dMo4Ai4hz4J
 nYeeAgpjGuu+XLMmi7EiGxpI0fQL3gc=
 =oN/E
 -----END PGP SIGNATURE-----

Merge tag 'vfs-6.14-rc2.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull vfs fixes from Christian Brauner:

 - Fix fsnotify FMODE_NONOTIFY* handling.

   This also disables fsnotify on all pseudo files by default apart from
   very select exceptions. This carries a regression risk so we need to
   watch out and adapt accordingly. However, it is overall a significant
   improvement over the current status quo where every rando file can
   get fsnotify enabled.

 - Cleanup and simplify lockref_init() after recent lockref changes.

 - Fix vboxfs build with gcc-15.

 - Add an assert into inode_set_cached_link() to catch corrupt links.

 - Allow users to also use an empty string check to detect whether a
   given mount option string was empty or not.

 - Fix how security options were appended to statmount()'s ->mnt_opt
   field.

 - Fix statmount() selftests to always check the returned mask.

 - Fix uninitialized value in vfs_statx_path().

 - Fix pidfs_ioctl() sanity checks to guard against ioctl() overloading
   and preserve extensibility.

* tag 'vfs-6.14-rc2.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  vfs: sanity check the length passed to inode_set_cached_link()
  pidfs: improve ioctl handling
  fsnotify: disable pre-content and permission events by default
  selftests: always check mask returned by statmount(2)
  fsnotify: disable notification by default for all pseudo files
  fs: fix adding security options to statmount.mnt_opt
  fsnotify: use accessor to set FMODE_NONOTIFY_*
  lockref: remove count argument of lockref_init
  gfs2: switch to lockref_init(..., 1)
  gfs2: use lockref_init for gl_lockref
  statmount: let unset strings be empty
  vboxsf: fix building with GCC 15
  fs/stat.c: avoid harmless garbage value problem in vfs_statx_path()
This commit is contained in:
Linus Torvalds 2025-02-07 09:22:31 -08:00
commit 8c67da5bc1
19 changed files with 145 additions and 54 deletions

View File

@ -798,7 +798,7 @@ static int ptmx_open(struct inode *inode, struct file *filp)
nonseekable_open(inode, filp);
/* We refuse fsnotify events on ptmx, since it's a shared resource */
filp->f_mode |= FMODE_NONOTIFY;
file_set_fsnotify_mode(filp, FMODE_NONOTIFY);
retval = tty_alloc_file(filp);
if (retval)

View File

@ -1700,7 +1700,7 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
smp_store_release(&dentry->d_name.name, dname); /* ^^^ */
dentry->d_flags = 0;
lockref_init(&dentry->d_lockref, 1);
lockref_init(&dentry->d_lockref);
seqcount_spinlock_init(&dentry->d_seq, &dentry->d_lock);
dentry->d_inode = NULL;
dentry->d_parent = dentry;

View File

@ -726,7 +726,7 @@ static int z_erofs_register_pcluster(struct z_erofs_frontend *fe)
if (IS_ERR(pcl))
return PTR_ERR(pcl);
lockref_init(&pcl->lockref, 1); /* one ref for this request */
lockref_init(&pcl->lockref); /* one ref for this request */
pcl->algorithmformat = map->m_algorithmformat;
pcl->length = 0;
pcl->partial = true;

View File

@ -194,6 +194,11 @@ static int init_file(struct file *f, int flags, const struct cred *cred)
* refcount bumps we should reinitialize the reused file first.
*/
file_ref_init(&f->f_ref, 1);
/*
* Disable permission and pre-content events for all files by default.
* They may be enabled later by file_set_fsnotify_mode_from_watchers().
*/
file_set_fsnotify_mode(f, FMODE_NONOTIFY_PERM);
return 0;
}
@ -375,7 +380,13 @@ struct file *alloc_file_pseudo(struct inode *inode, struct vfsmount *mnt,
if (IS_ERR(file)) {
ihold(inode);
path_put(&path);
return file;
}
/*
* Disable all fsnotify events for pseudo files by default.
* They may be enabled by caller with file_set_fsnotify_mode().
*/
file_set_fsnotify_mode(file, FMODE_NONOTIFY);
return file;
}
EXPORT_SYMBOL(alloc_file_pseudo);
@ -400,6 +411,11 @@ struct file *alloc_file_pseudo_noaccount(struct inode *inode,
return file;
}
file_init_path(file, &path, fops);
/*
* Disable all fsnotify events for pseudo files by default.
* They may be enabled by caller with file_set_fsnotify_mode().
*/
file_set_fsnotify_mode(file, FMODE_NONOTIFY);
return file;
}
EXPORT_SYMBOL_GPL(alloc_file_pseudo_noaccount);

View File

@ -1201,8 +1201,8 @@ int gfs2_glock_get(struct gfs2_sbd *sdp, u64 number,
if (glops->go_instantiate)
gl->gl_flags |= BIT(GLF_INSTANTIATE_NEEDED);
gl->gl_name = name;
lockref_init(&gl->gl_lockref);
lockdep_set_subclass(&gl->gl_lockref.lock, glops->go_subclass);
gl->gl_lockref.count = 1;
gl->gl_state = LM_ST_UNLOCKED;
gl->gl_target = LM_ST_UNLOCKED;
gl->gl_demote_state = LM_ST_EXCLUSIVE;

View File

@ -51,7 +51,6 @@ static void gfs2_init_glock_once(void *foo)
{
struct gfs2_glock *gl = foo;
spin_lock_init(&gl->gl_lockref.lock);
INIT_LIST_HEAD(&gl->gl_holders);
INIT_LIST_HEAD(&gl->gl_lru);
INIT_LIST_HEAD(&gl->gl_ail_list);

View File

@ -236,7 +236,7 @@ static struct gfs2_quota_data *qd_alloc(unsigned hash, struct gfs2_sbd *sdp, str
return NULL;
qd->qd_sbd = sdp;
lockref_init(&qd->qd_lockref, 0);
lockref_init(&qd->qd_lockref);
qd->qd_id = qid;
qd->qd_slot = -1;
INIT_LIST_HEAD(&qd->qd_lru);
@ -297,7 +297,6 @@ static int qd_get(struct gfs2_sbd *sdp, struct kqid qid,
spin_lock_bucket(hash);
*qdp = qd = gfs2_qd_search_bucket(hash, sdp, qid);
if (qd == NULL) {
new_qd->qd_lockref.count++;
*qdp = new_qd;
list_add(&new_qd->qd_list, &sdp->sd_quota_list);
hlist_bl_add_head_rcu(&new_qd->qd_hlist, &qd_hash_table[hash]);
@ -1450,6 +1449,7 @@ int gfs2_quota_init(struct gfs2_sbd *sdp)
if (qd == NULL)
goto fail_brelse;
qd->qd_lockref.count = 0;
set_bit(QDF_CHANGE, &qd->qd_flags);
qd->qd_change = qc_change;
qd->qd_slot = slot;

View File

@ -5087,31 +5087,30 @@ static int statmount_mnt_opts(struct kstatmount *s, struct seq_file *seq)
{
struct vfsmount *mnt = s->mnt;
struct super_block *sb = mnt->mnt_sb;
size_t start = seq->count;
int err;
err = security_sb_show_options(seq, sb);
if (err)
return err;
if (sb->s_op->show_options) {
size_t start = seq->count;
err = security_sb_show_options(seq, sb);
if (err)
return err;
err = sb->s_op->show_options(seq, mnt->mnt_root);
if (err)
return err;
if (unlikely(seq_has_overflowed(seq)))
return -EAGAIN;
if (seq->count == start)
return 0;
/* skip leading comma */
memmove(seq->buf + start, seq->buf + start + 1,
seq->count - start - 1);
seq->count--;
}
if (unlikely(seq_has_overflowed(seq)))
return -EAGAIN;
if (seq->count == start)
return 0;
/* skip leading comma */
memmove(seq->buf + start, seq->buf + start + 1,
seq->count - start - 1);
seq->count--;
return 0;
}
@ -5191,39 +5190,45 @@ static int statmount_string(struct kstatmount *s, u64 flag)
size_t kbufsize;
struct seq_file *seq = &s->seq;
struct statmount *sm = &s->sm;
u32 start = seq->count;
u32 start, *offp;
/* Reserve an empty string at the beginning for any unset offsets */
if (!seq->count)
seq_putc(seq, 0);
start = seq->count;
switch (flag) {
case STATMOUNT_FS_TYPE:
sm->fs_type = start;
offp = &sm->fs_type;
ret = statmount_fs_type(s, seq);
break;
case STATMOUNT_MNT_ROOT:
sm->mnt_root = start;
offp = &sm->mnt_root;
ret = statmount_mnt_root(s, seq);
break;
case STATMOUNT_MNT_POINT:
sm->mnt_point = start;
offp = &sm->mnt_point;
ret = statmount_mnt_point(s, seq);
break;
case STATMOUNT_MNT_OPTS:
sm->mnt_opts = start;
offp = &sm->mnt_opts;
ret = statmount_mnt_opts(s, seq);
break;
case STATMOUNT_OPT_ARRAY:
sm->opt_array = start;
offp = &sm->opt_array;
ret = statmount_opt_array(s, seq);
break;
case STATMOUNT_OPT_SEC_ARRAY:
sm->opt_sec_array = start;
offp = &sm->opt_sec_array;
ret = statmount_opt_sec_array(s, seq);
break;
case STATMOUNT_FS_SUBTYPE:
sm->fs_subtype = start;
offp = &sm->fs_subtype;
statmount_fs_subtype(s, seq);
break;
case STATMOUNT_SB_SOURCE:
sm->sb_source = start;
offp = &sm->sb_source;
ret = statmount_sb_source(s, seq);
break;
default:
@ -5251,6 +5256,7 @@ static int statmount_string(struct kstatmount *s, u64 flag)
seq->buf[seq->count++] = '\0';
sm->mask |= flag;
*offp = start;
return 0;
}

View File

@ -648,7 +648,7 @@ EXPORT_SYMBOL_GPL(fsnotify);
* Later, fsnotify permission hooks do not check if there are permission event
* watches, but that there were permission event watches at open time.
*/
void file_set_fsnotify_mode(struct file *file)
void file_set_fsnotify_mode_from_watchers(struct file *file)
{
struct dentry *dentry = file->f_path.dentry, *parent;
struct super_block *sb = dentry->d_sb;
@ -665,7 +665,7 @@ void file_set_fsnotify_mode(struct file *file)
*/
if (likely(!fsnotify_sb_has_priority_watchers(sb,
FSNOTIFY_PRIO_CONTENT))) {
file->f_mode |= FMODE_NONOTIFY_PERM;
file_set_fsnotify_mode(file, FMODE_NONOTIFY_PERM);
return;
}
@ -676,7 +676,7 @@ void file_set_fsnotify_mode(struct file *file)
if ((!d_is_dir(dentry) && !d_is_reg(dentry)) ||
likely(!fsnotify_sb_has_priority_watchers(sb,
FSNOTIFY_PRIO_PRE_CONTENT))) {
file->f_mode |= FMODE_NONOTIFY | FMODE_NONOTIFY_PERM;
file_set_fsnotify_mode(file, FMODE_NONOTIFY | FMODE_NONOTIFY_PERM);
return;
}
@ -686,19 +686,25 @@ void file_set_fsnotify_mode(struct file *file)
*/
mnt_mask = READ_ONCE(real_mount(file->f_path.mnt)->mnt_fsnotify_mask);
if (unlikely(fsnotify_object_watched(d_inode(dentry), mnt_mask,
FSNOTIFY_PRE_CONTENT_EVENTS)))
FSNOTIFY_PRE_CONTENT_EVENTS))) {
/* Enable pre-content events */
file_set_fsnotify_mode(file, 0);
return;
}
/* Is parent watching for pre-content events on this file? */
if (dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED) {
parent = dget_parent(dentry);
p_mask = fsnotify_inode_watches_children(d_inode(parent));
dput(parent);
if (p_mask & FSNOTIFY_PRE_CONTENT_EVENTS)
if (p_mask & FSNOTIFY_PRE_CONTENT_EVENTS) {
/* Enable pre-content events */
file_set_fsnotify_mode(file, 0);
return;
}
}
/* Nobody watching for pre-content events from this file */
file->f_mode |= FMODE_NONOTIFY | FMODE_NONOTIFY_PERM;
file_set_fsnotify_mode(file, FMODE_NONOTIFY | FMODE_NONOTIFY_PERM);
}
#endif

View File

@ -905,7 +905,8 @@ static int do_dentry_open(struct file *f,
f->f_sb_err = file_sample_sb_err(f);
if (unlikely(f->f_flags & O_PATH)) {
f->f_mode = FMODE_PATH | FMODE_OPENED | FMODE_NONOTIFY;
f->f_mode = FMODE_PATH | FMODE_OPENED;
file_set_fsnotify_mode(f, FMODE_NONOTIFY);
f->f_op = &empty_fops;
return 0;
}
@ -935,10 +936,10 @@ static int do_dentry_open(struct file *f,
/*
* Set FMODE_NONOTIFY_* bits according to existing permission watches.
* If FMODE_NONOTIFY was already set for an fanotify fd, this doesn't
* change anything.
* If FMODE_NONOTIFY mode was already set for an fanotify fd or for a
* pseudo file, this call will not change the mode.
*/
file_set_fsnotify_mode(f);
file_set_fsnotify_mode_from_watchers(f);
error = fsnotify_open_perm(f);
if (error)
goto cleanup_all;
@ -1122,7 +1123,7 @@ struct file *dentry_open_nonotify(const struct path *path, int flags,
if (!IS_ERR(f)) {
int error;
f->f_mode |= FMODE_NONOTIFY;
file_set_fsnotify_mode(f, FMODE_NONOTIFY);
error = vfs_open(path, f);
if (error) {
fput(f);

View File

@ -287,7 +287,6 @@ static bool pidfs_ioctl_valid(unsigned int cmd)
switch (cmd) {
case FS_IOC_GETVERSION:
case PIDFD_GET_CGROUP_NAMESPACE:
case PIDFD_GET_INFO:
case PIDFD_GET_IPC_NAMESPACE:
case PIDFD_GET_MNT_NAMESPACE:
case PIDFD_GET_NET_NAMESPACE:
@ -300,6 +299,17 @@ static bool pidfs_ioctl_valid(unsigned int cmd)
return true;
}
/* Extensible ioctls require some more careful checks. */
switch (_IOC_NR(cmd)) {
case _IOC_NR(PIDFD_GET_INFO):
/*
* Try to prevent performing a pidfd ioctl when someone
* erronously mistook the file descriptor for a pidfd.
* This is not perfect but will catch most cases.
*/
return (_IOC_TYPE(cmd) == _IOC_TYPE(PIDFD_GET_INFO));
}
return false;
}

View File

@ -960,6 +960,12 @@ int create_pipe_files(struct file **res, int flags)
res[1] = f;
stream_open(inode, res[0]);
stream_open(inode, res[1]);
/*
* Disable permission and pre-content events, but enable legacy
* inotify events for legacy users.
*/
file_set_fsnotify_mode(res[0], FMODE_NONOTIFY_PERM);
file_set_fsnotify_mode(res[1], FMODE_NONOTIFY_PERM);
return 0;
}

View File

@ -281,6 +281,8 @@ static int vfs_statx_path(struct path *path, int flags, struct kstat *stat,
u32 request_mask)
{
int error = vfs_getattr(path, stat, request_mask, flags);
if (error)
return error;
if (request_mask & STATX_MNT_ID_UNIQUE) {
stat->mnt_id = real_mount(path->mnt)->mnt_id_unique;
@ -302,7 +304,7 @@ static int vfs_statx_path(struct path *path, int flags, struct kstat *stat,
if (S_ISBLK(stat->mode))
bdev_statx(path, stat, request_mask);
return error;
return 0;
}
static int vfs_statx_fd(int fd, int flags, struct kstat *stat,

View File

@ -21,7 +21,8 @@
#define VBOXSF_SUPER_MAGIC 0x786f4256 /* 'VBox' little endian */
static const unsigned char VBSF_MOUNT_SIGNATURE[4] = "\000\377\376\375";
static const unsigned char VBSF_MOUNT_SIGNATURE[4] = { '\000', '\377', '\376',
'\375' };
static int follow_symlinks;
module_param(follow_symlinks, int, 0444);

View File

@ -222,7 +222,6 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
#define FMODE_FSNOTIFY_HSM(mode) 0
#endif
/*
* Attribute flags. These should be or-ed together to figure out what
* has been changed!
@ -791,6 +790,19 @@ struct inode {
static inline void inode_set_cached_link(struct inode *inode, char *link, int linklen)
{
int testlen;
/*
* TODO: patch it into a debug-only check if relevant macros show up.
* In the meantime, since we are suffering strlen even on production kernels
* to find the right length, do a fixup if the wrong value got passed.
*/
testlen = strlen(link);
if (testlen != linklen) {
WARN_ONCE(1, "bad length passed for symlink [%s] (got %d, expected %d)",
link, linklen, testlen);
linklen = testlen;
}
inode->i_link = link;
inode->i_linklen = linklen;
inode->i_opflags |= IOP_CACHED_LINK;
@ -3140,6 +3152,12 @@ static inline void exe_file_allow_write_access(struct file *exe_file)
allow_write_access(exe_file);
}
static inline void file_set_fsnotify_mode(struct file *file, fmode_t mode)
{
file->f_mode &= ~FMODE_FSNOTIFY_MASK;
file->f_mode |= mode;
}
static inline bool inode_is_open_for_write(const struct inode *inode)
{
return atomic_read(&inode->i_writecount) > 0;

View File

@ -129,7 +129,7 @@ static inline int fsnotify_file(struct file *file, __u32 mask)
#ifdef CONFIG_FANOTIFY_ACCESS_PERMISSIONS
void file_set_fsnotify_mode(struct file *file);
void file_set_fsnotify_mode_from_watchers(struct file *file);
/*
* fsnotify_file_area_perm - permission hook before access to file range
@ -213,7 +213,7 @@ static inline int fsnotify_open_perm(struct file *file)
}
#else
static inline void file_set_fsnotify_mode(struct file *file)
static inline void file_set_fsnotify_mode_from_watchers(struct file *file)
{
}

View File

@ -37,12 +37,13 @@ struct lockref {
/**
* lockref_init - Initialize a lockref
* @lockref: pointer to lockref structure
* @count: initial count
*
* Initializes @lockref->count to 1.
*/
static inline void lockref_init(struct lockref *lockref, unsigned int count)
static inline void lockref_init(struct lockref *lockref)
{
spin_lock_init(&lockref->lock);
lockref->count = count;
lockref->count = 1;
}
void lockref_get(struct lockref *lockref);

View File

@ -479,6 +479,11 @@ struct file *sock_alloc_file(struct socket *sock, int flags, const char *dname)
sock->file = file;
file->private_data = sock;
stream_open(SOCK_INODE(sock), file);
/*
* Disable permission and pre-content events, but enable legacy
* inotify events for legacy users.
*/
file_set_fsnotify_mode(file, FMODE_NONOTIFY_PERM);
return file;
}
EXPORT_SYMBOL(sock_alloc_file);

View File

@ -383,6 +383,10 @@ static void test_statmount_mnt_point(void)
return;
}
if (!(sm->mask & STATMOUNT_MNT_POINT)) {
ksft_test_result_fail("missing STATMOUNT_MNT_POINT in mask\n");
return;
}
if (strcmp(sm->str + sm->mnt_point, "/") != 0) {
ksft_test_result_fail("unexpected mount point: '%s' != '/'\n",
sm->str + sm->mnt_point);
@ -408,6 +412,10 @@ static void test_statmount_mnt_root(void)
strerror(errno));
return;
}
if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
ksft_test_result_fail("missing STATMOUNT_MNT_ROOT in mask\n");
return;
}
mnt_root = sm->str + sm->mnt_root;
last_root = strrchr(mnt_root, '/');
if (last_root)
@ -437,6 +445,10 @@ static void test_statmount_fs_type(void)
strerror(errno));
return;
}
if (!(sm->mask & STATMOUNT_FS_TYPE)) {
ksft_test_result_fail("missing STATMOUNT_FS_TYPE in mask\n");
return;
}
fs_type = sm->str + sm->fs_type;
for (s = known_fs; s != NULL; s++) {
if (strcmp(fs_type, *s) == 0)
@ -464,6 +476,11 @@ static void test_statmount_mnt_opts(void)
return;
}
if (!(sm->mask & STATMOUNT_MNT_BASIC)) {
ksft_test_result_fail("missing STATMOUNT_MNT_BASIC in mask\n");
return;
}
while (getline(&line, &len, f_mountinfo) != -1) {
int i;
char *p, *p2;
@ -514,7 +531,10 @@ static void test_statmount_mnt_opts(void)
if (p2)
*p2 = '\0';
statmount_opts = sm->str + sm->mnt_opts;
if (sm->mask & STATMOUNT_MNT_OPTS)
statmount_opts = sm->str + sm->mnt_opts;
else
statmount_opts = "";
if (strcmp(statmount_opts, p) != 0)
ksft_test_result_fail(
"unexpected mount options: '%s' != '%s'\n",