[PATCH 4.14 66/85] perf/core: Fix bad use of igrab()

From: Greg Kroah-Hartman
Date: Tue Nov 17 2020 - 08:16:52 EST


From: Song Liu <songliubraving@xxxxxx>

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@xxxxxxxxxxxxxxx>
Reported-by: Miklos Szeredi <miklos@xxxxxxxxxx>
Signed-off-by: Song Liu <songliubraving@xxxxxx>
Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
Cc: <kernel-team@xxxxxx>
Cc: Alexander Shishkin <alexander.shishkin@xxxxxxxxxxxxxxx>
Cc: Arnaldo Carvalho de Melo <acme@xxxxxxxxxx>
Cc: Jiri Olsa <jolsa@xxxxxxxxxx>
Cc: Linus Torvalds <torvalds@xxxxxxxxxxxxxxxxxxxx>
Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
Cc: Stephane Eranian <eranian@xxxxxxxxxx>
Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx>
Cc: Vince Weaver <vincent.weaver@xxxxxxxxx>
Fixes: 375637bc5249 ("perf/core: Introduce address range filtering")
Link: http://lkml.kernel.org/r/20180418062907.3210386-2-songliubraving@xxxxxx
Signed-off-by: Ingo Molnar <mingo@xxxxxxxxxx>
Signed-off-by: Sudip Mukherjee <sudipm.mukherjee@xxxxxxxxx>
Signed-off-by: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
---
arch/x86/events/intel/pt.c | 4 ++--
include/linux/perf_event.h | 2 +-
kernel/events/core.c | 21 +++++++++------------
3 files changed, 12 insertions(+), 15 deletions(-)

--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -1190,7 +1190,7 @@ static int pt_event_addr_filters_validat
if (!filter->range || !filter->size)
return -EOPNOTSUPP;

- if (!filter->inode) {
+ if (!filter->path.dentry) {
if (!valid_kernel_ip(filter->offset))
return -EINVAL;

@@ -1217,7 +1217,7 @@ static void pt_event_addr_filters_sync(s
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 */
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -466,7 +466,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,
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -6450,7 +6450,7 @@ static void perf_event_addr_filters_exec

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++;
}
@@ -7124,7 +7124,7 @@ static bool perf_addr_filter_match(struc
struct file *file, unsigned long offset,
unsigned long size)
{
- if (filter->inode != file_inode(file))
+ if (d_inode(filter->path.dentry) != file_inode(file))
return false;

if (filter->offset > offset + size)
@@ -8345,8 +8345,7 @@ static void free_filters_list(struct lis
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);
}
@@ -8443,7 +8442,7 @@ static void perf_event_addr_filters_appl
* 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);

@@ -8516,7 +8515,6 @@ perf_event_parse_addr_filter(struct perf
{
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;
@@ -8620,19 +8618,18 @@ perf_event_parse_addr_filter(struct perf
goto fail_free_name;

/* 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;

event->addr_filters.nr_file_filters++;