acpi_ns_validate_handle's code is modified and triggers IBT

From: Preble, Adam C
Date: Sat Jun 24 2023 - 01:29:23 EST


When IBT was finally turned on by default, I got nailed with a missing endbr when calling a kernel function (acpi_ns_validate_handle). We're dragging that function out of the kernel by dragging out kallsyms_lookup. I understand this kind of thing is frowned upon, but I'm not in a position with my maintenance yet to decide on what to do instead.

I eventually discovered it even happens with a 6.3.9 kernel running in QEMU. Note thought that I've only tested this on 64-bit Intel platforms. It looks like the first nine bytes are getting replaced with something like two expanded no-ops. I think it's a 4-byte no-op and 5-byte no-op. This then bugs the kernel when trying to jump to it from a function pointer in another module--clearly an indirect jump that can't be compiled away.

If I disassemble the vmlinux image and look at acpi_ns_validate_handle in there, I very much have an endbr at the start. The machine instructions are getting mutated after the fact.

I have some code I can share for this. Instead of relying on having IBT enabled, this just looks at the bytes and reports if there's a mismatch. I use the assembler I was seeing from disassembly as a reference, but it'll just print out the delta for the first 16 bytes either way.

If it matters, the underlying distribution has been Debian Bullseye for all experiments.

Anyways, my question is a rather ambiguous and exasperated "what is going on here?!"

#include <linux/module.h>
#include <linux/acpi.h>
#include <linux/kprobes.h>

// If you're not resolving acpi_namespace_node, try this include. The 6.3.9
// kernel I was using didn't need it, but I think I needed it at least in
// 6.2.0.
// #include <acpi/acpica/aclocal.h>

// kallsyms_lookup_name is not public any more and anybody trying to use it has
// to do something clever. We are doing our own clever thing to expose it
// courtesy of this smart method:
// https://github.com/xcellerator/linux_kernel_hacking/issues/3
static struct kprobe kp = {
.symbol_name = "kallsyms_lookup_name"
};

typedef struct acpi_namespace_node (*acpi_ns_validate_handle_t)(acpi_handle);
typedef unsigned long (*kallsyms_lookup_name_t)(const char *);

static int __init init_acpi_ibt_test_module(void)
{
acpi_ns_validate_handle_t func;
kallsyms_lookup_name_t kallsyms_lookup_name;
unsigned int* as_ints;
unsigned int expected[] = {
0xfa1e0ff3,
0xa13c67e8,
0x894855ff,
0x894853e5,
};
int i;
int bad = 0;

pr_info("Loading ibt crash test module. It will now run the test\n");


register_kprobe(&kp);

kallsyms_lookup_name = (kallsyms_lookup_name_t) kp.addr;

func = (acpi_ns_validate_handle_t) kallsyms_lookup_name
("acpi_ns_validate_handle");

as_ints = (unsigned int*) func;
printk(KERN_ERR "Expected: ");
for(i = 0; i < 4; ++i) {
printk(KERN_CONT "%08x ", expected[i]);
}
printk(KERN_ERR "Actual: ");
for(i = 0; i < 4; ++i) {
printk(KERN_CONT "%08x ", as_ints[i]);
bad |= expected[i] != as_ints[i];
}

if(bad) {
pr_err("Mismatch in starting code for acpi_ns_validate_handle!\n");
return -1;
}

return 0;
}

static void __exit cleanup_ibt_acpi_test_module(void)
{
pr_info("Unloading ibt crash test module.\n");
}

module_init(init_acpi_ibt_test_module);
module_exit(cleanup_ibt_acpi_test_module)

MODULE_LICENSE("GPL");