[PATCH 4/4] objtool/klp: strip klp-induced module dependencies from livepatch modules
Joe Lawrence <[email protected]> Mon, 20 Jul 2026 10:56:58 -0400
| Newsgroups | org.kernel.vger.live-patching |
|---|---|
| Message-ID | <[email protected]> |
The klp-diff pipeline creates weak UNDEF placeholder symbols for cross-module references that will become klp-relocations. When modpost runs on the linked livepatch .ko, it sees these UNDEF symbols, resolves them via Module.symvers, and adds the providing modules to the depends= field in .modinfo. These dependencies are unnecessary: klp-relocations are resolved by the livepatch infrastructure at patch-enable time, not by the module loader. Adding hard module dependencies defeats late-module patching, since the kernel will refuse to load the livepatch module unless all depended modules are already present. Fix this by having klp-post-link strip any depends= entry that is only the result of klp-symbol (.klp.sym.<mod>.*). The check is conservative: if any remaining SHN_UNDEF symbol matches a known export name from that module, the dependency is kept (indicating a legitimate non-KLP reference). Assisted-by: Claude:claude-opus-4-6 Signed-off-by: Joe Lawrence <[email protected]> --- tools/objtool/klp-post-link.c | 175 ++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/tools/objtool/klp-post-link.c b/tools/objtool/klp-post-link.c index 141b0a46ca52..8bdec0b38f65 100644 --- a/tools/objtool/klp-post-link.c +++ b/tools/objtool/klp-post-link.c @@ -19,6 +19,178 @@ #include <objtool/util.h> #include <linux/livepatch_external.h> +static bool modnames_match(const char *a, const char *b, size_t len) +{ + for (size_t i = 0; i < len; i++) { + char ca = a[i] == '-' ? '_' : a[i]; + char cb = b[i] == '-' ? '_' : b[i]; + if (ca != cb) + return false; + } + return true; +} + +/* + * Extract the export name length from a KLP symbol name suffix: "<name>,<pos>" + */ +static size_t klp_sym_name_len(const char *name_start) +{ + const char *comma = strrchr(name_start, ','); + return comma ? (size_t)(comma - name_start) : strlen(name_start); +} + +/* + * Check if the dependency on 'mod' is purely from KLP relocations. + * + * Returns true only if: + * (a) .klp.sym.<mod>.* symbols exist (the dep is KLP-related), AND + * (b) no remaining SHN_UNDEF symbol shares a name with any of those exports + * (which would indicate a non-KLP reference to the same module) + */ +static bool is_klp_only_dep(struct elf *elf, const char *mod) +{ + const char *exports[1024]; + size_t export_lens[1024]; + int nr_exports = 0; + struct symbol *sym; + size_t prefix_len = strlen(KLP_SYM_PREFIX); + + /* Collect export names from .klp.sym.<mod>.* symbols */ + for_each_sym(elf, sym) { + const char *name = sym->name; + const char *dot; + + if (!strstarts(name, KLP_SYM_PREFIX)) + continue; + + dot = strchr(name + prefix_len, '.'); + if (!dot) + continue; + + if (strlen(mod) != (size_t)(dot - name - prefix_len) || + !modnames_match(name + prefix_len, mod, dot - name - prefix_len)) + continue; + + if (nr_exports >= 1024) { + WARN("too many KLP exports for module %s, skipping dependency stripping", mod); + return false; + } + + exports[nr_exports] = dot + 1; + export_lens[nr_exports] = klp_sym_name_len(dot + 1); + nr_exports++; + } + + if (!nr_exports) + return false; + + /* + * Verify no remaining UNDEF symbol matches an export name from this + * module. After fix_klp_relocs(), KLP placeholder symbols have been + * converted to SHN_LIVEPATCH. Any leftover UNDEF with a matching name + * indicates a legitimate non-KLP dependency. + */ + for_each_sym(elf, sym) { + const char *name = sym->name; + + if (sym->sym.st_shndx != SHN_UNDEF) + continue; + if (!name || !*name) + continue; + + for (int i = 0; i < nr_exports; i++) { + if (strlen(name) == export_lens[i] && + !strncmp(name, exports[i], export_lens[i])) + return false; + } + } + + return true; +} + +/* + * Remove modules from .modinfo depends= that are only referenced via KLP + * relocations. These dependencies are an artifact of the placeholder symbols + * created by klp-diff for the linker/objtool and should not constrain module + * load ordering (which would break late-module patching). + */ +static int fix_modinfo_depends(struct elf *elf) +{ + struct section *sec; + char *data, *data_end, *depends = NULL; + char *new_depends, *p; + char *tok, *save; + char *dep_val, *dep_copy; + size_t old_entry_len, new_entry_len; + + sec = find_section_by_name(elf, ".modinfo"); + if (!sec || !sec->data || !sec->data->d_buf) + return 0; + + data = sec->data->d_buf; + data_end = data + sec->data->d_size; + + for (char *s = data; s < data_end; s += strlen(s) + 1) { + if (strstarts(s, "depends=")) { + depends = s; + break; + } + } + + if (!depends) + return 0; + + dep_val = depends + strlen("depends="); + if (!*dep_val) + return 0; + + new_depends = strdup(dep_val); + if (!new_depends) { + ERROR_GLIBC("strdup"); + return -1; + } + + dep_copy = strdup(dep_val); + if (!dep_copy) { + ERROR_GLIBC("strdup"); + free(new_depends); + return -1; + } + + p = new_depends; + *p = '\0'; + + tok = strtok_r(dep_copy, ",", &save); + while (tok) { + if (!is_klp_only_dep(elf, tok)) { + if (p != new_depends) + *p++ = ','; + strcpy(p, tok); + p += strlen(tok); + } + tok = strtok_r(NULL, ",", &save); + } + + old_entry_len = strlen(depends) + 1; + new_entry_len = strlen("depends=") + strlen(new_depends) + 1; + + if (strcmp(dep_val, new_depends) && new_entry_len <= old_entry_len) { + snprintf(depends, old_entry_len, "depends=%s", new_depends); + memset(depends + new_entry_len, '\0', + old_entry_len - new_entry_len); + mark_sec_changed(elf, sec, true); + } else if (new_entry_len > old_entry_len) { + ERROR("new depends= string exceeds original size (shouldn't happen)"); + free(dep_copy); + free(new_depends); + return -1; + } + + free(dep_copy); + free(new_depends); + return 0; +} + static int fix_klp_relocs(struct elf *elf) { struct section *symtab, *klp_relocs; @@ -163,6 +335,9 @@ int cmd_klp_post_link(int argc, const char **argv) if (fix_klp_relocs(elf)) return -1; + if (fix_modinfo_depends(elf)) + return -1; + if (elf_write(elf)) return -1; -- 2.54.0