CVE-2024-50099

CVE-2024-50099

在本次讨论中,关键的漏洞源于 LDR literalLDRSW literal 指令的模拟函数 simulate_ldr_literal()simulate_ldrsw_literal(),这两个函数直接通过 C 代码访问内存,而没有使用内核的异常表(extable)来处理内存访问错误,导致内核在遇到无效内存访问时触发 BUG(),进而导致内核崩溃或其他异常情况。

原理:LDR Literal 和 LDRSW Literal 没有使用异常表导致 BUG()

simulate_ldr_literal()simulate_ldrsw_literal() 函数

这两个函数模拟了 ARM 架构下的 LDR literal(加载字面量)和 LDRSW literal(加载字面量并扩展为符号扩展)指令的执行,它们主要用来模拟从指定内存地址加载数据到寄存器。以下是两个函数的简要解析:

1. simulate_ldr_literal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __kprobes
simulate_ldr_literal(u32 opcode, long addr, struct pt_regs *regs)
{
u64 *load_addr;
int xn = opcode & 0x1f; // 目标寄存器号
int disp;

disp = ldr_displacement(opcode); // 获取指令中的位移量
load_addr = (u64 *) (addr + disp); // 计算加载地址

if (opcode & (1 << 30)) /* x0-x30 */
set_x_reg(regs, xn, *load_addr); // 设置64位寄存器
else /* w0-w30 */
set_w_reg(regs, xn, *load_addr); // 设置32位寄存器

instruction_pointer_set(regs, instruction_pointer(regs) + 4); // 更新指令指针
}
  • 功能: 该函数模拟 ARM 指令 LDR(literal),用于将内存中的数据加载到寄存器中。指令中的位移量(displacement)决定了加载的数据位置。
  • 核心步骤:
    • 通过 ldr_displacement(opcode) 获取内存地址偏移量。
    • 根据指令的目标寄存器(x0-x30 或 w0-w30)从计算出的地址加载数据。
    • 更新指令指针以指向下一条指令。

2. simulate_ldrsw_literal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __kprobes
simulate_ldrsw_literal(u32 opcode, long addr, struct pt_regs *regs)
{
s32 *load_addr;
int xn = opcode & 0x1f; // 目标寄存器号
int disp;

disp = ldr_displacement(opcode); // 获取指令中的位移量
load_addr = (s32 *) (addr + disp); // 计算加载地址

set_x_reg(regs, xn, *load_addr); // 设置64位寄存器

instruction_pointer_set(regs, instruction_pointer(regs) + 4); // 更新指令指针
}
  • 功能: 该函数模拟 LDRSW 指令,用于将内存中的 32 位数据加载到寄存器,并将其符号扩展到 64 位(扩展为一个 s32 类型的值)。
  • 核心步骤:
    • 获取位移量并计算目标地址。
    • 从该地址加载 32 位的数据,并通过符号扩展将其存储在 64 位寄存器中。
    • 更新指令指针,继续执行下一条指令。

没有使用异常表(extable)的问题

在 Linux 内核中,当内核访问内存时,通常会使用 异常表(extable) 来处理潜在的内存访问错误。例如,如果内存访问遇到页错误,异常表会捕获这个错误并采取适当的恢复措施(例如,重试访问、返回错误或采取其他措施)。

然而,simulate_ldr_literal()simulate_ldrsw_literal() 函数存在一个问题:它们直接使用普通的 C 代码来访问用户内存,而没有设置异常表(extable)来捕获内存访问中的错误。如果这两个函数尝试访问无效的内存地址(例如,地址无效、没有权限访问或超出有效范围),它们不会捕获错误,而是导致内核触发一个硬件异常(如页面错误)。

为什么没有使用异常表(extable)?

  1. 异常表的作用: 异常表(extable)用于捕获和处理内核中的异常,尤其是内存访问异常。如果在执行指令时遇到页错误或其他内存错误,内核会查找异常表,查看是否有适当的处理机制来恢复错误或采取其他安全措施。

  2. 直接内存访问的风险:

    • simulate_ldr_literal()simulate_ldrsw_literal() 这两个函数直接通过普通 C 代码访问内存,而没有为这些内存访问设置异常表(extable)。如果访问的内存无效或无法访问(例如,用户空间内存或非分配的内存),它们会引发硬件异常,而没有任何恢复机制。
    • 这种做法在某些情况下可能导致 BUG() 宏被触发,内核会崩溃并终止执行。

BUG() 宏的作用:

  • BUG() 是 Linux 内核中的一个宏,当遇到致命错误时会触发。它的作用是触发一个内核崩溃,通常用于开发或调试阶段,帮助开发者识别和修复问题。
  • 在这种情况下,由于没有适当的异常处理机制,如果指令模拟过程中出现内存访问错误,内核会调用 BUG(),导致内核线程终止,进而可能导致系统崩溃或死锁。

修复逻辑:

为了解决这个问题,修复的关键点是 避免在 uprobe 的情况下使用 simulate_ldr_literal()simulate_ldrsw_literal(),并且增加对这些指令的处理逻辑

  1. 修改 arm_kprobe_decode_insnarm_probe_decode_insn 中的指令处理
    • 通过检查 LDR literalLDRSW literal 指令的类型,在 arm_kprobe_decode_insn 中对这些指令的处理进行了修改:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      if (aarch64_insn_is_ldr_lit(insn)) {
      api->handler = simulate_ldr_literal;
      decoded = INSN_GOOD_NO_SLOT;
      } else if (aarch64_insn_is_ldrsw_lit(insn)) {
      api->handler = simulate_ldrsw_literal;
      decoded = INSN_GOOD_NO_SLOT;
      } else {
      decoded = arm_probe_decode_insn(insn, &asi->api);
      }
    • 这种修改确保了 LDR literalLDRSW literal 指令不会使用不安全的内存访问函数,防止了错误的发生。
  2. 拒绝不安全的 uprobe 使用
    • 在修改后的逻辑中,simulate_ldr_literal()simulate_ldrsw_literal() 只允许在 kprobe 的上下文中使用,而不会在 uprobe 中进行模拟。
    • 这意味着当 LDR literalLDRSW literal 被检测到时,如果它们属于 uprobe 指令类型,系统会拒绝执行这些指令的处理,防止它们触发异常。
  3. 返回 INSN_REJECTED
    • 对于不支持的 LDR literalLDRSW literal 指令类型,arm_probe_decode_insn() 返回 INSN_REJECTED,即拒绝执行这类指令。
    • 这样,内核可以避免进一步的错误,保持稳定性,防止错误访问内存并触发内核崩溃。

总结

修复的核心在于: - 避免不安全的内存访问:通过检查指令类型并为 LDR literalLDRSW literal 指令提供适当的处理逻辑,避免了直接使用 simulate_ldr_literal()simulate_ldrsw_literal() 进行内存访问,尤其是在 uprobe 中。 - 增强内核稳定性:通过使用 INSN_REJECTED 和拒绝处理不安全的指令,防止了内核因非法内存访问而触发 BUG()。 - 修复了直接内存访问导致的问题:通过检查内存访问的合法性,确保内核的内存访问机制更加健壮。

通过这些改动,内核避免了直接通过 C 代码访问用户内存而不处理异常,从而提高了内核的稳定性,解决了 CVE-2024-50099 漏洞。


CVE-2024-50099
https://realwujing.github.io/linux/kernel/bugs/perf/CVE-2024-50099/
作者
Wu Jing
发布于
2024年12月26日
许可协议