Re: [PATCH v1 10/58] perf evlist: Add reference count

sashiko-bot
2026-04-20T00:53:41Z

Sashiko AI review found 4 potential issue(s):
- [High] Calling `evlist__put()` in the Python bindings triggers `free()` on an interior pointer, corrupting the heap.
- [High] Accessing an evlist item from Python returns a detached clone, breaking interaction with opened events.
- [High] Double `evlist__put()` causing a Use-After-Free / Double Free in `builtin-trace.c`.
- [Medium] `pyrf_evlist__init` leaks the previously allocated `evlist` if `__init__` is called multiple times.
--

commit c68aec7a307f75660ac1e10cdc04f9b5d7aa17be
Author: Ian Rogers <irogers@google.com>

perf evlist: Add reference count

This patch introduces a reference count for the evlist structure, replacing
evlist__delete with evlist__put. In the Python bindings, the evlist is now
allocated dynamically instead of inline, preparing the way for further
reference counting changes for evsels.

> diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
> --- a/tools/perf/builtin-trace.c
> +++ b/tools/perf/builtin-trace.c
[ ... ]
> @@ -5364,7 +5364,7 @@ static void trace__exit(struct trace *trace)
> zfree(&trace->syscalls.table);
> }
> zfree(&trace->perfconfig_events);
> - evlist__delete(trace->evlist);
> + evlist__put(trace->evlist);

Can this cause a use-after-free and double free? Looking at trace__replay(),
trace->evlist is assigned to session->evlist without taking a reference. At
the end of that function, perf_session__delete() is called, which drops the
reference to session->evlist and frees it. Then cmd_trace() calls
trace__exit(), which executes this evlist__put() on the freed memory.

> diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
> --- a/tools/perf/util/python.c
> +++ b/tools/perf/util/python.c
[ ... ]
> @@ -1285,15 +1285,21 @@ static int pyrf_evlist__init(struct pyrf_evlist *pevlist,
> if (!PyArg_ParseTuple(args, "OO", &pcpus, &pthreads))
> return -1;
>
> + pevlist->evlist = evlist__new();
> + if (!pevlist->evlist) {
> + PyErr_NoMemory();
> + return -1;
> + }

Does this leak the previously allocated evlist if __init__ is called multiple
times on the same instance? In Python, __init__ can be called repeatedly, and
the previous pointer would be unconditionally overwritten without calling
evlist__put().

[ ... ]
> static void pyrf_evlist__delete(struct pyrf_evlist *pevlist)
> {
> - evlist__exit(&pevlist->evlist);
> + evlist__put(pevlist->evlist);
> Py_TYPE(pevlist)->tp_free((PyObject*)pevlist);
> }

Can this trigger free() on an interior pointer and corrupt the heap? In
pyrf_evlist__add(), an evsel embedded inside a Python object is added directly
to the C evlist. When the reference count drops to 0 here, evlist__put() calls
evlist__purge(), which iterates over the entries and calls evsel__delete().
This blindly invokes free() on the embedded pointer.

I see from upstream discussions that this issue is resolved in a later commit
(perf evsel: Add reference count).

[ ... ]
> @@ -1794,17 +1816,16 @@ static PyObject *pyrf_evlist__item(PyObject *obj, Py_ssize_t i)
> struct pyrf_evlist *pevlist = (void *)obj;
> struct evsel *pos;
>
> - if (i >= pevlist->evlist.core.nr_entries) {
> + if (i >= pevlist->evlist->core.nr_entries) {
> PyErr_SetString(PyExc_IndexError, "Index out of range");
> return NULL;
> }
>
> - evlist__for_each_entry(&pevlist->evlist, pos) {
> + evlist__for_each_entry(pevlist->evlist, pos) {
> if (i-- == 0)
> break;
> }
> -
> - return Py_BuildValue("O", container_of(pos, struct pyrf_evsel, evsel));
> + return pyrf_evsel__from_evsel(pos);
> }

Does accessing an evlist item from Python return a detached clone? The new
return path uses pyrf_evsel__from_evsel(), which deep-copies the event via
evsel__clone() instead of returning the opened event. Modifying the returned
event or accessing its file descriptors would operate on this copy.

I note this is addressed in a later patch in the series (perf evsel: Add
reference count).

--
Sashiko AI review · https://sashiko.dev/#/patchset/20260419235911.2186050-1-irogers@google.com?part=10