1

Consider the following kernel module source, which uses kprobes_register to get the address of a kernel symbol.

It works for symbols like flush_tlb_all, but not for sys_call_table.

A quick lookup using /proc/kallsyms:

sudo cat /proc/kallsyms | grep -E '\sflush_tlb_all$|\ssys_call_table$'
ffffffff86a83e20 T flush_tlb_all
ffffffff87a002e0 D sys_call_table

The difference being (man nm) that flush_tlb_all is in the text section, where as sys_call_table is in the data section. However, both symbols are global (external).

Why can't kprobes_register be used to lookup the address of sys_call_table?

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

#define MAX_SYMBOL_LEN  64
static char symbol[MAX_SYMBOL_LEN] = "flush_tlb_all";
module_param_string(symbol, symbol, sizeof(symbol), 0644);

/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {
   .symbol_name = symbol,
};

static int __init kprobe_init(void)
{
   int ret;

   ret = register_kprobe(&kp);
   if (ret < 0) {
      pr_err("register_kprobe failed, returned %d\n", ret);
      return ret;
   }
   pr_info("Planted kprobe at %p\n", kp.addr);
   return 0;
}

static void __exit kprobe_exit(void)
{
   unregister_kprobe(&kp);
   pr_info("kprobe at %p unregistered\n", kp.addr);
}

module_init(kprobe_init);
module_exit(kprobe_exit);
MODULE_LICENSE("GPL");
2

The difference being (man nm) that flush_tlb_all is in the text section, where as sys_call_table is in the data section. However, both symbols are global (external).

This is exactly why register_kprobe fails on sys_call_table: it only allows probes in the kernel text. kprobe_register calls check_kprobe_address_safe once it has the address to probe, and the latter checks that the address is part of the kernel text.

9
  • Thanks @StephenKitt. How is it possible to retrieve address of global symbols in the data section? Normally kallsyms would allow that, however its API is not exported to modules anymore. Maybe I should do a kprobe for the kallsyms functions and see where that leads me. I need this, because I want to experience with dynamically patching syscalls. – Shuzheng May 3 at 5:17
  • An approach similar to this one should still work, as long as you change the syscall table to be writable. – Stephen Kitt May 3 at 13:30
  • Thanks. However, the approach is extremely dependent on the given kernel for the searched kernel-memory range (between two symbols). I did it by using kprobe to get the non-exported kallsyms_lookup_name, and then used that to get the sys_call_table. However, my solution depends on kallsyms and kprobes having been configured for the given kernel. – Shuzheng May 3 at 15:18
  • ~@StephenKitt - Regarding your approach, it seems that sys_call_table[__NR_close]==__x64_sys_close and that sys_close is not exported anymore from syscalls.h? Furthermore, ksys_close is not equal to __x64_sys_close, so I have no real way of comparing sys_call_table[__NR_close] to a known symbol? Do you have any ideas on how to solve this issue? – Shuzheng May 5 at 8:50
  • That’s a good point, syscalls are no longer exported to modules. I don’t know of a workaround. – Stephen Kitt May 5 at 11:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.