[SRU][J:linux-aws/N:linux-aws/R:linux-aws/S:linux-aws][PATCH 0/1] CAP_PERFMON insufficient to get perf data

Máté Szabó mszabo at salesforce.com
Tue Jul 28 19:01:00 UTC 2026


BugLink: https://bugs.launchpad.net/bugs/2131046

This patch has already been applied to mainline Ubuntu kernels,
but AWS kernels would likewise benefit from the fix for the
reasons listed in the SRU justification below.

SRU Justification:

[ Impact ]

The Ubuntu-specific perf_event_paranoid level 4 introduces an additional
capability check that requires CAP_SYS_ADMIN to access perf events.
However, this check was implemented before CAP_PERFMON was introduced,
and was never updated to recognize the new capability.

CAP_PERFMON was specifically designed to allow performance monitoring
operations without granting the broad privileges of CAP_SYS_ADMIN. The
current implementation forces users to grant CAP_SYS_ADMIN even when
CAP_PERFMON would be sufficient, violating the principle of least
privilege.

The perfmon_capable() helper function checks for either CAP_PERFMON or
CAP_SYS_ADMIN, providing the intended functionality while maintaining
backward compatibility with systems that use CAP_SYS_ADMIN.

This change allows processes with CAP_PERFMON to access perf events when
perf_event_paranoid is set to 4, while still requiring explicit grants
as intended by the stricter paranoid level. Processes with CAP_SYS_ADMIN
continue to work as before.

[ Test Plan ]

Use the following reproducer (with CAP_PERFMON file capabilities) to check
if CAP_SYS_ADMIN is still required.

```
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/capability.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <linux/perf_event.h>

static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, int cpu, int group_fd, unsigned long flags) {
  return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags);
}

void print_capabilities(void) {
  cap_t capabilities;
  char *capabilities_text;

  capabilities = cap_get_proc();
  if (capabilities == NULL) {
    printf("[!] Impossbile to get process capabilities.\n");
    return;
  }

  capabilities_text = cap_to_text(capabilities, NULL);
  if (capabilities_text == NULL) {
    printf("[!] Impossbile to convert process capabilities.\n");
    cap_free(capabilities);
    return;
  }

  printf("[*] Current capabilities: %s\n", capabilities_text);

  cap_free(capabilities_text);
  cap_free(capabilities);
}

int check_perf_access(const char *event_name, int event_type, int event_config) {
  struct perf_event_attr pe;
  int fd;

  memset(&pe, 0, sizeof(struct perf_event_attr));
  pe.type = event_type;
  pe.size = sizeof(struct perf_event_attr);
  pe.config = event_config;
  pe.disabled = 1;
  pe.exclude_kernel = 0;
  pe.exclude_hv = 1;

  fd = perf_event_open(&pe, 0, -1, -1, 0);

  if (fd == -1) {
    printf("[!] %s: Impossible to open perf event\n", event_name);
    return -1;
  } else {
    printf("[*] %s: Successfully opened perf event\n", event_name);
    close(fd);
    return 0;
  }
}

int main(int argc, char *argv[]) {
  int result = 0;
  FILE *fp;
  int paranoid_level = -2;

  fp = fopen("/proc/sys/kernel/perf_event_paranoid", "r");
  if (fp) {
    if (fscanf(fp, "%d", &paranoid_level) == 1) {
      printf("[*] Current perf_event_paranoid level: %d.\n", paranoid_level);
      if (paranoid_level == 4) {
        printf("[*] This is a custom Ubuntu paranoid level.\n");
      }
    }
    fclose(fp);
  } else {
    printf("[!] Impossible to perf_event_paranoid level.\n");
  }

  printf("\n");

  print_capabilities();

  printf("\n");

  result += check_perf_access("CPU_CYCLES", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES);
  result += check_perf_access("INSTRUCTIONS", PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS);
  result += check_perf_access("CACHE_REFERENCES", PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES);

  printf("\n");

  if (result == 0) {
    printf("All perf events accessible with current capabilities\n");
  } else {
    printf("Some events were not accessible with current capabilities\n");
  }
}
```

Before the patch:

```
$ ./perf_repro
[*] Current perf_event_paranoid level: 4.
[*] This is a custom Ubuntu paranoid level.

[*] Current capabilities: cap_perfmon=ep

[!] CPU_CYCLES: Impossible to open perf event
[!] INSTRUCTIONS: Impossible to open perf event
[!] CACHE_REFERENCES: Impossible to open perf event

Some events were not accessible with current capabilities
```

After the patch:
```
$ ./perf_repro
[*] Current perf_event_paranoid level: 4.
[*] This is a custom Ubuntu paranoid level.

[*] Current capabilities: cap_perfmon=ep

[*] CPU_CYCLES: Successfully opened perf event
[*] INSTRUCTIONS: Successfully opened perf event
[*] CACHE_REFERENCES: Successfully opened perf event

All perf events accessible with current capabilities
```

[ Regression Potential ]

The regression potential is minimal.
The fix maintains backward compatibility as perfmon_capable() accepts both
CAP_SYS_ADMIN and CAP_PERFMON, ensuring all curently working systems
are not impacted by the fix.
No security weakening occurs since CAP_PERFMON was designed
for performance monitoring and provides fewer privileges than CAP_SYS_ADMIN.

Massimiliano Pellizzer (1):
  UBUNTU: SAUCE: perf/core: Allow CAP_PERFMON for paranoid level 4

 kernel/events/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

-- 
2.55.0




More information about the kernel-team mailing list