Perf 事件和工具安全性

概述

使用 Linux 的性能计数器 (perf_events) [1] , [2] , [3] 可能会带来相当大的风险,导致被监控进程访问的敏感数据泄露。无论是在直接使用 perf_events 系统调用 API [2] 的情况下,还是通过 Perf 工具用户模式实用程序 (Perf) 生成的数据文件 [3] , [4] ,都可能发生数据泄露。风险取决于 perf_events 性能监控单元 (PMU) [2] 和 Perf 收集并公开用于性能分析的数据的性质。收集的系统和性能数据可以分为几个类别

  1. 系统硬件和软件配置数据,例如:CPU 型号及其缓存配置,可用内存量及其拓扑结构,使用的内核和 Perf 版本,性能监控设置,包括实验时间,事件配置,Perf 命令行参数等。

  2. 用户和内核模块路径及其加载地址和大小,进程和线程名称及其 PID 和 TID,捕获的硬件和软件事件的时间戳。

  3. 内核软件计数器的内容(例如,用于上下文切换,页面错误,CPU 迁移),架构硬件性能计数器 (PMC) [8] 和特定于机器的寄存器 (MSR) [9] ,它们提供系统各个受监控部分的执行指标(例如,内存控制器 (IMC),互连 (QPI/UPI) 或外围 (PCIe) uncore 计数器),而无需直接归因于任何执行上下文状态。

  4. 架构执行上下文寄存器的内容(例如,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 将传统上与超级用户相关联的特权划分为不同的单元,称为 capabilities [6],可以针对非特权用户的进程和文件在每个线程的基础上独立启用和禁用。

启用 CAP_PERFMON capability 的非特权进程在 perf_events 性能监控和可观察性操作方面被视为特权进程,因此绕过内核中的范围权限检查。 CAP_PERFMON 实现了内核中性能监控和可观察性操作的最小特权原则 [13] (POSIX 1003.1e: 2.2.2.39),并提供了一种安全的系统性能监控和可观察性方法。

出于向后兼容性的原因,CAP_SYS_ADMIN 特权进程也可以访问 perf_events 监控和可观察性操作,但不建议将 CAP_SYS_ADMIN 用于安全监控和可观察性用例,而应使用 CAP_PERFMON capability。 如果使用 perf_events 系统调用 API 的进程的系统审计记录 [14] 包含拒绝获取 CAP_PERFMON 和 CAP_SYS_ADMIN capability 的记录,则建议仅为该进程提供 CAP_PERFMON capability,作为解决与使用性能监控和可观察性相关的双重访问拒绝日志记录的首选安全方法。

在 Linux v5.9 之前,使用 perf_events 系统调用的非特权进程还需要进行 PTRACE_MODE_READ_REALCREDS ptrace 访问模式检查 [7],其结果决定是否允许监控。 因此,提供 CAP_SYS_PTRACE capability 的非特权进程实际上被允许通过检查。 从 Linux v5.9 开始,不需要 CAP_SYS_PTRACE capability,并且 CAP_PERFMON 足以为进程提供进行性能监控和可观察性操作的能力。

授予非特权进程的其他 capability 可以有效地启用捕获额外数据,这些数据对于以后对受监控进程或系统的性能分析是必需的。 例如,CAP_SYSLOG capability 允许从 /proc/kallsyms 文件读取内核空间内存地址。

特权 Perf 用户组

capabilities 机制,特权 capability-dumb 文件 [6],文件系统 ACL [10] 和 sudo [15] 实用程序可用于创建专用特权 Perf 用户组,这些用户被允许不受限制地执行性能监控和可观察性。 可以采取以下步骤来创建此类特权 Perf 用户组。

  1. 创建 perf_users 特权 Perf 用户组,将 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
  1. 将所需的 capabilities 分配给 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

请注意,您可能需要在混合中使用“cap_ipc_lock”才能使用“perf top”之类的工具,或者使用“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 工具可执行文件分配所需的 capabilities(例如,文件系统以 nosuid 选项挂载或文件系统不支持扩展属性),则可以创建具有 capabilities 的特权环境,自然是 shell。 该 shell 为固有进程提供 CAP_PERFMON 和其他所需 capabilities,以便在环境中不受限制地进行性能监控和可观察性操作。 只能通过 sudo 实用程序为 perf_users 组的成员开放对环境的访问。 为了创建这样的环境

  1. 创建一个 shell 脚本,该脚本使用 capsh 实用程序 [16] 将 CAP_PERFMON 和其他所需 capabilities 分配到 shell 进程的环境 capability 集中,在启用 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
  1. 使用 perf_users 组的规则扩展 /etc/sudoers 文件中的 sudo 策略

# grep perf_users /etc/sudoers
%perf_users    ALL=/usr/local/bin/perf.shell
  1. 检查 perf_users 组的成员是否有权访问特权 shell,并且在固有进程的允许,有效和环境 capability 集中启用了 CAP_PERFMON 和其他所需 capabilities

$ 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 capability 管理的性能监控 API 的工具。

此特定访问控制管理仅适用于运行具有 CAP_SETPCAP, CAP_SETFCAP [6] capabilities 的超级用户或 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] capability 的非特权进程,则忽略该限制。

>=1:

范围仅包括每个进程的性能监控,而不包括系统范围的性能监控。 当在用户或内核空间中执行时发生的 CPU 和系统事件可以被监控和捕获以供以后分析。 施加每个用户每个 CPU 的 perf_event_mlock_kb 锁定限制,但对于具有 CAP_IPC_LOCK capability 的非特权进程,则忽略该限制。

>=2:

范围仅包括每个进程的性能监控。 只能监控和捕获在用户空间中执行时发生的 CPU 和系统事件。 施加每个用户每个 CPU 的 perf_event_mlock_kb 锁定限制,但对于具有 CAP_IPC_LOCK capability 的非特权进程,则忽略该限制。

资源控制

打开文件描述符

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 的内存,该内存高于用于 perf_event mmap 缓冲区的 RLIMIT_MEMLOCK 限制 (ulimit -l)。 特别是,这意味着,如果用户想要启动两个或多个性能监控进程,则用户需要手动将可用的 4128 KiB 分配给监控进程,例如,使用 --mmap-pages Perf 记录模式选项。 否则,第一个启动的性能监控进程将分配所有可用的 4128 KiB,而其他进程将由于缺少内存而无法继续。

对于具有 CAP_IPC_LOCK capability 的进程,将忽略 RLIMIT_MEMLOCK 和 perf_event_mlock_kb 资源约束。 因此,可以通过为 Perf 可执行文件提供 CAP_IPC_LOCK capability,为 perf_events/Perf 特权用户提供高于 perf_events/Perf 性能监控目的约束的内存。

参考书目