3.8 KiB
CVE-2024-50194
Description
In the Linux kernel, the following vulnerability has been resolved:arm64: probes: Fix uprobes for big-endian kernelsThe arm64 uprobes code is broken for big-endian kernels as it doesn'tconvert the in-memory instruction encoding (which is alwayslittle-endian) into the kernel's native endianness before analyzing andsimulating instructions. This may result in a few distinct problems:* The kernel may may erroneously reject probing an instruction which can safely be probed.* The kernel may erroneously erroneously permit stepping an instruction out-of-line when that instruction cannot be stepped out-of-line safely.* The kernel may erroneously simulate instruction incorrectly dur to interpretting the byte-swapped encoding.The endianness mismatch isn't caught by the compiler or sparse because:* The arch_uprobe::{insn,ixol} fields are encoded as arrays of u8, so the compiler and sparse have no idea these contain a little-endian 32-bit value. The core uprobes code populates these with a memcpy() which similarly does not handle endianness.* While the uprobe_opcode_t type is an alias for __le32, both arch_uprobe_analyze_insn() and arch_uprobe_skip_sstep() cast from u8[] to the similarly-named probe_opcode_t, which is an alias for u32. Hence there is no endianness conversion warning.Fix this by changing the arch_uprobe::{insn,ixol} fields to __le32 andadding the appropriate __le32_to_cpu() conversions prior to consumingthe instruction encoding. The core uprobes copies these fields as opaqueranges of bytes, and so is unaffected by this change.At the same time, remove MAX_UINSN_BYTES and consistently useAARCH64_INSN_SIZE for clarity.Tested with the following:| #include <stdio.h>| #include <stdbool.h>|| #define noinline attribute((noinline))|| static noinline void *adrp_self(void)| {| void *addr;|| asm volatile(| " adrp %x0, adrp_self\n"| " add %x0, %x0, :lo12:adrp_self\n"| : "=r" (addr));| }||| int main(int argc, char *argv)| {| void *ptr = adrp_self();| bool equal = (ptr == adrp_self);|| printf("adrp_self => %p\n"| "adrp_self() => %p\n"| "%s\n",| adrp_self, ptr, equal ? "EQUAL" : "NOT EQUAL");|| return 0;| }.... where the adrp_self() function was compiled to:| 00000000004007e0 <adrp_self>:| 4007e0: 90000000 adrp x0, 400000 <__ehdr_start>| 4007e4: 911f8000 add x0, x0, #0x7e0| 4007e8: d65f03c0 retBefore this patch, the ADRP is not recognized, and is assumed to besteppable, resulting in corruption of the result:| # ./adrp-self| adrp_self => 0x4007e0| adrp_self() => 0x4007e0| EQUAL| # echo 'p /root/adrp-self:0x007e0' > /sys/kernel/tracing/uprobe_events| # echo 1 > /sys/kernel/tracing/events/uprobes/enable| # ./adrp-self| adrp_self => 0x4007e0| adrp_self() => 0xffffffffff7e0| NOT EQUALAfter this patch, the ADRP is correctly recognized and simulated:| # ./adrp-self| adrp_self => 0x4007e0| adrp_self() => 0x4007e0| EQUAL| #| # echo 'p /root/adrp-self:0x007e0' > /sys/kernel/tracing/uprobe_events| # echo 1 > /sys/kernel/tracing/events/uprobes/enable| # ./adrp-self| adrp_self => 0x4007e0| adrp_self() => 0x4007e0| EQUAL
POC
Reference
No PoCs from references.