[SRU][J/N/P/Q/Unstable][PATCH 0/1] CAP_PERFMON insufficient to get perf data

Massimiliano Pellizzer massimiliano.pellizzer at canonical.com
Tue Nov 11 15:34:20 UTC 2025


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

[ 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.51.0




More information about the kernel-team mailing list