Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perf/core: Fix cgroup event list management
The active cgroup events are managed in the per-cpu cgrp_cpuctx_list. This list is accessed from current cpu and not protected by any locks. But from the commit ef54c1a ("perf: Rework perf_event_exit_event()"), this assumption does not hold true anymore. In the perf_remove_from_context(), it can remove an event from the context without an IPI when the context is not active. I think it assumes task event context, but it's possible for cpu event context only with cgroup events can be inactive at the moment - and it might become active soon. If the event is enabled when it's about to be closed, it might call perf_cgroup_event_disable() and list_del() with the cgrp_cpuctx_list on a different cpu. This resulted in a crash due to an invalid list pointer access during the cgroup list traversal on the cpu which the event belongs to. The following program can crash my box easily.. #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <linux/perf_event.h> #include <sys/stat.h> #include <sys/syscall.h> //#define CGROUP_ROOT "/dev/cgroup/devices" #define CGROUP_ROOT "/sys/fs/cgroup" int perf_event_open(struct perf_event_attr *attr, int pid, int cpu, int grp, unsigned long flags) { return syscall(SYS_perf_event_open, attr, pid, cpu, grp, flags); } int get_cgroup_fd(const char *grp) { char buf[128]; snprintf(buf, sizeof(buf), "%s/%s", CGROUP_ROOT, grp); /* ignore failures */ mkdir(buf, 0755); return open(buf, O_RDONLY); } int main(int argc, char *argv[]) { struct perf_event_attr hw = { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES, }; struct perf_event_attr sw = { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CPU_CLOCK, }; int cpus = sysconf(_SC_NPROCESSORS_ONLN); int fd[4][cpus]; int cgrpA, cgrpB; cgrpA = get_cgroup_fd("A"); cgrpB = get_cgroup_fd("B"); if (cgrpA < 0 || cgrpB < 0) { printf("failed to get cgroup fd\n"); return 1; } while (1) { int i; for (i = 0; i < cpus; i++) { fd[0][i] = perf_event_open(&hw, cgrpA, i, -1, PERF_FLAG_PID_CGROUP); fd[1][i] = perf_event_open(&sw, cgrpA, i, -1, PERF_FLAG_PID_CGROUP); fd[2][i] = perf_event_open(&hw, cgrpB, i, -1, PERF_FLAG_PID_CGROUP); fd[3][i] = perf_event_open(&sw, cgrpB, i, -1, PERF_FLAG_PID_CGROUP); } for (i = 0; i < cpus; i++) { close(fd[3][i]); close(fd[2][i]); close(fd[1][i]); close(fd[0][i]); } } return 0; } Let's use IPI to prevent such crashes. Similarly, I think perf_install_in_context() should use IPI for the first cgroup event at least. Cc: Marco Elver <elver@google.com> Signed-off-by: Namhyung Kim <namhyung@kernel.org>
- Loading branch information