Re: [PATCH v8 07/11] proc: flush task dcache entries from all procfs instances
From: Alexey Gladkov
Date: Wed Feb 12 2020 - 12:08:45 EST
On Wed, Feb 12, 2020 at 08:59:29AM -0600, Eric W. Biederman wrote:
> Alexey Gladkov <gladkov.alexey@xxxxxxxxx> writes:
>
> > On Mon, Feb 10, 2020 at 07:36:08PM -0600, Eric W. Biederman wrote:
> >> Alexey Gladkov <gladkov.alexey@xxxxxxxxx> writes:
> >>
> >> > This allows to flush dcache entries of a task on multiple procfs mounts
> >> > per pid namespace.
> >> >
> >> > The RCU lock is used because the number of reads at the task exit time
> >> > is much larger than the number of procfs mounts.
> >>
> >> A couple of quick comments.
> >>
> >> > Cc: Kees Cook <keescook@xxxxxxxxxxxx>
> >> > Cc: Andy Lutomirski <luto@xxxxxxxxxx>
> >> > Signed-off-by: Djalal Harouni <tixxdz@xxxxxxxxx>
> >> > Suggested-by: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx>
> >> > Signed-off-by: Alexey Gladkov <gladkov.alexey@xxxxxxxxx>
> >> > ---
> >> > fs/proc/base.c | 20 +++++++++++++++-----
> >> > fs/proc/root.c | 27 ++++++++++++++++++++++++++-
> >> > include/linux/pid_namespace.h | 2 ++
> >> > include/linux/proc_fs.h | 2 ++
> >> > 4 files changed, 45 insertions(+), 6 deletions(-)
> >> >
> >> > diff --git a/fs/proc/base.c b/fs/proc/base.c
> >> > index 4ccb280a3e79..24b7c620ded3 100644
> >> > --- a/fs/proc/base.c
> >> > +++ b/fs/proc/base.c
> >> > @@ -3133,7 +3133,7 @@ static const struct inode_operations proc_tgid_base_inode_operations = {
> >> > .permission = proc_pid_permission,
> >> > };
> >> >
> >> > -static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)
> >> > +static void proc_flush_task_mnt_root(struct dentry *mnt_root, pid_t pid, pid_t tgid)
> >> Perhaps just rename things like:
> >> > +static void proc_flush_task_root(struct dentry *root, pid_t pid, pid_t tgid)
> >> > {
> >>
> >> I don't think the mnt_ prefix conveys any information, and it certainly
> >> makes everything longer and more cumbersome.
> >>
> >> > struct dentry *dentry, *leader, *dir;
> >> > char buf[10 + 1];
> >> > @@ -3142,7 +3142,7 @@ static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)
> >> > name.name = buf;
> >> > name.len = snprintf(buf, sizeof(buf), "%u", pid);
> >> > /* no ->d_hash() rejects on procfs */
> >> > - dentry = d_hash_and_lookup(mnt->mnt_root, &name);
> >> > + dentry = d_hash_and_lookup(mnt_root, &name);
> >> > if (dentry) {
> >> > d_invalidate(dentry);
> >> > dput(dentry);
> >> > @@ -3153,7 +3153,7 @@ static void proc_flush_task_mnt(struct vfsmount *mnt, pid_t pid, pid_t tgid)
> >> >
> >> > name.name = buf;
> >> > name.len = snprintf(buf, sizeof(buf), "%u", tgid);
> >> > - leader = d_hash_and_lookup(mnt->mnt_root, &name);
> >> > + leader = d_hash_and_lookup(mnt_root, &name);
> >> > if (!leader)
> >> > goto out;
> >> >
> >> > @@ -3208,14 +3208,24 @@ void proc_flush_task(struct task_struct *task)
> >> > int i;
> >> > struct pid *pid, *tgid;
> >> > struct upid *upid;
> >> > + struct dentry *mnt_root;
> >> > + struct proc_fs_info *fs_info;
> >> >
> >> > pid = task_pid(task);
> >> > tgid = task_tgid(task);
> >> >
> >> > for (i = 0; i <= pid->level; i++) {
> >> > upid = &pid->numbers[i];
> >> > - proc_flush_task_mnt(upid->ns->proc_mnt, upid->nr,
> >> > - tgid->numbers[i].nr);
> >> > +
> >> > + rcu_read_lock();
> >> > + list_for_each_entry_rcu(fs_info, &upid->ns->proc_mounts, pidns_entry) {
> >> > + mnt_root = fs_info->m_super->s_root;
> >> > + proc_flush_task_mnt_root(mnt_root, upid->nr, tgid->numbers[i].nr);
> >> > + }
> >> > + rcu_read_unlock();
> >> > +
> >> > + mnt_root = upid->ns->proc_mnt->mnt_root;
> >> > + proc_flush_task_mnt_root(mnt_root, upid->nr, tgid->numbers[i].nr);
> >>
> >> I don't think this following of proc_mnt is needed. It certainly
> >> shouldn't be. The loop through all of the super blocks should be
> >> enough.
> >
> > Yes, thanks!
> >
> >> Once this change goes through. UML can be given it's own dedicated
> >> proc_mnt for the initial pid namespace, and proc_mnt can be removed
> >> entirely.
> >
> > After you deleted the old sysctl syscall we could probably do it.
> >
> >> Unless something has changed recently UML is the only other user of
> >> pid_ns->proc_mnt. That proc_mnt really only exists to make the loop in
> >> proc_flush_task easy to write.
> >
> > Now I think, is there any way to get rid of proc_mounts or even
> > proc_flush_task somehow.
> >
> >> It also probably makes sense to take the rcu_read_lock() over
> >> that entire for loop.
> >
> > Al Viro pointed out to me that I cannot use rcu locks here :(
>
> Fundamentally proc_flush_task is an optimization. Just getting rid of
> dentries earlier. At least at one point it was an important
> optimization because the old process dentries would just sit around
> doing nothing for anyone.
>
> I wonder if instead of invalidating specific dentries we could instead
> fire wake up a shrinker and point it at one or more instances of proc.
>
> The practical challenge I see is something might need to access the
> dentries to see that they are invalid.
>
> We definitely could try without this optimization and see what happens.
When Linus said that a semaphore for proc_mounts is a bad idea, I tried
to come up with some kind of asynchronous way to clear it per superblock.
I gave up with the asynchronous GC because userspace can quite easily get
ahead of it.
Without this optimization the kernel starts to consume a lot of memory
during intensive reading /proc. I tried to do:
while :; do
for x in `seq 0 9`; do sleep 0.1; done;
ls /proc/[0-9]*;
done >/dev/null;
and memory consumption went up without proc_flush_task. Since we have
mounted procfs in each container, this is dangerous.
--
Rgrds, legion