static unsigned long _stp_umodule_relocate(const char *path, unsigned long offset, struct task_struct *tsk) { unsigned i; unsigned long vm_start = 0; dbug_sym(1, "[%d] %s, %lx\n", tsk->pid, path, offset); for (i = 0; i < _stp_num_modules; i++) { struct _stp_module *m = _stp_modules[i]; if (strcmp(path, m->path) || m->num_sections != 1) continue; if (!strcmp(m->sections[0].name, ".absolute")) return offset; if (strcmp(m->sections[0].name, ".dynamic")) continue; if (stap_find_vma_map_info_user(tsk->group_leader, m, &vm_start, NULL, NULL) == 0) { offset += vm_start; dbug_sym(1, "address=%lx\n", offset); return offset; } } return 0; }
/* Validate user module based on build-id (if present) */ static int _stp_usermodule_check(struct task_struct *tsk, const char *path_name, unsigned long addr) { struct _stp_module *m = NULL; unsigned long notes_addr; unsigned i, j; unsigned char practice_id_bits[MAXSTRINGLEN]; unsigned long vm_end = 0; #ifdef STP_NO_BUILDID_CHECK return 0; #endif for (i = 0; i < _stp_num_modules; i++) { m = _stp_modules[i]; if (strcmp(path_name, _stp_modules[i]->path) != 0) continue; if (m->build_id_len > 0) { int ret, build_id_len; notes_addr = addr + m->build_id_offset /* + m->module_base */; dbug_sym(1, "build-id validation [%d %s] address=%#lx build_id_offset=%#lx\n", tsk->pid, m->path, addr, m->build_id_offset); if (notes_addr <= addr) { _stp_warn ("build-id address %lx < base %lx\n", notes_addr, addr); continue; } return _stp_build_id_check (m, notes_addr, tsk); } } return 0; }
/* Iterate over _stp_modules, looking for a kernel module of given name. Run build-id checking for it. Return 0 on ok. */ static int _stp_kmodule_check (const char *name) { struct _stp_module *m = NULL; unsigned long notes_addr, base_addr; unsigned i,j; #ifdef STP_NO_BUILDID_CHECK return 0; #endif for (i = 0; i < _stp_num_modules; i++) { m = _stp_modules[i]; if (strcmp (name, m->name)) continue; if (m->build_id_len > 0 && m->notes_sect != 0) { dbug_sym(1, "build-id validation [%s]\n", m->name); /* notes end address */ notes_addr = m->notes_sect + m->build_id_offset; base_addr = m->notes_sect; if (notes_addr <= base_addr) { /* shouldn't happen */ _stp_warn ("build-id address %lx < base %lx\n", notes_addr, base_addr); continue; } return _stp_build_id_check (m, notes_addr, NULL); } /* end checking */ } /* end loop */ return 0; /* name not found */ }
static void _stp_do_relocation(const char __user *buf, size_t count) { static struct _stp_msg_relocation msg; /* by protocol, never concurrently used */ static struct _stp13_msg_relocation msg13; /* ditto */ /* PR12612: Let's try to be compatible with systemtap modules being compiled by new systemtap, but loaded (staprun'd) by an older systemtap runtime. The only known incompatilibility is that we get an older, smaller, relocation message. So here we accept both sizes. */ if (sizeof(msg) == count) { /* systemtap 1.4+ runtime */ if (unlikely(copy_from_user (& msg, buf, count))) return; } else if (sizeof(msg13) == count) { /* systemtap 1.3- runtime */ if (unlikely(copy_from_user (& msg13, buf, count))) return; #if STP_MODULE_NAME_LEN <= STP13_MODULE_NAME_LEN #error "STP_MODULE_NAME_LEN should not be smaller than STP13_MODULE_NAME_LEN" #endif strlcpy (msg.module, msg13.module, STP13_MODULE_NAME_LEN); strlcpy (msg.reloc, msg13.reloc, STP13_MODULE_NAME_LEN); msg.address = msg13.address; } else { errk ("STP_RELOCATE message size mismatch (%lu or %lu vs %lu)\n", (long unsigned) sizeof(msg), (long unsigned) sizeof (msg13), (long unsigned) count); return; } dbug_sym(2, "relocate (%s %s 0x%lx)\n", msg.module, msg.reloc, (unsigned long) msg.address); /* Detect actual kernel load address. */ if (!strcmp ("kernel", msg.module) && !strcmp ("_stext", msg.reloc)) { dbug_sym(2, "found kernel _stext load address: 0x%lx\n", (unsigned long) msg.address); if (_stp_kretprobe_trampoline != (unsigned long) -1) _stp_kretprobe_trampoline += (unsigned long) msg.address; } _stp_kmodule_update_address(msg.module, msg.reloc, msg.address); }
/* Returns absolute address of offset into kernel module/section. Returns zero when module and section couldn't be found (aren't in memory yet). */ static unsigned long _stp_kmodule_relocate(const char *module, const char *section, unsigned long offset) { unsigned i, j; dbug_sym(1, "%s, %s, %lx\n", module, section, offset); /* absolute, unrelocated address */ if (!module || !strcmp(section, "") ||_stp_num_modules == 0) { return offset; } for (i = 0; i < _stp_num_modules; i++) { struct _stp_module *m = _stp_modules[i]; if (strcmp(module, m->name)) /* duplication apprx. not possible for kernel */ continue; for (j = 0; j < m->num_sections; j++) { struct _stp_section *s = &m->sections[j]; if (!strcmp(section, s->name)) { /* mod and sec name match. tsk should match dynamic/static. */ if (s->static_addr != 0) { unsigned long addr = offset + s->static_addr; dbug_sym(1, "address=%lx\n", addr); return addr; } else { /* static section, not in memory yet? */ dbug_sym(1, "section %s, not in memory yet?", s->name); return 0; } } } } return 0; }
/* Update the given module/section's offset value. Assume that there is no need for locking or for super performance. NB: this is only for kernel modules, which exist singly at run time. User-space modules (executables, shared libraries) exist at different addresses in different processes, so are tracked in the _stp_tf_vma_map. */ static void _stp_kmodule_update_address(const char* module, const char* reloc, /* NULL="all" */ unsigned long address) { unsigned mi, si; for (mi=0; mi<_stp_num_modules; mi++) { const char *note_sectname = ".note.gnu.build-id"; if (strcmp (_stp_modules[mi]->name, module)) continue; if (reloc && !strcmp (note_sectname, reloc)) { dbug_sym(1, "module %s special section %s address %#lx\n", _stp_modules[mi]->name, note_sectname, address); _stp_modules[mi]->notes_sect = address; /* cache this particular address */ } for (si=0; si<_stp_modules[mi]->num_sections; si++) { if (reloc && strcmp (_stp_modules[mi]->sections[si].name, reloc)) continue; else { dbug_sym(1, "module %s section %s address %#lx\n", _stp_modules[mi]->name, _stp_modules[mi]->sections[si].name, address); _stp_modules[mi]->sections[si].static_addr = address; if (reloc) break; else continue; /* wildcarded - will have more hits */ } } /* loop over sections */ } /* loop over modules */ }
/* Validate all-modules + kernel based on build-id (if present). * The completed case is the following combination: * Debuginfo Module Kernel * X X * has build-id/not unloaded has build-id/not * loaded && (has build-id/not) * * NB: build-id exists only if ld>=2.18 and kernel>= 2.6.23 */ static int _stp_module_check(void) { struct _stp_module *m = NULL; unsigned long notes_addr, base_addr; unsigned i,j; int rc = 0; #ifdef STP_NO_BUILDID_CHECK return 0; #endif for (i = 0; i < _stp_num_modules; i++) { m = _stp_modules[i]; if (m->build_id_len > 0 && m->notes_sect != 0) { dbug_sym(1, "build-id validation [%s]\n", m->name); /* kernel only */ /* skip userspace program */ if (m->name[0] != '/') continue; /* notes end address */ if (!strcmp(m->name, "kernel")) { notes_addr = _stp_kmodule_relocate("kernel", "_stext", m->build_id_offset); base_addr = _stp_kmodule_relocate("kernel", "_stext", 0); } else { notes_addr = m->notes_sect + m->build_id_offset; base_addr = m->notes_sect; } if (notes_addr <= base_addr) { /* shouldn't happen */ _stp_warn ("build-id address %lx <= base %lx\n", notes_addr, base_addr); continue; } rc |= _stp_build_id_check (m, notes_addr, NULL); } /* end checking */ } /* end loop */ return rc; }
/* Validate user module based on build-id (if present) */ static int _stp_usermodule_check(struct task_struct *tsk, const char *path_name, unsigned long addr) { struct _stp_module *m = NULL; unsigned long notes_addr; unsigned i, j; unsigned char practice_id_bits[MAXSTRINGLEN]; unsigned long vm_end = 0; #ifdef STP_NO_BUILDID_CHECK return 0; #endif WARN_ON(!path_name || path_name[0]!='/'); // user-space only for (i = 0; i < _stp_num_modules; i++) { m = _stp_modules[i]; /* PR16406 must be unique userspace name (/-prefixed path); it's also in m->name */ if (strcmp(path_name, m->path) != 0) continue; if (m->build_id_len > 0) { int ret, build_id_len; notes_addr = addr + m->build_id_offset /* + m->module_base */; dbug_sym(1, "build-id validation [%d %s] address=%#lx build_id_offset=%#lx\n", tsk->pid, m->path, addr, m->build_id_offset); if (notes_addr <= addr) { _stp_warn ("build-id address %lx < base %lx\n", notes_addr, addr); continue; } return _stp_build_id_check (m, notes_addr, tsk); } } return 0; /* not found */ }
/* Return (user) module in which the the given addr falls. Returns NULL when no module can be found that contains the addr. Fills in vm_start (addr where module is mapped in) and (base) name of module when given. Note that user modules always have exactly one section (.dynamic or .absolute). */ static struct _stp_module *_stp_umod_lookup(unsigned long addr, struct task_struct *task, const char **name, unsigned long *vm_start, unsigned long *vm_end) { void *user = NULL; #ifdef CONFIG_COMPAT /* Handle 32bit signed values in 64bit longs, chop off top bits. */ if (test_tsk_thread_flag(task, TIF_32BIT)) addr &= ((compat_ulong_t) ~0); #endif if (stap_find_vma_map_info(task->group_leader, addr, vm_start, vm_end, name, &user) == 0) if (user != NULL) { struct _stp_module *m = (struct _stp_module *)user; dbug_sym(1, "found module %s at 0x%lx\n", m->path, vm_start ? *vm_start : 0); return m; } return NULL; }
/* Iterate over _stp_modules, looking for a kernel module of given name. Run build-id checking for it. Return 0 on ok. */ static int _stp_kmodule_check (const char *name) { struct _stp_module *m = NULL; unsigned long notes_addr, base_addr; unsigned i,j; #ifdef STP_NO_BUILDID_CHECK return 0; #endif WARN_ON(!name || name[0]=='/'); // non-userspace only for (i = 0; i < _stp_num_modules; i++) { m = _stp_modules[i]; /* PR16406 must be unique kernel module name (non-/-prefixed path) */ if (strcmp (name, m->name)) continue; if (m->build_id_len > 0 && m->notes_sect != 0) { dbug_sym(1, "build-id validation [%s]\n", m->name); /* notes end address */ notes_addr = m->notes_sect + m->build_id_offset; base_addr = m->notes_sect; if (notes_addr <= base_addr) { /* shouldn't happen */ _stp_warn ("build-id address %lx < base %lx\n", notes_addr, base_addr); continue; } return _stp_build_id_check (m, notes_addr, NULL); } /* end checking */ } /* end loop */ return 0; /* not found */ }