Perf 事件和工具安全性¶
概述¶
使用 Linux 的性能计数器 (perf_events) [1], [2], [3] 可能会带来相当大的风险,即泄漏被监控进程访问的敏感数据。数据泄漏可能发生在直接使用 perf_events 系统调用 API [2] 的场景中,以及通过 Perf 工具用户模式实用程序 (Perf) 生成的数据文件 [3], [4]。风险取决于 perf_events 性能监控单元 (PMU) [2] 和 Perf 收集并暴露用于性能分析的数据的性质。收集的系统和性能数据可以分为几个类别
系统硬件和软件配置数据,例如:CPU 型号及其缓存配置、可用内存量及其拓扑、使用的内核和 Perf 版本、性能监控设置(包括实验时间、事件配置、Perf 命令行参数等)。
用户和内核模块路径及其加载地址和大小、进程和线程名称及其 PID 和 TID、捕获的硬件和软件事件的时间戳。
内核软件计数器的内容(例如,用于上下文切换、页面错误、CPU 迁移)、架构硬件性能计数器 (PMC) [8] 和特定于机器的寄存器 (MSR) [9],这些寄存器提供系统各个监控部分的执行指标(例如,内存控制器 (IMC)、互连 (QPI/UPI) 或外围 (PCIe) 非核心计数器),而没有直接归因于任何执行上下文状态。
架构执行上下文寄存器的内容(例如,x86_64 上的 RIP、RSP、RBP)、进程用户和内核空间内存地址和数据、捕获此类别数据的各种架构 MSR 的内容。
属于第四类的数据可能包含敏感的进程数据。如果 PMU 在某些监控模式下捕获执行上下文寄存器的值或进程内存中的数据,则对此类监控模式的访问需要正确排序和保护。因此,perf_events 性能监控和可观察性操作是安全访问控制管理的主题 [5]。
perf_events 访问控制¶
为了执行安全检查,Linux 实现将进程分为两类 [6]:a) 特权进程(其有效用户 ID 为 0,称为超级用户或 root),以及 b) 非特权进程(其有效 UID 为非零)。特权进程绕过所有内核安全权限检查,因此 perf_events 性能监控完全可用于特权进程,而没有访问、范围和资源限制。
非特权进程需要根据进程的凭据 [5](通常:有效 UID、有效 GID 和补充组列表)进行完整的安全权限检查。
Linux 将传统上与超级用户关联的特权划分为不同的单元,称为能力 [6],这些能力可以为非特权用户的进程和文件独立地启用和禁用。
启用了 CAP_PERFMON 能力的非特权进程在 perf_events 性能监控和可观察性操作方面被视为特权进程,因此会绕过内核中的范围权限检查。CAP_PERFMON 为内核中的性能监控和可观察性操作实现了最小特权原则 [13] (POSIX 1003.1e: 2.2.2.39),并为系统中的性能监控和可观察性提供了一种安全的方法。
出于向后兼容的原因,perf_events 监控和可观察性操作的访问也对 CAP_SYS_ADMIN 特权进程开放,但不鼓励将 CAP_SYS_ADMIN 用于安全监控和可观察性用例,而应使用 CAP_PERFMON 能力。如果使用 perf_events 系统调用 API 的进程的系统审核记录 [14] 包含拒绝获取 CAP_PERFMON 和 CAP_SYS_ADMIN 能力的记录,则建议单独为该进程提供 CAP_PERFMON 能力,作为解决与性能监控和可观察性使用相关的双重访问拒绝日志记录的首选安全方法。
在 Linux v5.9 之前,使用 perf_events 系统调用的非特权进程还需要进行 PTRACE_MODE_READ_REALCREDS ptrace 访问模式检查 [7],其结果决定是否允许监控。因此,启用了 CAP_SYS_PTRACE 能力的非特权进程实际上被允许通过检查。从 Linux v5.9 开始,不再需要 CAP_SYS_PTRACE 能力,提供 CAP_PERFMON 就足以让进程进行性能监控和可观察性操作。
授予非特权进程的其他能力可以有效地启用捕获其他数据,这些数据对于稍后对受监控进程或系统进行性能分析是必需的。例如,CAP_SYSLOG 能力允许从 /proc/kallsyms 文件读取内核空间内存地址。
特权 Perf 用户组¶
能力机制、特权能力哑文件 [6]、文件系统 ACL [10] 和 sudo [15] 实用程序可用于创建特权 Perf 用户的专用组,这些用户被允许不受限制地执行性能监控和可观察性。可以采取以下步骤来创建此类特权 Perf 用户组。
创建特权 Perf 用户的 perf_users 组,将 perf_users 组分配给 Perf 工具可执行文件,并限制系统中不在 perf_users 组中的其他用户对可执行文件的访问
# groupadd perf_users
# ls -alhF
-rwxr-xr-x 2 root root 11M Oct 19 15:12 perf
# chgrp perf_users perf
# ls -alhF
-rwxr-xr-x 2 root perf_users 11M Oct 19 15:12 perf
# chmod o-rwx perf
# ls -alhF
-rwxr-x--- 2 root perf_users 11M Oct 19 15:12 perf
将所需的功能分配给 Perf 工具可执行文件,并为 perf_users 组的成员启用监控和可观察性权限 [6]
# setcap "cap_perfmon,cap_sys_ptrace,cap_syslog=ep" perf
# setcap -v "cap_perfmon,cap_sys_ptrace,cap_syslog=ep" perf
perf: OK
# getcap perf
perf = cap_sys_ptrace,cap_syslog,cap_perfmon+ep
如果已安装的 libcap [16] 尚不支持 “cap_perfmon”,则使用 “38” 代替,即:
# setcap "38,cap_ipc_lock,cap_sys_ptrace,cap_syslog=ep" perf
请注意,对于诸如“perf top”之类的工具,您可能需要在组合中使用‘cap_ipc_lock’,或者使用‘perf top -m N’来减少它用于 perf 环形缓冲区的内存,请参阅下面的内存分配部分。
使用不支持 CAP_PERFMON 的 libcap 将导致 cap_get_flag(caps, 38, CAP_EFFECTIVE, &val) 失败,这将导致默认事件为 ‘cycles:u’,因此作为一种解决方法,请显式请求 ‘cycles’ 事件,即:
# perf top -e cycles
要使用仅具有 CAP_PERFMON 的 perf 二进制文件获取内核和用户样本。
因此,perf_users 组的成员能够通过使用已配置的 Perf 工具可执行文件的功能来进行性能监控和可观察性,该可执行文件在执行时会通过 perf_events 子系统的范围检查。
如果无法为 Perf 工具可执行文件分配所需的功能(例如,文件系统以 nosuid 选项挂载或文件系统不支持扩展属性),则可以创建具有特权的环境(自然是 shell)。该 shell 为固有进程提供 CAP_PERFMON 和其他所需的功能,以便在环境中无限制地进行性能监控和可观察性操作。可以通过 sudo 实用程序仅为 perf_users 组的成员开放对环境的访问。为了创建这样的环境
创建一个 shell 脚本,该脚本使用 capsh 实用程序 [16] 将 CAP_PERFMON 和其他所需的功能分配到 shell 进程的 ambient 功能集,在启用 SECBIT_NO_SETUID_FIXUP、SECBIT_NOROOT 和 SECBIT_NO_CAP_AMBIENT_RAISE 位后锁定进程安全位,然后将进程标识更改为 sudo 脚本调用者,该调用者本质上应该是 perf_users 组的成员
# ls -alh /usr/local/bin/perf.shell
-rwxr-xr-x. 1 root root 83 Oct 13 23:57 /usr/local/bin/perf.shell
# cat /usr/local/bin/perf.shell
exec /usr/sbin/capsh --iab=^cap_perfmon --secbits=239 --user=$SUDO_USER -- -l
在 /etc/sudoers 文件中扩展 sudo 策略,为 perf_users 组添加规则
# grep perf_users /etc/sudoers
%perf_users ALL=/usr/local/bin/perf.shell
检查 perf_users 组的成员是否可以访问特权 shell,并且固有进程的 permitted、effective 和 ambient 功能集中启用了 CAP_PERFMON 和其他所需的功能
$ id
uid=1003(capsh_test) gid=1004(capsh_test) groups=1004(capsh_test),1000(perf_users) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
$ sudo perf.shell
[sudo] password for capsh_test:
$ grep Cap /proc/self/status
CapInh: 0000004000000000
CapPrm: 0000004000000000
CapEff: 0000004000000000
CapBnd: 000000ffffffffff
CapAmb: 0000004000000000
$ capsh --decode=0000004000000000
0x0000004000000000=cap_perfmon
因此,perf_users 组的成员可以访问特权环境,在该环境中,他们可以使用受 CAP_PERFMON Linux 功能控制的、采用性能监控 API 的工具。
此特定的访问控制管理仅适用于具有 CAP_SETPCAP、CAP_SETFCAP [6] 功能的超级用户或 root 运行进程。
非特权用户¶
非特权进程的 perf_events 范围和访问控制由 perf_event_paranoid [2] 设置控制
- -1:
对使用 perf_events 性能监控不施加任何范围和访问限制。当分配用于存储性能数据的内存缓冲区时,将忽略每个用户、每个 CPU 的 perf_event_mlock_kb [2] 锁定限制。这是最不安全的模式,因为允许监控的范围被最大化,并且对为性能监控分配的资源没有施加 perf_events 特定的限制。
- >=0:
范围包括按进程和系统范围的性能监控,但不包括原始跟踪点和 ftrace 函数跟踪点监控。可以在用户空间或内核空间中执行时发生的 CPU 和系统事件可以被监控和捕获以供以后分析。每个用户、每个 CPU 的 perf_event_mlock_kb 锁定限制是强制执行的,但对于具有 CAP_IPC_LOCK [6] 功能的非特权进程则忽略。
- >=1:
范围仅包括按进程的性能监控,不包括系统范围的性能监控。可以在用户空间或内核空间中执行时发生的 CPU 和系统事件可以被监控和捕获以供以后分析。每个用户、每个 CPU 的 perf_event_mlock_kb 锁定限制是强制执行的,但对于具有 CAP_IPC_LOCK 功能的非特权进程则忽略。
- >=2:
范围仅包括按进程的性能监控。只能监控和捕获在用户空间中执行时发生的 CPU 和系统事件以供以后分析。每个用户、每个 CPU 的 perf_event_mlock_kb 锁定限制是强制执行的,但对于具有 CAP_IPC_LOCK 功能的非特权进程则忽略。
资源控制¶
打开的文件描述符¶
perf_events 系统调用 API [2] 为每个已配置的 PMU 事件分配文件描述符。打开的文件描述符是每个进程可计量的资源,受 RLIMIT_NOFILE [11] 限制(ulimit -n)控制,该限制通常源自登录 shell 进程。当在大型服务器系统上为大量事件配置 Perf 集合时,很容易达到此限制,从而阻止所需的监控配置。可以通过修改 limits.conf 文件 [12] 的内容来按用户增加 RLIMIT_NOFILE 限制。通常,Perf 采样会话(perf record)需要的打开 perf_event 文件描述符的数量不小于受监控事件的数量乘以受监控 CPU 的数量。
内存分配¶
用户进程可用于捕获性能监控数据的内存量由 perf_event_mlock_kb [2] 设置控制。此 perf_event 特定资源设置定义了允许用户进程映射以执行性能监控的内存的每个 CPU 总限制。该设置本质上扩展了 RLIMIT_MEMLOCK [11] 限制,但仅限于专门用于捕获监控的性能事件和相关数据的内存区域。
例如,如果一台机器有八个核心,并且 perf_event_mlock_kb 限制设置为 516 KiB,那么用户进程将获得 516 KiB * 8 = 4128 KiB 的内存,该内存高于 RLIMIT_MEMLOCK 限制 (ulimit -l) 用于 perf_event mmap 缓冲区。特别是,这意味着,如果用户想要启动两个或多个性能监控进程,则需要用户手动在监控进程之间分配可用的 4128 KiB,例如,使用 --mmap-pages Perf record 模式选项。否则,第一个启动的性能监控进程将分配所有可用的 4128 KiB,并且其他进程将由于缺少内存而无法继续。
对于具有 CAP_IPC_LOCK 功能的进程,RLIMIT_MEMLOCK 和 perf_event_mlock_kb 资源限制将被忽略。因此,通过为 Perf 可执行文件提供 CAP_IPC_LOCK 功能,可以为 perf_events/Perf 特权用户提供高于约束的内存,以用于 perf_events/Perf 性能监控目的。