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

Paolo Pisati paolo.pisati at canonical.com
Wed Nov 12 15:13:00 UTC 2025


On Tue, Nov 11, 2025 at 04:34:20PM +0100, Massimiliano Pellizzer wrote:
> 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
> 
> 
> -- 
> kernel-team mailing list
> kernel-team at lists.ubuntu.com
> https://lists.ubuntu.com/mailman/listinfo/kernel-team

Acked-by: Paolo Pisati <paolo.pisati at canonical.com>
-- 
bye,
p.



More information about the kernel-team mailing list