Re: [PATCH v2] perf test amd ibs: avoid using executable heap
From: Peter Zijlstra
Date: Thu Jul 02 2026 - 06:24:45 EST
On Wed, Jul 01, 2026 at 10:29:04PM +0530, Ravi Bangoria wrote:
> Hi Peter, Ondrej,
>
> >> permission under SELinux (things like JIT or regex compilation need it
> >> as well). mmap() with MAP_ANONYMOUS will give us a zeroed mapping that
> >> begins on a page boundary, so the result is equivalent to the original
> >> code even without a memset() or the page-alignment dance.
> >
> > I would argue that having RWX is a problem, you really want RW->RO->RX
> > transitions, so even with mmap() you want to combine with mprotect().
>
> My original intent for using RWX was to generate sufficient Icache miss
> samples for the IBS Fetch unit by overwriting the code prior to execution.
> I am wondering whether it would be possible to achieve the same result
> by using CLFLUSH with RX permissions. Something like below (build tested
> only).
So for a test it is fine to have RWX, my comments were mostly aimed at
the IMO insane SELinux policies.
CLFLUSH+MB, and on AMD MB is serializing. Thus CLFLUSH will flush the
I-cache and MB will flush decode / ucode buffers IIRC. So yeah,
CLFLUSH+MB should work fine; if you want to go that route.
> --- a/tools/perf/arch/x86/tests/amd-ibs-period.c
> +++ b/tools/perf/arch/x86/tests/amd-ibs-period.c
> @@ -25,6 +25,7 @@ static int page_size;
> #define PERF_MMAP_TOTAL_PAGES (PERF_MMAP_DATA_PAGES + 1)
> #define PERF_MMAP_TOTAL_SIZE (PERF_MMAP_TOTAL_PAGES * page_size)
>
> +#define mb() asm volatile("mfence":::"memory")
> #define rmb() asm volatile("lfence":::"memory")
>
> enum {
> @@ -41,10 +42,16 @@ struct perf_pmu *fetch_pmu;
> struct perf_pmu *op_pmu;
> unsigned int perf_event_max_sample_rate;
>
> +static inline void clflush(const volatile void *p)
> +{
> + asm volatile("clflush (%0)" :: "r"(p) : "memory");
> +}
> +
> /* Dummy workload to generate IBS samples. */
> static int dummy_workload_1(unsigned long count)
> {
> - int (*func)(void);
> + int (*func1)(void);
> + int (*func2)(void);
> int ret = 0;
> char *p;
> char insn1[] = {
> @@ -59,33 +66,42 @@ static int dummy_workload_1(unsigned long count)
> 0xcc, /* int 3 */
> };
>
> - p = calloc(2, page_size);
> - if (!p) {
> - printf("malloc() failed. %m");
> +
> + p = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
RW
> + if (p == MAP_FAILED) {
> + printf("mmap() failed. %m");
> return 1;
> }
>
> - func = (void *)((unsigned long)(p + page_size - 1) & ~(page_size - 1));
> + memcpy(p, insn1, sizeof(insn1));
> + memcpy(p + 128, insn2, sizeof(insn2));
>
> - ret = mprotect(func, page_size, PROT_READ | PROT_WRITE | PROT_EXEC);
> + ret = mprotect(p, page_size, PROT_READ | PROT_EXEC);
RX
You really need an RO step in between IIRC, otherwise, depending on arch
details and mprotect implementation details, it is possible to have WX
overlap.
Notably, you want to have a TLB flush between removing W and adding X.
But again, this isn't relevant for simple test cases, but does matter
for JITs, esp. when they're embedded into applications with lots of user
input.
The thing you want to avoid at all cost is things like buffer overflows
(write primitives) to escalate into random code execution, which if
there are RWX buffers around, is almost trivial.