Re: [PATCH v2 15/58] perf python: Add python session abstraction wrapping perf's session

sashiko-bot
2026-04-23T05:05:54Z

Sashiko AI review found 5 potential issue(s):
- [Critical] Processing a crafted or large perf.data file causes a heap buffer overflow in `pyrf_event__new`.
- [High] Shallow copy of `struct perf_sample` leaves dangling pointers to transient `mmap` memory, leading to Use-After-Free.
- [High] Using an uninitialized `perf.session` object causes a NULL pointer dereference and crashes the interpreter.
- [Medium] Python exceptions raised in the sample callback are silently cleared instead of being propagated.
- [Medium] Multiple calls to `perf.session.__init__` cause memory and resource leaks.
--

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

perf python: Add python session abstraction wrapping perf's session

This commit adds a python wrapper for the perf session, allowing developers
to process perf.data files within python scripts. It introduces the ability
to pass a sample callback during session creation and exposes thread querying
capabilities.

> diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
> index a2cdd92e05482..9cd7cca321aa1 100644
> --- a/tools/perf/util/python.c
> +++ b/tools/perf/util/python.c
[ ... ]
> @@ -2383,6 +2389,249 @@ static int pyrf_data__setup_types(void)
[ ... ]
> +static int pyrf_session_tool__sample(const struct perf_tool *tool,
> + union perf_event *event,
> + struct perf_sample *sample,
> + struct evsel *evsel,
> + struct machine *machine __maybe_unused)
> +{
> + struct pyrf_session *psession = container_of(tool, struct pyrf_session, tool);
> + PyObject *pyevent = pyrf_event__new(event);

Since event->header.size can reach up to 65535 bytes for events with large
payloads, does pyrf_event__new() allocate enough space to safely handle the
copy without causing a heap buffer overflow?

> + struct pyrf_event *pevent = (struct pyrf_event *)pyevent;
> + PyObject *ret;
> +
> + if (pyevent == NULL)
> + return -ENOMEM;
> +
> + memcpy(&pevent->sample, sample, sizeof(struct perf_sample));

Does this shallow copy leave pointers like sample->raw_data pointing directly
into the memory-mapped perf.data file?

If the python script retains the event object after the event loop advances
the mmap window, could this lead to a use-after-free when dereferencing
those pointers?

> + pevent->sample.evsel = evsel__get(evsel);
> + /* Avoid shallow copy pointing to lazily allocated memory that would be double freed. */
> + pevent->sample.user_regs = NULL;
> + pevent->sample.intr_regs = NULL;
> + if (pevent->sample.merged_callchain)
> + pevent->sample.callchain = NULL;
> +
> + ret = PyObject_CallFunction(psession->sample, "O", pyevent);
> + if (!ret) {
> + PyErr_Print();

Does calling PyErr_Print() clear the python error indicator?

If the python callback raises an exception, will it be silently swallowed
here instead of propagating back to the user's script?

> + Py_DECREF(pyevent);
> + return -1;
> + }
> + Py_DECREF(ret);
> + Py_DECREF(pyevent);
> + return 0;
> +}
[ ... ]
> +static int pyrf_session__init(struct pyrf_session *psession, PyObject *args, PyObject *kwargs)
> +{
> + struct pyrf_data *pdata;
> + PyObject *sample = NULL;
> + static char *kwlist[] = { "data", "sample", NULL };
> +
> + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!|O", kwlist, &pyrf_data__type, &pdata,
> + &sample))
> + return -1;
> +
> + Py_INCREF(pdata);
> + psession->pdata = pdata;
> + perf_tool__init(&psession->tool, /*ordered_events=*/true);
> + psession->tool.ordering_requires_timestamps = true;
[ ... ]
> + psession->tool.text_poke = perf_event__process_text_poke;
> + psession->tool.build_id = perf_event__process_build_id;
> + psession->session = perf_session__new(&pdata->data, &psession->tool);

Since python permits explicit re-entrancy into an object's __init__ method,
does this code leak the previously allocated perf_session struct and other
properties if __init__ is called multiple times on the same instance?

> + if (IS_ERR(psession->session)) {
> + PyErr_Format(PyExc_IOError, "failed to create session: %ld",
> + PTR_ERR(psession->session));
[ ... ]
> +static PyObject *pyrf_session__process_events(struct pyrf_session *psession)
> +{
> + int err = perf_session__process_events(psession->session);

If a python script creates a session object without calling __init__, for
example via perf.session.__new__(), could psession->session remain NULL and
cause a segmentation fault here?

> + return PyLong_FromLong(err);
> +}

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