perf/core: Fix bad use of igrab()
authorSong Liu <songliubraving@fb.com>
Wed, 18 Apr 2018 06:29:07 +0000 (23:29 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 18 Nov 2020 17:26:29 +0000 (18:26 +0100)
commit 9511bce9fe8e5e6c0f923c09243a713eba560141 upstream

As Miklos reported and suggested:

 "This pattern repeats two times in trace_uprobe.c and in
  kernel/events/core.c as well:

      ret = kern_path(filename, LOOKUP_FOLLOW, &path);
      if (ret)
          goto fail_address_parse;

      inode = igrab(d_inode(path.dentry));
      path_put(&path);

  And it's wrong.  You can only hold a reference to the inode if you
  have an active ref to the superblock as well (which is normally
  through path.mnt) or holding s_umount.

  This way unmounting the containing filesystem while the tracepoint is
  active will give you the "VFS: Busy inodes after unmount..." message
  and a crash when the inode is finally put.

  Solution: store path instead of inode."

This patch fixes the issue in kernel/event/core.c.

Reviewed-and-tested-by: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Reported-by: Miklos Szeredi <miklos@szeredi.hu>
Signed-off-by: Song Liu <songliubraving@fb.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Cc: <kernel-team@fb.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Stephane Eranian <eranian@google.com>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Vince Weaver <vincent.weaver@maine.edu>
Fixes: 375637bc5249 ("perf/core: Introduce address range filtering")
Link: http://lkml.kernel.org/r/20180418062907.3210386-2-songliubraving@fb.com
Signed-off-by: Ingo Molnar <mingo@kernel.org>
[sudip: Backported to 4.9: use file_inode()]
Signed-off-by: Sudip Mukherjee <sudipm.mukherjee@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
arch/x86/events/intel/pt.c
include/linux/perf_event.h
kernel/events/core.c

index df60b58691e7c29b395c3603f696e056ddfa6ab2..1808c57ce1614658bc392deaff2079f46a475ff2 100644 (file)
@@ -1117,7 +1117,7 @@ static int pt_event_addr_filters_validate(struct list_head *filters)
                if (!filter->range || !filter->size)
                        return -EOPNOTSUPP;
 
-               if (!filter->inode) {
+               if (!filter->path.dentry) {
                        if (!valid_kernel_ip(filter->offset))
                                return -EINVAL;
 
@@ -1144,7 +1144,7 @@ static void pt_event_addr_filters_sync(struct perf_event *event)
                return;
 
        list_for_each_entry(filter, &head->list, entry) {
-               if (filter->inode && !offs[range]) {
+               if (filter->path.dentry && !offs[range]) {
                        msr_a = msr_b = 0;
                } else {
                        /* apply the offset */
index ae8ecf8210192e6b1e8d7611d392cd994819602b..a7057b772612950205a5f2f14027f465cac2e320 100644 (file)
@@ -475,7 +475,7 @@ struct pmu {
  */
 struct perf_addr_filter {
        struct list_head        entry;
-       struct inode            *inode;
+       struct path             path;
        unsigned long           offset;
        unsigned long           size;
        unsigned int            range   : 1,
index 7aad4d22b42239b9ec2a598c208a3a6732e67aaa..78e4062b8e1bb523ed1370cac2f01f21d7259ec7 100644 (file)
@@ -6271,7 +6271,7 @@ static void perf_event_addr_filters_exec(struct perf_event *event, void *data)
 
        raw_spin_lock_irqsave(&ifh->lock, flags);
        list_for_each_entry(filter, &ifh->list, entry) {
-               if (filter->inode) {
+               if (filter->path.dentry) {
                        event->addr_filters_offs[count] = 0;
                        restart++;
                }
@@ -6814,7 +6814,7 @@ static bool perf_addr_filter_match(struct perf_addr_filter *filter,
                                     struct file *file, unsigned long offset,
                                     unsigned long size)
 {
-       if (filter->inode != file->f_inode)
+       if (d_inode(filter->path.dentry) != file_inode(file))
                return false;
 
        if (filter->offset > offset + size)
@@ -8028,8 +8028,7 @@ static void free_filters_list(struct list_head *filters)
        struct perf_addr_filter *filter, *iter;
 
        list_for_each_entry_safe(filter, iter, filters, entry) {
-               if (filter->inode)
-                       iput(filter->inode);
+               path_put(&filter->path);
                list_del(&filter->entry);
                kfree(filter);
        }
@@ -8123,7 +8122,7 @@ static void perf_event_addr_filters_apply(struct perf_event *event)
                 * Adjust base offset if the filter is associated to a binary
                 * that needs to be mapped:
                 */
-               if (filter->inode)
+               if (filter->path.dentry)
                        event->addr_filters_offs[count] =
                                perf_addr_filter_apply(filter, mm);
 
@@ -8196,7 +8195,6 @@ perf_event_parse_addr_filter(struct perf_event *event, char *fstr,
 {
        struct perf_addr_filter *filter = NULL;
        char *start, *orig, *filename = NULL;
-       struct path path;
        substring_t args[MAX_OPT_ARGS];
        int state = IF_STATE_ACTION, token;
        unsigned int kernel = 0;
@@ -8287,19 +8285,18 @@ perf_event_parse_addr_filter(struct perf_event *event, char *fstr,
                                        goto fail;
 
                                /* look up the path and grab its inode */
-                               ret = kern_path(filename, LOOKUP_FOLLOW, &path);
+                               ret = kern_path(filename, LOOKUP_FOLLOW,
+                                               &filter->path);
                                if (ret)
                                        goto fail_free_name;
 
-                               filter->inode = igrab(d_inode(path.dentry));
-                               path_put(&path);
                                kfree(filename);
                                filename = NULL;
 
                                ret = -EINVAL;
-                               if (!filter->inode ||
-                                   !S_ISREG(filter->inode->i_mode))
-                                       /* free_filters_list() will iput() */
+                               if (!filter->path.dentry ||
+                                   !S_ISREG(d_inode(filter->path.dentry)
+                                            ->i_mode))
                                        goto fail;
                        }