| // SPDX-License-Identifier: GPL-2.0-only |
| #include <linux/codetag.h> |
| #include <linux/idr.h> |
| #include <linux/kallsyms.h> |
| #include <linux/module.h> |
| #include <linux/seq_buf.h> |
| #include <linux/slab.h> |
| #include <linux/vmalloc.h> |
| |
| struct codetag_type { |
| struct list_head link; |
| unsigned int count; |
| struct idr mod_idr; |
| struct rw_semaphore mod_lock; /* protects mod_idr */ |
| struct codetag_type_desc desc; |
| }; |
| |
| struct codetag_range { |
| struct codetag *start; |
| struct codetag *stop; |
| }; |
| |
| struct codetag_module { |
| struct module *mod; |
| struct codetag_range range; |
| }; |
| |
| static DEFINE_MUTEX(codetag_lock); |
| static LIST_HEAD(codetag_types); |
| |
| void codetag_lock_module_list(struct codetag_type *cttype, bool lock) |
| { |
| if (lock) |
| down_read(&cttype->mod_lock); |
| else |
| up_read(&cttype->mod_lock); |
| } |
| |
| bool codetag_trylock_module_list(struct codetag_type *cttype) |
| { |
| return down_read_trylock(&cttype->mod_lock) != 0; |
| } |
| |
| struct codetag_iterator codetag_get_ct_iter(struct codetag_type *cttype) |
| { |
| struct codetag_iterator iter = { |
| .cttype = cttype, |
| .cmod = NULL, |
| .mod_id = 0, |
| .ct = NULL, |
| }; |
| |
| return iter; |
| } |
| |
| static inline struct codetag *get_first_module_ct(struct codetag_module *cmod) |
| { |
| return cmod->range.start < cmod->range.stop ? cmod->range.start : NULL; |
| } |
| |
| static inline |
| struct codetag *get_next_module_ct(struct codetag_iterator *iter) |
| { |
| struct codetag *res = (struct codetag *) |
| ((char *)iter->ct + iter->cttype->desc.tag_size); |
| |
| return res < iter->cmod->range.stop ? res : NULL; |
| } |
| |
| struct codetag *codetag_next_ct(struct codetag_iterator *iter) |
| { |
| struct codetag_type *cttype = iter->cttype; |
| struct codetag_module *cmod; |
| struct codetag *ct; |
| |
| lockdep_assert_held(&cttype->mod_lock); |
| |
| if (unlikely(idr_is_empty(&cttype->mod_idr))) |
| return NULL; |
| |
| ct = NULL; |
| while (true) { |
| cmod = idr_find(&cttype->mod_idr, iter->mod_id); |
| |
| /* If module was removed move to the next one */ |
| if (!cmod) |
| cmod = idr_get_next_ul(&cttype->mod_idr, |
| &iter->mod_id); |
| |
| /* Exit if no more modules */ |
| if (!cmod) |
| break; |
| |
| if (cmod != iter->cmod) { |
| iter->cmod = cmod; |
| ct = get_first_module_ct(cmod); |
| } else |
| ct = get_next_module_ct(iter); |
| |
| if (ct) |
| break; |
| |
| iter->mod_id++; |
| } |
| |
| iter->ct = ct; |
| return ct; |
| } |
| |
| void codetag_to_text(struct seq_buf *out, struct codetag *ct) |
| { |
| if (ct->modname) |
| seq_buf_printf(out, "%s:%u [%s] func:%s", |
| ct->filename, ct->lineno, |
| ct->modname, ct->function); |
| else |
| seq_buf_printf(out, "%s:%u func:%s", |
| ct->filename, ct->lineno, ct->function); |
| } |
| |
| static inline size_t range_size(const struct codetag_type *cttype, |
| const struct codetag_range *range) |
| { |
| return ((char *)range->stop - (char *)range->start) / |
| cttype->desc.tag_size; |
| } |
| |
| static void *get_symbol(struct module *mod, const char *prefix, const char *name) |
| { |
| DECLARE_SEQ_BUF(sb, KSYM_NAME_LEN); |
| const char *buf; |
| void *ret; |
| |
| seq_buf_printf(&sb, "%s%s", prefix, name); |
| if (seq_buf_has_overflowed(&sb)) |
| return NULL; |
| |
| buf = seq_buf_str(&sb); |
| preempt_disable(); |
| ret = mod ? |
| (void *)find_kallsyms_symbol_value(mod, buf) : |
| (void *)kallsyms_lookup_name(buf); |
| preempt_enable(); |
| |
| return ret; |
| } |
| |
| static struct codetag_range get_section_range(struct module *mod, |
| const char *section) |
| { |
| return (struct codetag_range) { |
| get_symbol(mod, "__start_", section), |
| get_symbol(mod, "__stop_", section), |
| }; |
| } |
| |
| static const char *get_mod_name(__maybe_unused struct module *mod) |
| { |
| #ifdef CONFIG_MODULES |
| if (mod) |
| return mod->name; |
| #endif |
| return "(built-in)"; |
| } |
| |
| static int codetag_module_init(struct codetag_type *cttype, struct module *mod) |
| { |
| struct codetag_range range; |
| struct codetag_module *cmod; |
| int err; |
| |
| range = get_section_range(mod, cttype->desc.section); |
| if (!range.start || !range.stop) { |
| pr_warn("Failed to load code tags of type %s from the module %s\n", |
| cttype->desc.section, get_mod_name(mod)); |
| return -EINVAL; |
| } |
| |
| /* Ignore empty ranges */ |
| if (range.start == range.stop) |
| return 0; |
| |
| BUG_ON(range.start > range.stop); |
| |
| cmod = kmalloc(sizeof(*cmod), GFP_KERNEL); |
| if (unlikely(!cmod)) |
| return -ENOMEM; |
| |
| cmod->mod = mod; |
| cmod->range = range; |
| |
| down_write(&cttype->mod_lock); |
| err = idr_alloc(&cttype->mod_idr, cmod, 0, 0, GFP_KERNEL); |
| if (err >= 0) { |
| cttype->count += range_size(cttype, &range); |
| if (cttype->desc.module_load) |
| cttype->desc.module_load(cttype, cmod); |
| } |
| up_write(&cttype->mod_lock); |
| |
| if (err < 0) { |
| kfree(cmod); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_MODULES |
| void codetag_load_module(struct module *mod) |
| { |
| struct codetag_type *cttype; |
| |
| if (!mod) |
| return; |
| |
| mutex_lock(&codetag_lock); |
| list_for_each_entry(cttype, &codetag_types, link) |
| codetag_module_init(cttype, mod); |
| mutex_unlock(&codetag_lock); |
| } |
| |
| bool codetag_unload_module(struct module *mod) |
| { |
| struct codetag_type *cttype; |
| bool unload_ok = true; |
| |
| if (!mod) |
| return true; |
| |
| /* await any module's kfree_rcu() operations to complete */ |
| kvfree_rcu_barrier(); |
| |
| mutex_lock(&codetag_lock); |
| list_for_each_entry(cttype, &codetag_types, link) { |
| struct codetag_module *found = NULL; |
| struct codetag_module *cmod; |
| unsigned long mod_id, tmp; |
| |
| down_write(&cttype->mod_lock); |
| idr_for_each_entry_ul(&cttype->mod_idr, cmod, tmp, mod_id) { |
| if (cmod->mod && cmod->mod == mod) { |
| found = cmod; |
| break; |
| } |
| } |
| if (found) { |
| if (cttype->desc.module_unload) |
| if (!cttype->desc.module_unload(cttype, cmod)) |
| unload_ok = false; |
| |
| cttype->count -= range_size(cttype, &cmod->range); |
| idr_remove(&cttype->mod_idr, mod_id); |
| kfree(cmod); |
| } |
| up_write(&cttype->mod_lock); |
| } |
| mutex_unlock(&codetag_lock); |
| |
| return unload_ok; |
| } |
| #endif /* CONFIG_MODULES */ |
| |
| struct codetag_type * |
| codetag_register_type(const struct codetag_type_desc *desc) |
| { |
| struct codetag_type *cttype; |
| int err; |
| |
| BUG_ON(desc->tag_size <= 0); |
| |
| cttype = kzalloc(sizeof(*cttype), GFP_KERNEL); |
| if (unlikely(!cttype)) |
| return ERR_PTR(-ENOMEM); |
| |
| cttype->desc = *desc; |
| idr_init(&cttype->mod_idr); |
| init_rwsem(&cttype->mod_lock); |
| |
| err = codetag_module_init(cttype, NULL); |
| if (unlikely(err)) { |
| kfree(cttype); |
| return ERR_PTR(err); |
| } |
| |
| mutex_lock(&codetag_lock); |
| list_add_tail(&cttype->link, &codetag_types); |
| mutex_unlock(&codetag_lock); |
| |
| return cttype; |
| } |