mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
vfs-7.2-rc5.fixes
Please consider pulling these changes from the signed vfs-7.2-rc5.fixes tag.
Thanks!
Christian
-----BEGIN PGP SIGNATURE-----
iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCamYljAAKCRCRxhvAZXjc
ogWyAPwORZDIIsRiAQbnPs+OkYszuWCY29OeUrTf+m3z+tBTLwD+NUFtpH5XIcYJ
3jHtGXPoHjEaOVsNyIdxwnOxWfo+6ws=
=VNq0
-----END PGP SIGNATURE-----
Merge tag 'vfs-7.2-rc5.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs
Pull vfs fixes from Christian Brauner:
- vfs: Preserve the ACL_DONT_CACHE state in forget_cached_acl().
ACL_DONT_CACHE is meant to be a permanent opt-out from ACL caching
which FUSE relies on for servers that don't negotiate FUSE_POSIX_ACL.
The helper replaced it with ACL_NOT_CACHED, silently re-enabling the
cache, and as fuse doesn't invalidate the cache for such servers a
properly timed get_acl() returned stale ACLs. Comes with a fuse
selftest reproducing this.
- pidfs:
- Preserve PIDFD_THREAD when a thread pidfd is reopened via
open_by_handle_at(). PIDFD_THREAD shares the O_EXCL bit which
do_dentry_open() strips after the flags have been validated, so
the reopened pidfd silently became a process pidfd. Comes with a
selftest.
- Add a pidfs_dentry_open() helper so the regular pidfd allocation
path and the file handle path share the code that forces O_RDWR
and reapplies the pidfd flags that do_dentry_open() strips.
- Handle FS_IOC32_GETVERSION in the compat ioctl path.
- Make pidfs_ino_lock static.
- iomap:
- Fix the block range calculation in ifs_clear_range_dirty() so a
partial clear doesn't drop the dirty state of blocks the range
only partially covers.
- Support invalidating partial folios so a partial truncate or hole
punch with blocksize < foliosize doesn't leave stale dirty bits
behind.
- Only set did_zero when iomap_zero_iter() actually zeroed
something.
- Guard ifs_set_range_dirty() and ifs_set_range_uptodate() against
zero-length ranges where the unsigned last-block calculation
underflows and bitmap_set() writes far beyond the ifs->state
allocation.
- Don't merge ioends with different io_private values as the merge
could leak or corrupt the private data of the individual ioends.
- exec:
- Raise bprm->have_execfd only once the binfmt_misc interpreter has
actually been opened. The flag was set as soon as a matching 'O'
or 'C' entry was found. If the interpreter open failed with
ENOEXEC the exec fell through to the next binary format with
have_execfd raised but no executable staged and begin_new_exec()
NULL derefed past the point of no return.
- Fix an unsigned loop counter wrap in transfer_args_to_stack() on
nommu. An overlong argument or environment string pushes bprm->p
below PAGE_SIZE, the stop index becomes zero, and the loop never
terminates, wrapping its counter and copying garbage from in
front of the page array into the new process stack.
- Make binfmt_elf_fdpic only honour the first PT_INTERP like
binfmt_elf does. Each additional PT_INTERP overwrote the previous
interpreter, leaking the name allocation and the interpreter file
reference together with the write denial open_exec() took,
leaving the file unwritable for as long as the system runs.
- overlayfs:
- Compare the full escaped xattr prefix including the trailing dot.
An xattr like "trusted.overlay.overlayfoo" was misclassified as
an escaped overlay xattr.
- Check read access to the copy_file_range() source with the
source's mounter credentials.
- super: Thawing a filesystem whose block device was frozen with
bdev_freeze() deadlocked. Dropping the last block layer freeze
reference from under s_umount ends up in fs_bdev_thaw() which
reacquires s_umount on the same task. Pin the superblock with an
active reference instead and call bdev_thaw() without holding
s_umount.
- procfs: Return EACCES instead of success when the ptrace access check
for namespace links fails.
- afs: Use afs_dir_get_block() rather than afs_dir_find_block() for
block 0 in afs_edit_dir_remove(), matching afs_edit_dir_add().
- Push the memcg gating of ->nr_cached_objects() down into the btrfs
and shmem callbacks instead of skipping every callback during
non-root memcg reclaim. The blanket check short-circuited XFS whose
inode reclaim hook is intentionally driven from per-memcg contexts to
free memcg-charged slab.
- eventpoll: Pin files while checking reverse paths.
Since struct file became SLAB_TYPESAFE_BY_RCU a concurrent close
could free and recycle the file under the check which then took and
dropped the f_lock of whatever live file now occupies that slot.
* tag 'vfs-7.2-rc5.fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (24 commits)
super: fix emergency thaw deadlock on frozen block devices
pidfs: make pidfs_ino_lock static
eventpoll: pin files while checking reverse paths
fs: push nr_cached_objects memcg gating into individual filesystems
afs: Fix afs_edit_dir_remove() to get, not find, block 0
iomap: prevent ioend merge when io_private differs
iomap: add comments for ifs_clear/set_range_dirty()
iomap: fix out-of-bounds bitmap_set() with zero-length range
iomap: fix incorrect did_zero setting in iomap_zero_iter()
iomap: support invalidating partial folios
iomap: correct the range of a partial dirty clear
fs/super: fix emergency thaw double-unlock of s_umount
pidfs: handle FS_IOC32_GETVERSION in compat ioctl
ovl: check access to copy_file_range source with src mounter creds
proc: Fix broken error paths for namespace links
pidfs: add pidfs_dentry_open() helper
selftests/pidfd: check PIDFD_THREAD survives open_by_handle_at()
pidfs: preserve thread pidfds reopened by file handle
ovl: fix trusted xattr escape prefix matching
selftests/fuse: add ACL_DONT_CACHE regression test
...
This commit is contained in:
commit
4235cb24ec
|
|
@ -415,7 +415,7 @@ void afs_edit_dir_remove(struct afs_vnode *vnode,
|
|||
if (!afs_dir_init_iter(&iter, name))
|
||||
return;
|
||||
|
||||
meta = afs_dir_find_block(&iter, 0);
|
||||
meta = afs_dir_get_block(&iter, 0);
|
||||
if (!meta)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -231,6 +231,10 @@ static int load_elf_fdpic_binary(struct linux_binprm *bprm)
|
|||
for (i = 0; i < exec_params.hdr.e_phnum; i++, phdr++) {
|
||||
switch (phdr->p_type) {
|
||||
case PT_INTERP:
|
||||
/* elf ABI allows only one interpreter */
|
||||
if (interpreter_name)
|
||||
continue;
|
||||
|
||||
retval = -ENOMEM;
|
||||
if (phdr->p_filesz > PATH_MAX)
|
||||
goto error;
|
||||
|
|
|
|||
|
|
@ -228,9 +228,6 @@ static int load_misc_binary(struct linux_binprm *bprm)
|
|||
goto ret;
|
||||
}
|
||||
|
||||
if (fmt->flags & MISC_FMT_OPEN_BINARY)
|
||||
bprm->have_execfd = 1;
|
||||
|
||||
/* make argv[1] be the path to the binary */
|
||||
retval = copy_string_kernel(bprm->interp, bprm);
|
||||
if (retval < 0)
|
||||
|
|
@ -260,6 +257,8 @@ static int load_misc_binary(struct linux_binprm *bprm)
|
|||
goto ret;
|
||||
|
||||
bprm->interpreter = interp_file;
|
||||
if (fmt->flags & MISC_FMT_OPEN_BINARY)
|
||||
bprm->have_execfd = 1;
|
||||
if (fmt->flags & MISC_FMT_CREDENTIALS)
|
||||
bprm->execfd_creds = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <linux/namei.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/magic.h>
|
||||
#include <linux/memcontrol.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/ratelimit.h>
|
||||
#include <linux/crc32c.h>
|
||||
|
|
@ -2434,6 +2435,15 @@ static long btrfs_nr_cached_objects(struct super_block *sb, struct shrink_contro
|
|||
struct btrfs_fs_info *fs_info = btrfs_sb(sb);
|
||||
const s64 nr = percpu_counter_read_positive(&fs_info->evictable_extent_maps);
|
||||
|
||||
/*
|
||||
* The evictable extent map counter is filesystem-global and does not
|
||||
* honour sc->memcg, so it is only meaningful on the global (kswapd or
|
||||
* root direct reclaim) shrink path. Skip the per-memcg iterations of
|
||||
* shrink_slab_memcg() to avoid queueing duplicate global work.
|
||||
*/
|
||||
if (!mem_cgroup_shrink_is_root(sc))
|
||||
return 0;
|
||||
|
||||
trace_btrfs_extent_map_shrinker_count(fs_info, nr);
|
||||
|
||||
return nr;
|
||||
|
|
|
|||
|
|
@ -459,11 +459,14 @@ static struct kmem_cache *pwq_cache __ro_after_init;
|
|||
* Wrapper anchor for file->f_ep when the watched file is not itself an
|
||||
* eventpoll; for the epoll-watches-epoll case, file->f_ep points at
|
||||
* &watched_ep->refs directly. The ->next field threads
|
||||
* ctx->tfile_check_list during one EPOLL_CTL_ADD path check.
|
||||
* ctx->tfile_check_list during one EPOLL_CTL_ADD path check. The ->file
|
||||
* field holds a reference to the associated file while the head is on
|
||||
* the list.
|
||||
*/
|
||||
struct epitems_head {
|
||||
struct hlist_head epitems;
|
||||
struct epitems_head *next;
|
||||
struct file *file;
|
||||
};
|
||||
|
||||
static struct kmem_cache *ephead_cache __ro_after_init;
|
||||
|
|
@ -480,6 +483,16 @@ static void list_file(struct file *file, struct ep_ctl_ctx *ctx)
|
|||
|
||||
head = container_of(file->f_ep, struct epitems_head, epitems);
|
||||
if (!head->next) {
|
||||
/*
|
||||
* The caller owns a reference to @file or holds the ep->mtx for the
|
||||
* epitem that led here. The latter blocks eventpoll_release_file()
|
||||
* before the file allocation can be freed and reused. A dying leaf
|
||||
* can be skipped since removing links cannot increase the reverse
|
||||
* path count.
|
||||
*/
|
||||
if (!file_ref_get(&file->f_ref))
|
||||
return;
|
||||
head->file = file;
|
||||
head->next = ctx->tfile_check_list;
|
||||
ctx->tfile_check_list = head;
|
||||
}
|
||||
|
|
@ -489,15 +502,18 @@ static void unlist_file(struct epitems_head *head)
|
|||
{
|
||||
struct epitems_head *to_free = head;
|
||||
struct hlist_node *p = rcu_dereference(hlist_first_rcu(&head->epitems));
|
||||
struct file *file = head->file;
|
||||
if (p) {
|
||||
struct epitem *epi= container_of(p, struct epitem, fllink);
|
||||
spin_lock(&epi->ffd.file->f_lock);
|
||||
if (!hlist_empty(&head->epitems))
|
||||
to_free = NULL;
|
||||
head->next = NULL;
|
||||
head->file = NULL;
|
||||
spin_unlock(&epi->ffd.file->f_lock);
|
||||
}
|
||||
free_ephead(to_free);
|
||||
fput(file);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SYSCTL
|
||||
|
|
|
|||
|
|
@ -740,7 +740,7 @@ int transfer_args_to_stack(struct linux_binprm *bprm,
|
|||
stop = bprm->p >> PAGE_SHIFT;
|
||||
sp = *sp_location;
|
||||
|
||||
for (index = MAX_ARG_PAGES - 1; index >= stop; index--) {
|
||||
for (index = MAX_ARG_PAGES; index-- > stop; ) {
|
||||
unsigned int offset = index == stop ? bprm->p & ~PAGE_MASK : 0;
|
||||
char *src = kmap_local_page(bprm->page[index]) + offset;
|
||||
sp -= PAGE_SIZE - offset;
|
||||
|
|
|
|||
|
|
@ -68,11 +68,13 @@ static bool ifs_set_range_uptodate(struct folio *folio,
|
|||
struct iomap_folio_state *ifs, size_t off, size_t len)
|
||||
{
|
||||
struct inode *inode = folio->mapping->host;
|
||||
unsigned int first_blk = off >> inode->i_blkbits;
|
||||
unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
|
||||
unsigned int nr_blks = last_blk - first_blk + 1;
|
||||
unsigned int first_blk, last_blk;
|
||||
|
||||
bitmap_set(ifs->state, first_blk, nr_blks);
|
||||
if (len) {
|
||||
first_blk = off >> inode->i_blkbits;
|
||||
last_blk = (off + len - 1) >> inode->i_blkbits;
|
||||
bitmap_set(ifs->state, first_blk, last_blk - first_blk + 1);
|
||||
}
|
||||
return ifs_is_fully_uptodate(folio, ifs);
|
||||
}
|
||||
|
||||
|
|
@ -172,18 +174,29 @@ static unsigned iomap_find_dirty_range(struct folio *folio, u64 *range_start,
|
|||
return range_end - *range_start;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the per-block dirty bits for the range [@off, @off + @len) within a
|
||||
* folio. The range is rounded inwards so that only blocks fully covered by
|
||||
* the range are cleared. This is required for operations like folio
|
||||
* invalidation, where we must ensure a block is fully clean before discarding
|
||||
* it.
|
||||
*/
|
||||
static void ifs_clear_range_dirty(struct folio *folio,
|
||||
struct iomap_folio_state *ifs, size_t off, size_t len)
|
||||
{
|
||||
struct inode *inode = folio->mapping->host;
|
||||
unsigned int blks_per_folio = i_blocks_per_folio(inode, folio);
|
||||
unsigned int first_blk = (off >> inode->i_blkbits);
|
||||
unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
|
||||
unsigned int nr_blks = last_blk - first_blk + 1;
|
||||
unsigned int first_blk = round_up(off, i_blocksize(inode)) >>
|
||||
inode->i_blkbits;
|
||||
unsigned int last_blk = (off + len) >> inode->i_blkbits;
|
||||
unsigned long flags;
|
||||
|
||||
if (first_blk >= last_blk)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&ifs->state_lock, flags);
|
||||
bitmap_clear(ifs->state, first_blk + blks_per_folio, nr_blks);
|
||||
bitmap_clear(ifs->state, first_blk + blks_per_folio,
|
||||
last_blk - first_blk);
|
||||
spin_unlock_irqrestore(&ifs->state_lock, flags);
|
||||
}
|
||||
|
||||
|
|
@ -195,18 +208,29 @@ static void iomap_clear_range_dirty(struct folio *folio, size_t off, size_t len)
|
|||
ifs_clear_range_dirty(folio, ifs, off, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the per-block dirty bits for the range [@off, @off + @len) within a
|
||||
* folio. The range is rounded outwards so that any block partially touched
|
||||
* by the range is marked dirty. This ensures blocks containing even a
|
||||
* single dirty byte will be included in subsequent writeback, preventing
|
||||
* data loss when partial blocks are written.
|
||||
*/
|
||||
static void ifs_set_range_dirty(struct folio *folio,
|
||||
struct iomap_folio_state *ifs, size_t off, size_t len)
|
||||
{
|
||||
struct inode *inode = folio->mapping->host;
|
||||
unsigned int blks_per_folio = i_blocks_per_folio(inode, folio);
|
||||
unsigned int first_blk = (off >> inode->i_blkbits);
|
||||
unsigned int last_blk = (off + len - 1) >> inode->i_blkbits;
|
||||
unsigned int nr_blks = last_blk - first_blk + 1;
|
||||
unsigned int first_blk, last_blk;
|
||||
unsigned long flags;
|
||||
|
||||
if (!len)
|
||||
return;
|
||||
|
||||
first_blk = off >> inode->i_blkbits;
|
||||
last_blk = (off + len - 1) >> inode->i_blkbits;
|
||||
spin_lock_irqsave(&ifs->state_lock, flags);
|
||||
bitmap_set(ifs->state, first_blk + blks_per_folio, nr_blks);
|
||||
bitmap_set(ifs->state, first_blk + blks_per_folio,
|
||||
last_blk - first_blk + 1);
|
||||
spin_unlock_irqrestore(&ifs->state_lock, flags);
|
||||
}
|
||||
|
||||
|
|
@ -811,6 +835,8 @@ void iomap_invalidate_folio(struct folio *folio, size_t offset, size_t len)
|
|||
WARN_ON_ONCE(folio_test_writeback(folio));
|
||||
folio_cancel_dirty(folio);
|
||||
ifs_free(folio);
|
||||
} else {
|
||||
iomap_clear_range_dirty(folio, offset, len);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(iomap_invalidate_folio);
|
||||
|
|
@ -1619,6 +1645,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
|
|||
const struct iomap_write_ops *write_ops)
|
||||
{
|
||||
u64 bytes = iomap_length(iter);
|
||||
bool zeroed = false;
|
||||
int status;
|
||||
|
||||
do {
|
||||
|
|
@ -1639,6 +1666,8 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
|
|||
/* a NULL folio means we're done with a folio batch */
|
||||
if (!folio) {
|
||||
status = iomap_iter_advance_full(iter);
|
||||
if (status)
|
||||
return status;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1649,6 +1678,7 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
|
|||
bytes);
|
||||
|
||||
folio_zero_range(folio, offset, bytes);
|
||||
zeroed = true;
|
||||
folio_mark_accessed(folio);
|
||||
|
||||
ret = iomap_write_end(iter, bytes, bytes, folio);
|
||||
|
|
@ -1658,10 +1688,10 @@ static int iomap_zero_iter(struct iomap_iter *iter, bool *did_zero,
|
|||
|
||||
status = iomap_iter_advance(iter, bytes);
|
||||
if (status)
|
||||
break;
|
||||
return status;
|
||||
} while ((bytes = iomap_length(iter)) > 0);
|
||||
|
||||
if (did_zero)
|
||||
if (did_zero && zeroed)
|
||||
*did_zero = true;
|
||||
return status;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -385,6 +385,8 @@ static bool iomap_ioend_can_merge(struct iomap_ioend *ioend,
|
|||
|
||||
if (ioend->io_bio.bi_status != next->io_bio.bi_status)
|
||||
return false;
|
||||
if (ioend->io_private != next->io_private)
|
||||
return false;
|
||||
if (next->io_flags & IOMAP_IOEND_BOUNDARY)
|
||||
return false;
|
||||
if ((ioend->io_flags & IOMAP_IOEND_NOMERGE_FLAGS) !=
|
||||
|
|
|
|||
|
|
@ -528,6 +528,7 @@ static loff_t ovl_copyfile(struct file *file_in, loff_t pos_in,
|
|||
struct file *file_out, loff_t pos_out,
|
||||
loff_t len, unsigned int flags, enum ovl_copyop op)
|
||||
{
|
||||
struct inode *inode_in = file_inode(file_in);
|
||||
struct inode *inode_out = file_inode(file_out);
|
||||
struct file *realfile_in, *realfile_out;
|
||||
loff_t ret;
|
||||
|
|
@ -551,7 +552,20 @@ static loff_t ovl_copyfile(struct file *file_in, loff_t pos_in,
|
|||
if (IS_ERR(realfile_in))
|
||||
goto out_unlock;
|
||||
|
||||
with_ovl_creds(file_inode(file_out)->i_sb) {
|
||||
/*
|
||||
* For cross-sb copy, vfs_copy_file_range() will verify read access with
|
||||
* the mounter creds of the dest fs mounter, so we need to explicitly
|
||||
* verify read access with the source mounter creds.
|
||||
*/
|
||||
if (unlikely(inode_in->i_sb != inode_out->i_sb)) {
|
||||
with_ovl_creds(inode_in->i_sb) {
|
||||
ret = rw_verify_area(READ, realfile_in, &pos_in, len);
|
||||
if (unlikely(ret))
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
|
||||
with_ovl_creds(inode_out->i_sb) {
|
||||
switch (op) {
|
||||
case OVL_COPY:
|
||||
ret = vfs_copy_file_range(realfile_in, pos_in,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ static bool ovl_is_escaped_xattr(struct super_block *sb, const char *name)
|
|||
OVL_XATTR_ESCAPE_USER_PREFIX_LEN) == 0;
|
||||
else
|
||||
return strncmp(name, OVL_XATTR_ESCAPE_TRUSTED_PREFIX,
|
||||
OVL_XATTR_ESCAPE_TRUSTED_PREFIX_LEN - 1) == 0;
|
||||
OVL_XATTR_ESCAPE_TRUSTED_PREFIX_LEN) == 0;
|
||||
}
|
||||
|
||||
static bool ovl_is_own_xattr(struct super_block *sb, const char *name)
|
||||
|
|
|
|||
54
fs/pidfs.c
54
fs/pidfs.c
|
|
@ -1,5 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <linux/anon_inodes.h>
|
||||
#include <linux/compat.h>
|
||||
#include <linux/exportfs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
|
|
@ -107,7 +108,7 @@ struct pidfs_attr {
|
|||
|
||||
#if BITS_PER_LONG == 32
|
||||
|
||||
DEFINE_SPINLOCK(pidfs_ino_lock);
|
||||
static DEFINE_SPINLOCK(pidfs_ino_lock);
|
||||
static u64 pidfs_ino_nr = 1;
|
||||
|
||||
static inline unsigned long pidfs_ino(u64 ino)
|
||||
|
|
@ -659,6 +660,17 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
return open_namespace(ns_common);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
static long pidfd_compat_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
if (cmd == FS_IOC32_GETVERSION)
|
||||
cmd = FS_IOC_GETVERSION;
|
||||
|
||||
return pidfd_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
|
||||
}
|
||||
#endif
|
||||
|
||||
static int pidfs_file_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct pid *pid = inode->i_private;
|
||||
|
|
@ -686,7 +698,9 @@ static const struct file_operations pidfs_file_operations = {
|
|||
.show_fdinfo = pidfd_show_fdinfo,
|
||||
#endif
|
||||
.unlocked_ioctl = pidfd_ioctl,
|
||||
.compat_ioctl = compat_ptr_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = pidfd_compat_ioctl,
|
||||
#endif
|
||||
};
|
||||
|
||||
struct pid *pidfd_pid(const struct file *file)
|
||||
|
|
@ -915,6 +929,20 @@ static struct dentry *pidfs_fh_to_dentry(struct super_block *sb,
|
|||
return path.dentry;
|
||||
}
|
||||
|
||||
static struct file *pidfs_dentry_open(const struct path *path,
|
||||
unsigned int flags,
|
||||
const struct cred *cred)
|
||||
{
|
||||
struct file *file;
|
||||
|
||||
/* pidfds are always O_RDWR. */
|
||||
file = dentry_open(path, flags | O_RDWR, cred);
|
||||
/* do_dentry_open() strips O_EXCL and O_TRUNC. */
|
||||
if (!IS_ERR(file))
|
||||
file->f_flags |= flags & (PIDFD_THREAD | PIDFD_AUTOKILL);
|
||||
return file;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure that we reject any nonsensical flags that users pass via
|
||||
* open_by_handle_at(). Note that PIDFD_THREAD is defined as O_EXCL, and
|
||||
|
|
@ -940,11 +968,13 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx,
|
|||
static struct file *pidfs_export_open(const struct path *path, unsigned int oflags)
|
||||
{
|
||||
/*
|
||||
* Clear O_LARGEFILE as open_by_handle_at() forces it and raise
|
||||
* O_RDWR as pidfds always are.
|
||||
* Opening via file handle may never raise PIDFD_AUTOKILL. That can
|
||||
* only be done at task creation!
|
||||
*/
|
||||
oflags &= ~O_LARGEFILE;
|
||||
return dentry_open(path, oflags | O_RDWR, current_cred());
|
||||
if (WARN_ON_ONCE(oflags & PIDFD_AUTOKILL))
|
||||
return ERR_PTR(-EINVAL);
|
||||
/* Clear O_LARGEFILE as open_by_handle_at() forces it. */
|
||||
return pidfs_dentry_open(path, oflags & ~O_LARGEFILE, current_cred());
|
||||
}
|
||||
|
||||
static const struct export_operations pidfs_export_operations = {
|
||||
|
|
@ -1108,7 +1138,6 @@ static struct file_system_type pidfs_type = {
|
|||
|
||||
struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
|
||||
{
|
||||
struct file *pidfd_file;
|
||||
struct path path __free(path_put) = {};
|
||||
int ret;
|
||||
|
||||
|
|
@ -1126,16 +1155,7 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
|
|||
VFS_WARN_ON_ONCE(!pid->attr);
|
||||
|
||||
flags &= ~PIDFD_STALE;
|
||||
flags |= O_RDWR;
|
||||
pidfd_file = dentry_open(&path, flags, current_cred());
|
||||
/*
|
||||
* Raise PIDFD_THREAD and PIDFD_AUTOKILL explicitly as
|
||||
* do_dentry_open() strips O_EXCL and O_TRUNC.
|
||||
*/
|
||||
if (!IS_ERR(pidfd_file))
|
||||
pidfd_file->f_flags |= (flags & (PIDFD_THREAD | PIDFD_AUTOKILL));
|
||||
|
||||
return pidfd_file;
|
||||
return pidfs_dentry_open(&path, flags, current_cred());
|
||||
}
|
||||
|
||||
void __init pidfs_init(void)
|
||||
|
|
|
|||
|
|
@ -93,6 +93,13 @@ static void __forget_cached_acl(struct posix_acl **p)
|
|||
{
|
||||
struct posix_acl *old;
|
||||
|
||||
/*
|
||||
* ACL_DONT_CACHE is expected to be a "const" value and xchg it with
|
||||
* ACL_NOT_CACHED would enable acl caching for the inode -
|
||||
* clearly not what the caller has intended.
|
||||
*/
|
||||
if (READ_ONCE(*p) == ACL_DONT_CACHE)
|
||||
return;
|
||||
old = xchg(p, ACL_NOT_CACHED);
|
||||
if (!is_uncached_acl(old))
|
||||
posix_acl_release(old);
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ static const char *proc_ns_get_link(struct dentry *dentry,
|
|||
const struct proc_ns_operations *ns_ops = PROC_I(inode)->ns_ops;
|
||||
struct task_struct *task;
|
||||
struct path ns_path;
|
||||
int error = -EACCES;
|
||||
int error;
|
||||
|
||||
if (!dentry)
|
||||
return ERR_PTR(-ECHILD);
|
||||
|
|
@ -59,6 +59,7 @@ static const char *proc_ns_get_link(struct dentry *dentry,
|
|||
if (error)
|
||||
goto out_put_task;
|
||||
|
||||
error = -EACCES;
|
||||
if (!ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS))
|
||||
goto out;
|
||||
|
||||
|
|
@ -90,6 +91,7 @@ static int proc_ns_readlink(struct dentry *dentry, char __user *buffer, int bufl
|
|||
if (res)
|
||||
goto out_put_task;
|
||||
|
||||
res = -EACCES;
|
||||
if (ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS)) {
|
||||
res = ns_get_name(name, sizeof(name), task, ns_ops);
|
||||
if (res >= 0)
|
||||
|
|
|
|||
34
fs/super.c
34
fs/super.c
|
|
@ -1082,18 +1082,35 @@ void emergency_remount(void)
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool get_active_super(struct super_block *sb)
|
||||
{
|
||||
bool active = false;
|
||||
|
||||
if (super_lock_excl(sb)) {
|
||||
active = atomic_inc_not_zero(&sb->s_active);
|
||||
super_unlock_excl(sb);
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
static void do_thaw_all_callback(struct super_block *sb, void *unused)
|
||||
{
|
||||
if (!get_active_super(sb))
|
||||
return;
|
||||
|
||||
/* fs_bdev_thaw() acquires s_umount so it must not be held here */
|
||||
if (IS_ENABLED(CONFIG_BLOCK))
|
||||
while (sb->s_bdev && !bdev_thaw(sb->s_bdev))
|
||||
pr_warn("Emergency Thaw on %pg\n", sb->s_bdev);
|
||||
thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE, NULL);
|
||||
return;
|
||||
|
||||
if (super_lock_excl(sb))
|
||||
thaw_super_locked(sb, FREEZE_HOLDER_USERSPACE, NULL);
|
||||
deactivate_super(sb);
|
||||
}
|
||||
|
||||
static void do_thaw_all(struct work_struct *work)
|
||||
{
|
||||
__iterate_supers(do_thaw_all_callback, NULL, SUPER_ITER_EXCL);
|
||||
__iterate_supers(do_thaw_all_callback, NULL, SUPER_ITER_UNLOCKED);
|
||||
kfree(work);
|
||||
printk(KERN_WARNING "Emergency Thaw complete\n");
|
||||
}
|
||||
|
|
@ -1114,17 +1131,6 @@ void emergency_thaw_all(void)
|
|||
}
|
||||
}
|
||||
|
||||
static inline bool get_active_super(struct super_block *sb)
|
||||
{
|
||||
bool active = false;
|
||||
|
||||
if (super_lock_excl(sb)) {
|
||||
active = atomic_inc_not_zero(&sb->s_active);
|
||||
super_unlock_excl(sb);
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
static const char *filesystems_freeze_ptr = "filesystems_freeze";
|
||||
|
||||
static void filesystems_freeze_callback(struct super_block *sb, void *freeze_all_ptr)
|
||||
|
|
|
|||
|
|
@ -520,6 +520,22 @@ static inline bool mem_cgroup_is_root(struct mem_cgroup *memcg)
|
|||
return (memcg == root_mem_cgroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* mem_cgroup_shrink_is_root - is this a global or root-memcg shrink invocation?
|
||||
* @sc: shrink_control describing the current shrinker call
|
||||
*
|
||||
* Returns true when @sc represents a global reclaim shrink (sc->memcg == NULL)
|
||||
* or a root-memcg shrink, i.e. not a per-memcg iteration of
|
||||
* shrink_slab_memcg(). Filesystems whose ->nr_cached_objects()/
|
||||
* ->free_cached_objects() implementations operate on filesystem-global state
|
||||
* and do not honour sc->memcg can use this to early-return 0 in per-memcg
|
||||
* contexts.
|
||||
*/
|
||||
static inline bool mem_cgroup_shrink_is_root(struct shrink_control *sc)
|
||||
{
|
||||
return !sc->memcg || mem_cgroup_is_root(sc->memcg);
|
||||
}
|
||||
|
||||
static inline bool obj_cgroup_is_root(const struct obj_cgroup *objcg)
|
||||
{
|
||||
return objcg->is_root;
|
||||
|
|
@ -1071,6 +1087,11 @@ static inline bool mem_cgroup_is_root(struct mem_cgroup *memcg)
|
|||
return true;
|
||||
}
|
||||
|
||||
static inline bool mem_cgroup_shrink_is_root(struct shrink_control *sc)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool obj_cgroup_is_root(const struct obj_cgroup *objcg)
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
10
mm/shmem.c
10
mm/shmem.c
|
|
@ -846,6 +846,16 @@ static long shmem_unused_huge_count(struct super_block *sb,
|
|||
struct shrink_control *sc)
|
||||
{
|
||||
struct shmem_sb_info *sbinfo = SHMEM_SB(sb);
|
||||
|
||||
/*
|
||||
* The per-superblock shrinklist is filesystem-global and does not
|
||||
* honour sc->memcg, so it is only meaningful on the global (kswapd or
|
||||
* root direct reclaim) shrink path. Skip the per-memcg iterations of
|
||||
* shrink_slab_memcg() to avoid queueing duplicate global work.
|
||||
*/
|
||||
if (!mem_cgroup_shrink_is_root(sc))
|
||||
return 0;
|
||||
|
||||
return READ_ONCE(sbinfo->shrinklist_len);
|
||||
}
|
||||
#else /* !CONFIG_TRANSPARENT_HUGEPAGE */
|
||||
|
|
|
|||
|
|
@ -5,6 +5,13 @@ CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
|
|||
TEST_GEN_PROGS := fusectl_test
|
||||
TEST_GEN_FILES := fuse_mnt
|
||||
|
||||
# fuse_acl_cache_test requires libfuse3; add it only when the library is present.
|
||||
ACL_CFLAGS := $(shell pkg-config fuse3 --cflags 2>/dev/null)
|
||||
ACL_LDLIBS := $(shell pkg-config fuse3 --libs 2>/dev/null)
|
||||
ifneq ($(ACL_CFLAGS),)
|
||||
TEST_GEN_PROGS += fuse_acl_cache_test
|
||||
endif
|
||||
|
||||
include ../../lib.mk
|
||||
|
||||
VAR_CFLAGS := $(shell pkg-config fuse --cflags 2>/dev/null)
|
||||
|
|
@ -19,3 +26,6 @@ endif
|
|||
|
||||
$(OUTPUT)/fuse_mnt: CFLAGS += $(VAR_CFLAGS)
|
||||
$(OUTPUT)/fuse_mnt: LDLIBS += $(VAR_LDLIBS)
|
||||
|
||||
$(OUTPUT)/fuse_acl_cache_test: CFLAGS += $(ACL_CFLAGS)
|
||||
$(OUTPUT)/fuse_acl_cache_test: LDLIBS += $(ACL_LDLIBS)
|
||||
|
|
|
|||
347
tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
Normal file
347
tools/testing/selftests/filesystems/fuse/fuse_acl_cache_test.c
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test: FUSE ACL caching bug triggered by AT_STATX_FORCE_SYNC
|
||||
*
|
||||
* A FUSE mount that does not negotiate FUSE_POSIX_ACL initialises every inode
|
||||
* with i_acl = i_default_acl = ACL_DONT_CACHE. When a fresh stat is needed
|
||||
* (e.g. AT_STATX_FORCE_SYNC), fuse_update_get_attr() calls
|
||||
* forget_all_cached_acls() before issuing FUSE_GETATTR. On an unfixed kernel,
|
||||
* __forget_cached_acl() replaces ACL_DONT_CACHE with ACL_NOT_CACHED,
|
||||
* inadvertently enabling the kernel ACL cache for that inode. The next
|
||||
* getxattr populates the cache. Because fuse_set_acl() skips
|
||||
* forget_all_cached_acls() for !fc->posix_acl mounts, any subsequent change to
|
||||
* the ACL leaves the stale kernel entry in place, and the next getxattr returns
|
||||
* wrong data without ever reaching the FUSE daemon.
|
||||
*
|
||||
* Fix (fs/posix_acl.c): __forget_cached_acl() returns early when *p is
|
||||
* ACL_DONT_CACHE, preserving the "never cache" invariant for the inode's
|
||||
* lifetime.
|
||||
*
|
||||
* Test outline:
|
||||
* 1. Mount a minimal FUSE fs (no FUSE_POSIX_ACL negotiated).
|
||||
* 2. lgetxattr -> daemon called, ACL_A returned, NOT cached (ACL_DONT_CACHE).
|
||||
* 3. statx(AT_STATX_FORCE_SYNC) -> forget_all_cached_acls() called.
|
||||
* Buggy: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled).
|
||||
* Fixed: ACL_DONT_CACHE preserved.
|
||||
* 4. lgetxattr -> daemon called, ACL_A returned.
|
||||
* Buggy: result now cached (ACL_NOT_CACHED -> cached).
|
||||
* Fixed: result still not cached.
|
||||
* 5. Daemon switches to ACL_B internally (different size).
|
||||
* 6. lgetxattr -> should return ACL_B (44 bytes).
|
||||
* Buggy: cache hit, returns stale ACL_A (28 bytes). FAIL.
|
||||
* Fixed: no cache, daemon called, returns ACL_B (44 bytes). PASS.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define FUSE_USE_VERSION 31
|
||||
#include <fuse_lowlevel.h>
|
||||
|
||||
#include "kselftest_harness.h"
|
||||
|
||||
/* ---- ACL binary encoding ------------------------------------------------ */
|
||||
/*
|
||||
* POSIX ACL v2 xattr format (little-endian):
|
||||
* header: u32 version (= 0x00000002)
|
||||
* entry: u16 tag | u16 perm | u32 id
|
||||
*
|
||||
* Entries must appear in tag-ascending order; named USER/GROUP entries
|
||||
* require a MASK entry. Both ACLs pass posix_acl_from_xattr() validation.
|
||||
*/
|
||||
|
||||
/* ACL_A: 3 entries (USER_OBJ:rwx, GROUP_OBJ:r-x, OTHER:r-x) = 28 bytes */
|
||||
static const uint8_t acl_a[] = {
|
||||
0x02, 0x00, 0x00, 0x00, /* v2 header */
|
||||
0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
|
||||
0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
|
||||
0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
|
||||
};
|
||||
|
||||
/*
|
||||
* ACL_B: 5 entries — adds USER uid=1 and MASK = 44 bytes.
|
||||
* A named USER entry requires a MASK; all tags in ascending order.
|
||||
*/
|
||||
static const uint8_t acl_b[] = {
|
||||
0x02, 0x00, 0x00, 0x00, /* v2 header */
|
||||
0x01, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* USER_OBJ rwx */
|
||||
0x02, 0x00, 0x07, 0x00, 0x01, 0x00, 0x00, 0x00, /* USER uid=1 rwx */
|
||||
0x04, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* GROUP_OBJ r-x */
|
||||
0x10, 0x00, 0x07, 0x00, 0xff, 0xff, 0xff, 0xff, /* MASK rwx */
|
||||
0x20, 0x00, 0x05, 0x00, 0xff, 0xff, 0xff, 0xff, /* OTHER r-x */
|
||||
};
|
||||
|
||||
/* ---- Shared state (daemon thread <-> test thread) ----------------------- */
|
||||
|
||||
#define FILE_INO 2
|
||||
#define FILE_NAME "testfile"
|
||||
|
||||
struct daemon_state {
|
||||
pthread_mutex_t lock;
|
||||
const uint8_t *acl;
|
||||
size_t acl_size;
|
||||
int getxattr_count;
|
||||
};
|
||||
|
||||
/*
|
||||
* Global: callbacks are stateless fns so we use a single global.
|
||||
* Safe because only one test instance runs at a time.
|
||||
*/
|
||||
static struct daemon_state g_ds = {
|
||||
.lock = PTHREAD_MUTEX_INITIALIZER,
|
||||
};
|
||||
|
||||
/* ---- FUSE lowlevel callbacks -------------------------------------------- */
|
||||
|
||||
static void fs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
|
||||
{
|
||||
if (parent != FUSE_ROOT_ID || strcmp(name, FILE_NAME)) {
|
||||
fuse_reply_err(req, ENOENT);
|
||||
return;
|
||||
}
|
||||
struct fuse_entry_param e = {};
|
||||
|
||||
/*
|
||||
* Long attr/entry timeouts so that normal stat() calls do not
|
||||
* expire and trigger forget_all_cached_acls() on their own;
|
||||
* only the explicit AT_STATX_FORCE_SYNC should trigger it.
|
||||
*/
|
||||
e.ino = FILE_INO;
|
||||
e.generation = 1;
|
||||
e.attr_timeout = 10.0;
|
||||
e.entry_timeout = 10.0;
|
||||
e.attr.st_ino = FILE_INO;
|
||||
e.attr.st_mode = S_IFREG | 0644;
|
||||
e.attr.st_nlink = 1;
|
||||
fuse_reply_entry(req, &e);
|
||||
}
|
||||
|
||||
static void fs_getattr(fuse_req_t req, fuse_ino_t ino,
|
||||
struct fuse_file_info *fi)
|
||||
{
|
||||
struct stat st = {};
|
||||
|
||||
(void)fi;
|
||||
if (ino == FUSE_ROOT_ID) {
|
||||
st.st_ino = FUSE_ROOT_ID;
|
||||
st.st_mode = S_IFDIR | 0755;
|
||||
st.st_nlink = 2;
|
||||
} else if (ino == FILE_INO) {
|
||||
st.st_ino = FILE_INO;
|
||||
st.st_mode = S_IFREG | 0644;
|
||||
st.st_nlink = 1;
|
||||
} else {
|
||||
fuse_reply_err(req, ENOENT);
|
||||
return;
|
||||
}
|
||||
fuse_reply_attr(req, &st, 10);
|
||||
}
|
||||
|
||||
static void fs_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
|
||||
size_t size)
|
||||
{
|
||||
if (ino != FILE_INO ||
|
||||
strcmp(name, "system.posix_acl_access") != 0) {
|
||||
fuse_reply_err(req, ENODATA);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_ds.lock);
|
||||
const uint8_t *acl = g_ds.acl;
|
||||
size_t acl_size = g_ds.acl_size;
|
||||
g_ds.getxattr_count++;
|
||||
pthread_mutex_unlock(&g_ds.lock);
|
||||
|
||||
if (size == 0)
|
||||
fuse_reply_xattr(req, acl_size);
|
||||
else if (size < acl_size)
|
||||
fuse_reply_err(req, ERANGE);
|
||||
else
|
||||
fuse_reply_buf(req, (const char *)acl, acl_size);
|
||||
}
|
||||
|
||||
static const struct fuse_lowlevel_ops fs_ops = {
|
||||
.lookup = fs_lookup,
|
||||
.getattr = fs_getattr,
|
||||
.getxattr = fs_getxattr,
|
||||
};
|
||||
|
||||
/* ---- Daemon thread ------------------------------------------------------- */
|
||||
|
||||
static void *run_daemon(void *arg)
|
||||
{
|
||||
fuse_session_loop((struct fuse_session *)arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- kselftest harness --------------------------------------------------- */
|
||||
|
||||
FIXTURE(acl_cache) {
|
||||
struct fuse_session *se;
|
||||
char mountpoint[PATH_MAX];
|
||||
char file_path[PATH_MAX];
|
||||
pthread_t thread;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(acl_cache)
|
||||
{
|
||||
char *fuse_argv[] = { "fuse_acl_cache_test", NULL };
|
||||
struct fuse_args args = FUSE_ARGS_INIT(1, fuse_argv);
|
||||
|
||||
g_ds.acl = acl_a;
|
||||
g_ds.acl_size = sizeof(acl_a);
|
||||
g_ds.getxattr_count = 0;
|
||||
|
||||
strcpy(self->mountpoint, "/tmp/acl_cache_test_XXXXXX");
|
||||
if (!mkdtemp(self->mountpoint))
|
||||
SKIP(return, "mkdtemp: %s", strerror(errno));
|
||||
|
||||
snprintf(self->file_path, sizeof(self->file_path),
|
||||
"%s/" FILE_NAME, self->mountpoint);
|
||||
|
||||
self->se = fuse_session_new(&args, &fs_ops, sizeof(fs_ops), NULL);
|
||||
if (!self->se) {
|
||||
rmdir(self->mountpoint);
|
||||
SKIP(return, "fuse_session_new failed");
|
||||
}
|
||||
|
||||
if (fuse_session_mount(self->se, self->mountpoint)) {
|
||||
fuse_session_destroy(self->se);
|
||||
rmdir(self->mountpoint);
|
||||
SKIP(return, "fuse_session_mount failed "
|
||||
"(missing fusermount3 or insufficient privileges)");
|
||||
}
|
||||
|
||||
if (pthread_create(&self->thread, NULL, run_daemon, self->se)) {
|
||||
fuse_session_unmount(self->se);
|
||||
fuse_session_destroy(self->se);
|
||||
rmdir(self->mountpoint);
|
||||
SKIP(return, "pthread_create: %s", strerror(errno));
|
||||
}
|
||||
|
||||
fuse_opt_free_args(&args);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(acl_cache)
|
||||
{
|
||||
fuse_session_exit(self->se);
|
||||
fuse_session_unmount(self->se);
|
||||
pthread_join(self->thread, NULL);
|
||||
fuse_session_destroy(self->se);
|
||||
rmdir(self->mountpoint);
|
||||
}
|
||||
|
||||
static int do_force_statx(const char *path)
|
||||
{
|
||||
struct statx stx;
|
||||
|
||||
return statx(AT_FDCWD, path, AT_STATX_FORCE_SYNC, STATX_BASIC_STATS,
|
||||
&stx);
|
||||
}
|
||||
|
||||
TEST_F(acl_cache, stale_after_force_sync)
|
||||
{
|
||||
char buf[512];
|
||||
ssize_t sz;
|
||||
int count;
|
||||
|
||||
/*
|
||||
* Step 1: two getxattr calls before any statx(FORCE_SYNC).
|
||||
* i_acl == ACL_DONT_CACHE. __get_acl's cmpxchg(p, ACL_NOT_CACHED,
|
||||
* sentinel) finds *p != ACL_NOT_CACHED on every call, so the sentinel
|
||||
* is never placed and the result is never cached. Both calls must
|
||||
* reach the daemon, proving ACL_DONT_CACHE suppresses caching.
|
||||
*/
|
||||
sz = lgetxattr(self->file_path, "system.posix_acl_access",
|
||||
buf, sizeof(buf));
|
||||
ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
|
||||
|
||||
sz = lgetxattr(self->file_path, "system.posix_acl_access",
|
||||
buf, sizeof(buf));
|
||||
ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
|
||||
|
||||
pthread_mutex_lock(&g_ds.lock);
|
||||
count = g_ds.getxattr_count;
|
||||
pthread_mutex_unlock(&g_ds.lock);
|
||||
|
||||
ASSERT_EQ(count, 2);
|
||||
TH_LOG("step 1 OK: both pre-trigger getxattrs reached daemon (count=%d), "
|
||||
"ACL_DONT_CACHE is working", count);
|
||||
|
||||
/*
|
||||
* Step 2: statx(AT_STATX_FORCE_SYNC).
|
||||
* fuse_update_get_attr() calls forget_all_cached_acls() before sending
|
||||
* FUSE_GETATTR.
|
||||
* Buggy kernel: ACL_DONT_CACHE -> ACL_NOT_CACHED (cache enabled)
|
||||
* Fixed kernel: ACL_DONT_CACHE preserved (no effect)
|
||||
*/
|
||||
ASSERT_EQ(do_force_statx(self->file_path), 0);
|
||||
TH_LOG("step 2 OK: statx(AT_STATX_FORCE_SYNC) succeeded");
|
||||
|
||||
/*
|
||||
* Step 3: getxattr — cache population attempt after the trigger.
|
||||
* Buggy: *p == ACL_NOT_CACHED -> sentinel placed -> fuse_get_inode_acl
|
||||
* called -> ACL_A parsed and stored in the kernel cache.
|
||||
* Fixed: *p == ACL_DONT_CACHE -> sentinel placement skipped ->
|
||||
* fuse_get_inode_acl called but result not cached.
|
||||
* Either way the correct ACL_A is returned here.
|
||||
*/
|
||||
sz = lgetxattr(self->file_path, "system.posix_acl_access",
|
||||
buf, sizeof(buf));
|
||||
ASSERT_EQ(sz, (ssize_t)sizeof(acl_a));
|
||||
|
||||
pthread_mutex_lock(&g_ds.lock);
|
||||
count = g_ds.getxattr_count;
|
||||
pthread_mutex_unlock(&g_ds.lock);
|
||||
|
||||
ASSERT_EQ(count, 3);
|
||||
TH_LOG("step 3 OK: post-trigger getxattr reached daemon (count=%d), "
|
||||
"returned correct ACL_A (%zd bytes)", count, sz);
|
||||
|
||||
/*
|
||||
* Step 4: switch daemon to ACL_B (different size: 44 vs 28 bytes).
|
||||
* Simulates an ACL change that fuse_set_acl() would NOT invalidate for
|
||||
* !fc->posix_acl mounts (it skips forget_all_cached_acls in that case).
|
||||
* On a fixed kernel the ACL was never cached, so this is moot.
|
||||
*/
|
||||
pthread_mutex_lock(&g_ds.lock);
|
||||
g_ds.acl = acl_b;
|
||||
g_ds.acl_size = sizeof(acl_b);
|
||||
pthread_mutex_unlock(&g_ds.lock);
|
||||
TH_LOG("step 4: daemon switched to ACL_B (%zu bytes)", sizeof(acl_b));
|
||||
|
||||
/*
|
||||
* Step 5: getxattr — the decisive check.
|
||||
* Buggy kernel: cache hit -> stale ACL_A (28 bytes), count stays 3.
|
||||
* Fixed kernel: no cache -> daemon called -> ACL_B (44 bytes), count 4.
|
||||
*/
|
||||
sz = lgetxattr(self->file_path, "system.posix_acl_access",
|
||||
buf, sizeof(buf));
|
||||
|
||||
pthread_mutex_lock(&g_ds.lock);
|
||||
count = g_ds.getxattr_count;
|
||||
pthread_mutex_unlock(&g_ds.lock);
|
||||
|
||||
if (sz == (ssize_t)sizeof(acl_a))
|
||||
TH_LOG("step 5 BUG: stale ACL_A (%zd bytes) from kernel cache "
|
||||
"(count=%d); ACL_DONT_CACHE corrupted by "
|
||||
"forget_all_cached_acls()", sz, count);
|
||||
else
|
||||
TH_LOG("step 5 OK: daemon reached (count=%d), "
|
||||
"fresh ACL_B (%zd bytes)", count, sz);
|
||||
|
||||
EXPECT_EQ(sz, (ssize_t)sizeof(acl_b));
|
||||
EXPECT_EQ(count, 4);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
||||
|
|
@ -373,6 +373,7 @@ TEST_F(file_handle, open_by_handle_at_valid_flags)
|
|||
O_CLOEXEC |
|
||||
O_EXCL);
|
||||
ASSERT_GE(pidfd, 0);
|
||||
ASSERT_NE(fcntl(pidfd, F_GETFL) & PIDFD_THREAD, 0);
|
||||
|
||||
ASSERT_EQ(fstat(pidfd, &st2), 0);
|
||||
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user