[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", ¶noid_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