mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 15:07:32 -04:00
Pull procfs updates from Christian Brauner:
- Revamp fs/filesystems.c
The file was a mess with a hand-rolled linked list in desperate need
of a cleanup. The filesystems list is now RCU-ified, /proc files can
be marked permanent from outside fs/proc/, and the string emitted
when reading /proc/filesystems is pre-generated and cached instead of
pointer-chasing and printfing entry by entry on every read.
The file is read frequently because libselinux reads it and is linked
into numerous frequently used programs (even ones you would not
suspect, like sed!). Scalability also improves since reference
maintenance on open/close is bypassed.
open+read+close cycle single-threaded (ops/s):
before: 442732
after: 1063462 (+140%)
open+read+close cycle with 20 processes (ops/s):
before: 606177
after: 3300576 (+444%)
A follow-up patch adds missing unlocks in some corner cases and
tidies things up.
- Relax the mount visibility check for subset=pid mounts
When procfs is mounted with subset=pid, all static files become
unavailable and only the dynamic pid information is accessible. In
that case there is no point in imposing the full mount visibility
restrictions on the mounter - everything that can be hidden in procfs
is already inaccessible. These restrictions prevented procfs from
being mounted inside rootless containers since almost all container
implementations overmount parts of procfs to hide certain
directories.
As part of this /proc/self/net is only shown in subset=pid mounts for
CAP_NET_ADMIN, reconfiguring subset=pid is rejected, the
SB_I_USERNS_VISIBLE superblock flag is replaced with an
FS_USERNS_MOUNT_RESTRICTED filesystem flag, fully visible mounts are
recorded in a list, and the mount restrictions are finally
documented.
- Protect ptrace_may_access() with exec_update_lock in procfs
Most uses of ptrace_may_access() in procfs should hold
exec_update_lock to avoid TOCTOU issues with concurrent privileged
execve() (like setuid binary execution).
This fixes the easy cases - the owner and visibility checks and the
FD link permission checks - with the gnarlier ones to follow later.
* tag 'vfs-7.2-rc1.procfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
fs: fix ups and tidy ups to /proc/filesystems caching
proc: protect ptrace_may_access() with exec_update_lock (FD links)
proc: protect ptrace_may_access() with exec_update_lock (part 1)
docs: proc: add documentation about mount restrictions
proc: handle subset=pid separately in userns visibility checks
proc: prevent reconfiguring subset=pid
proc: subset=pid: Show /proc/self/net only for CAP_NET_ADMIN
fs: cache the string generated by reading /proc/filesystems
sysfs: remove trivial sysfs_get_tree() wrapper
fs: RCU-ify filesystems list
fs: move SB_I_USERNS_VISIBLE to FS_USERNS_MOUNT_RESTRICTED
proc: allow to mark /proc files permanent outside of fs/proc/
namespace: record fully visible mounts in list
279 lines
8.1 KiB
C
279 lines
8.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
#include <linux/mount.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/ns_common.h>
|
|
#include <linux/fs_pin.h>
|
|
|
|
extern struct file_system_type nullfs_fs_type;
|
|
extern struct list_head notify_list;
|
|
|
|
struct mnt_namespace {
|
|
struct ns_common ns;
|
|
struct mount * root;
|
|
struct {
|
|
struct rb_root mounts; /* Protected by namespace_sem */
|
|
struct rb_node *mnt_last_node; /* last (rightmost) mount in the rbtree */
|
|
struct rb_node *mnt_first_node; /* first (leftmost) mount in the rbtree */
|
|
};
|
|
struct user_namespace *user_ns;
|
|
struct ucounts *ucounts;
|
|
wait_queue_head_t poll;
|
|
u64 seq_origin; /* Sequence number of origin mount namespace */
|
|
u64 event;
|
|
#ifdef CONFIG_FSNOTIFY
|
|
__u32 n_fsnotify_mask;
|
|
struct fsnotify_mark_connector __rcu *n_fsnotify_marks;
|
|
#endif
|
|
struct hlist_head mnt_visible_mounts; /* SB_I_USERNS_VISIBLE mounts */
|
|
unsigned int nr_mounts; /* # of mounts in the namespace */
|
|
unsigned int pending_mounts;
|
|
refcount_t passive; /* number references not pinning @mounts */
|
|
bool is_anon;
|
|
} __randomize_layout;
|
|
|
|
struct mnt_pcp {
|
|
int mnt_count;
|
|
int mnt_writers;
|
|
};
|
|
|
|
struct mountpoint {
|
|
struct hlist_node m_hash;
|
|
struct dentry *m_dentry;
|
|
struct hlist_head m_list;
|
|
};
|
|
|
|
struct mount {
|
|
struct hlist_node mnt_hash;
|
|
struct mount *mnt_parent;
|
|
struct dentry *mnt_mountpoint;
|
|
struct vfsmount mnt;
|
|
union {
|
|
struct rb_node mnt_node; /* node in the ns->mounts rbtree */
|
|
struct rcu_head mnt_rcu;
|
|
struct llist_node mnt_llist;
|
|
};
|
|
#ifdef CONFIG_SMP
|
|
struct mnt_pcp __percpu *mnt_pcp;
|
|
#else
|
|
int mnt_count;
|
|
int mnt_writers;
|
|
#endif
|
|
struct list_head mnt_mounts; /* list of children, anchored here */
|
|
struct list_head mnt_child; /* and going through their mnt_child */
|
|
struct mount *mnt_next_for_sb; /* the next two fields are hlist_node, */
|
|
struct mount * __aligned(1) *mnt_pprev_for_sb;
|
|
/* except that LSB of pprev is stolen */
|
|
#define WRITE_HOLD 1 /* ... for use by mnt_hold_writers() */
|
|
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
|
|
struct list_head mnt_list;
|
|
struct list_head mnt_expire; /* link in fs-specific expiry list */
|
|
struct list_head mnt_share; /* circular list of shared mounts */
|
|
struct hlist_head mnt_slave_list;/* list of slave mounts */
|
|
struct hlist_node mnt_slave; /* slave list entry */
|
|
struct mount *mnt_master; /* slave is on master->mnt_slave_list */
|
|
/*
|
|
* Containing namespace (active or deactivating, non-refcounted).
|
|
* Normally protected by namespace_sem.
|
|
* Can also be accessed locklessly under RCU. RCU readers can't rely on
|
|
* the namespace still being active, but implicitly hold a passive
|
|
* reference (because an RCU delay happens between a namespace being
|
|
* deactivated and the corresponding passive refcount drop).
|
|
*/
|
|
struct mnt_namespace *mnt_ns;
|
|
struct mountpoint *mnt_mp; /* where is it mounted */
|
|
union {
|
|
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
|
|
struct hlist_node mnt_umount;
|
|
};
|
|
#ifdef CONFIG_FSNOTIFY
|
|
struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
|
|
__u32 mnt_fsnotify_mask;
|
|
struct list_head to_notify; /* need to queue notification */
|
|
struct mnt_namespace *prev_ns; /* previous namespace (NULL if none) */
|
|
#endif
|
|
int mnt_t_flags; /* namespace_sem-protected flags */
|
|
int mnt_id; /* mount identifier, reused */
|
|
u64 mnt_id_unique; /* mount ID unique until reboot */
|
|
int mnt_group_id; /* peer group identifier */
|
|
int mnt_expiry_mark; /* true if marked for expiry */
|
|
struct hlist_head mnt_pins;
|
|
struct hlist_head mnt_stuck_children;
|
|
struct hlist_node mnt_ns_visible; /* link in ns->mnt_visible_mounts */
|
|
struct mount *overmount; /* mounted on ->mnt_root */
|
|
} __randomize_layout;
|
|
|
|
enum {
|
|
T_SHARED = 1, /* mount is shared */
|
|
T_UNBINDABLE = 2, /* mount is unbindable */
|
|
T_MARKED = 4, /* internal mark for propagate_... */
|
|
T_UMOUNT_CANDIDATE = 8, /* for propagate_umount */
|
|
|
|
/*
|
|
* T_SHARED_MASK is the set of flags that should be cleared when a
|
|
* mount becomes shared. Currently, this is only the flag that says a
|
|
* mount cannot be bind mounted, since this is how we create a mount
|
|
* that shares events with another mount. If you add a new T_*
|
|
* flag, consider how it interacts with shared mounts.
|
|
*/
|
|
T_SHARED_MASK = T_UNBINDABLE,
|
|
};
|
|
|
|
#define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
|
|
|
|
static inline struct mount *real_mount(struct vfsmount *mnt)
|
|
{
|
|
return container_of(mnt, struct mount, mnt);
|
|
}
|
|
|
|
static inline int mnt_has_parent(const struct mount *mnt)
|
|
{
|
|
return mnt != mnt->mnt_parent;
|
|
}
|
|
|
|
static inline int is_mounted(struct vfsmount *mnt)
|
|
{
|
|
/* neither detached nor internal? */
|
|
return !IS_ERR_OR_NULL(real_mount(mnt)->mnt_ns);
|
|
}
|
|
|
|
extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
|
|
|
|
extern int __legitimize_mnt(struct vfsmount *, unsigned);
|
|
|
|
static inline bool __path_is_mountpoint(const struct path *path)
|
|
{
|
|
struct mount *m = __lookup_mnt(path->mnt, path->dentry);
|
|
return m && likely(!(m->mnt.mnt_flags & MNT_SYNC_UMOUNT));
|
|
}
|
|
|
|
extern void __detach_mounts(struct dentry *dentry);
|
|
|
|
static inline void detach_mounts(struct dentry *dentry)
|
|
{
|
|
if (!d_mountpoint(dentry))
|
|
return;
|
|
__detach_mounts(dentry);
|
|
}
|
|
|
|
static inline void get_mnt_ns(struct mnt_namespace *ns)
|
|
{
|
|
ns_ref_inc(ns);
|
|
}
|
|
|
|
extern seqlock_t mount_lock;
|
|
|
|
DEFINE_LOCK_GUARD_0(mount_writer, write_seqlock(&mount_lock),
|
|
write_sequnlock(&mount_lock))
|
|
DEFINE_LOCK_GUARD_0(mount_locked_reader, read_seqlock_excl(&mount_lock),
|
|
read_sequnlock_excl(&mount_lock))
|
|
|
|
struct proc_mounts {
|
|
struct mnt_namespace *ns;
|
|
struct path root;
|
|
int (*show)(struct seq_file *, struct vfsmount *);
|
|
};
|
|
|
|
extern const struct seq_operations mounts_op;
|
|
|
|
extern bool __is_local_mountpoint(const struct dentry *dentry);
|
|
static inline bool is_local_mountpoint(const struct dentry *dentry)
|
|
{
|
|
if (!d_mountpoint(dentry))
|
|
return false;
|
|
|
|
return __is_local_mountpoint(dentry);
|
|
}
|
|
|
|
static inline bool is_anon_ns(struct mnt_namespace *ns)
|
|
{
|
|
return ns->is_anon;
|
|
}
|
|
|
|
static inline bool anon_ns_root(const struct mount *m)
|
|
{
|
|
struct mnt_namespace *ns = READ_ONCE(m->mnt_ns);
|
|
|
|
return !IS_ERR_OR_NULL(ns) && is_anon_ns(ns) && m == ns->root;
|
|
}
|
|
|
|
static inline bool mnt_ns_attached(const struct mount *mnt)
|
|
{
|
|
return !RB_EMPTY_NODE(&mnt->mnt_node);
|
|
}
|
|
|
|
static inline bool mnt_ns_empty(const struct mnt_namespace *ns)
|
|
{
|
|
return RB_EMPTY_ROOT(&ns->mounts);
|
|
}
|
|
|
|
static inline void move_from_ns(struct mount *mnt)
|
|
{
|
|
struct mnt_namespace *ns = mnt->mnt_ns;
|
|
WARN_ON(!mnt_ns_attached(mnt));
|
|
if (ns->mnt_last_node == &mnt->mnt_node)
|
|
ns->mnt_last_node = rb_prev(&mnt->mnt_node);
|
|
if (ns->mnt_first_node == &mnt->mnt_node)
|
|
ns->mnt_first_node = rb_next(&mnt->mnt_node);
|
|
rb_erase(&mnt->mnt_node, &ns->mounts);
|
|
RB_CLEAR_NODE(&mnt->mnt_node);
|
|
if (!hlist_unhashed(&mnt->mnt_ns_visible))
|
|
hlist_del_init(&mnt->mnt_ns_visible);
|
|
}
|
|
|
|
bool has_locked_children(struct mount *mnt, struct dentry *dentry);
|
|
struct mnt_namespace *get_sequential_mnt_ns(struct mnt_namespace *mnt_ns,
|
|
bool previous);
|
|
|
|
static inline struct mnt_namespace *to_mnt_ns(struct ns_common *ns)
|
|
{
|
|
return container_of(ns, struct mnt_namespace, ns);
|
|
}
|
|
|
|
#ifdef CONFIG_FSNOTIFY
|
|
static inline void mnt_notify_add(struct mount *m)
|
|
{
|
|
/* Optimize the case where there are no watches */
|
|
if ((m->mnt_ns && m->mnt_ns->n_fsnotify_marks) ||
|
|
(m->prev_ns && m->prev_ns->n_fsnotify_marks))
|
|
list_add_tail(&m->to_notify, ¬ify_list);
|
|
else
|
|
m->prev_ns = m->mnt_ns;
|
|
}
|
|
#else
|
|
static inline void mnt_notify_add(struct mount *m)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static inline struct mount *topmost_overmount(struct mount *m)
|
|
{
|
|
while (m->overmount)
|
|
m = m->overmount;
|
|
return m;
|
|
}
|
|
|
|
static inline bool __test_write_hold(struct mount * __aligned(1) *val)
|
|
{
|
|
return (unsigned long)val & WRITE_HOLD;
|
|
}
|
|
|
|
static inline bool test_write_hold(const struct mount *m)
|
|
{
|
|
return __test_write_hold(m->mnt_pprev_for_sb);
|
|
}
|
|
|
|
static inline void set_write_hold(struct mount *m)
|
|
{
|
|
m->mnt_pprev_for_sb = (void *)((unsigned long)m->mnt_pprev_for_sb
|
|
| WRITE_HOLD);
|
|
}
|
|
|
|
static inline void clear_write_hold(struct mount *m)
|
|
{
|
|
m->mnt_pprev_for_sb = (void *)((unsigned long)m->mnt_pprev_for_sb
|
|
& ~WRITE_HOLD);
|
|
}
|
|
|
|
struct mnt_namespace *mnt_ns_from_dentry(struct dentry *dentry);
|