Skip to content

feat(kpm): LKM-style cross-module symbol export and dependency tracking - #318

Open
JavSaia wants to merge 2 commits into
bmax121:mainfrom
JavSaia:feat/kpm-symbol-system
Open

JavSaia wants to merge 2 commits into
bmax121:mainfrom
JavSaia:feat/kpm-symbol-system

Conversation

@JavSaia

@JavSaia JavSaia commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

What

The KPM loader currently lets a module import symbols from the KP runtime and from the running kernel, but not from other KPMs, and it has no notion of a module dependency. This gives it the semantics of the kernel's own module loader.

Symbol resolution

One pipeline for both backends (kpimg and LKM):

  1. KP runtime table
  2. cross-KPM exports (.kpm.export)
  3. kf_/kv_ pointer slots
  4. kernel kallsyms

A module-local PLT/GOT arena keeps out-of-range AArch64 branches reachable — JUMP26/CALL26 and the GOT-style relocations that a real kernel module build emits. An unresolvable symbol now fails the load and names the symbol, instead of breaking silently at the first call.

Cross-module exports

KPM_EXPORT(sym) is the EXPORT_SYMBOL equivalent. The entry is emitted into the allocatable .kpm.export section, a later KPM references it with a plain extern, and the loader resolves it by name. Export entries are sanity-checked in setup_load_info and recorded in move_module.

Dependencies

Tracked the way the kernel tracks them:

  • a module whose exports are still imported refuses to unload (-EBUSY)
  • an importer releases its providers when it goes away
  • a failed load rolls back every reference it took
  • a full dependency table fails the load rather than succeeding silently, which would otherwise let a provider be freed under a live consumer

The module list and export tables are protected by the KP-owned spinlock helper (kp_private_spin_lock), which is the correct primitive here: it takes _raw_spin_lock_irqsave when the kernel symbol is available and otherwise masks interrupts locally, unlike a bare spin_lock in this context.

Robustness

ELF input is validated before use:

  • bounded shstrtab/symtab/name checks
  • COMMON and LTO symbols rejected
  • 64-bit-safe relocation overflow masks
  • REL sections rejected instead of quietly ignored

0.13.x KPMs load unchanged on both backends.

Tests

  • checks/test_kpm.c — host tests for the link arena, the resolution pipeline and the dependency policy.
  • checks/symbol_compat.py — cross-version gate: resolves every symbol referenced by the shipped demos (and any older KPMs passed on the command line) against both backends' export tables.
  • kpms/demo-exports and kpms/demo-import — provider and importer sides, end to end.
PASS: link arena, symbol resolution, dependency policy
PASS: every referenced symbol resolves on both backends

Not covered

Runtime behaviour (load/unload, cross-KPM import, -EBUSY refusal) has not been exercised on a device yet.

@JavSaia

JavSaia commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

 I've tested it and it works fine!

@JavSaia

JavSaia commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@Admirepowered

Give the KPM loader the semantics of the kernel's own module loader.

Symbol resolution
- One resolution pipeline for both backends: KP runtime table ->
  cross-KPM exports (.kpm.export) -> kf_/kv_ pointer slots -> kernel
  kallsyms.  A module-local PLT/GOT arena keeps out-of-range AArch64
  branches (JUMP26/CALL26 and GOT-style relocs) reachable, and an
  unresolvable symbol now fails the load with the offending name instead
  of breaking silently at first call.

Cross-module exports
- KPM_EXPORT(sym), the EXPORT_SYMBOL equivalent: the entry is emitted
  into the allocatable .kpm.export section, later KPMs reference it with
  a plain extern, and the loader resolves it by name.  Export entries are
  sanity-checked in setup_load_info and recorded in move_module.

Dependencies
- Dependencies are tracked like the kernel records them: a module whose
  exports are still imported refuses to unload (-EBUSY), an importer
  releases its providers when it goes away, and a failed load rolls back
  every reference it took.  A full dependency table fails the load rather
  than succeeding silently, which would let a provider be freed under a
  live consumer.

Robustness
- ELF input is validated before use: bounded shstrtab/symtab/name checks,
  COMMON and LTO symbols rejected, 64-bit-safe relocation overflow masks,
  and REL sections rejected instead of quietly ignored.
- Locking uses the KP-owned spinlock helper (kp_private_spin_lock) so the
  module list and export tables are consistent across CPUs.
- Version-independent: 0.13.x KPMs load unchanged on both backends.

Tests
- checks/test_kpm.c covers the link arena, the resolution pipeline and
  the dependency policy on the host.
- checks/symbol_compat.py is the cross-version gate: it resolves every
  symbol referenced by the shipped demos (and any older KPMs given on the
  command line) against both backends' export tables.
- kpms/demo-exports and kpms/demo-import demonstrate the provider and
  importer sides end to end.
@JavSaia
JavSaia force-pushed the feat/kpm-symbol-system branch from d92eee9 to b526a66 Compare October 1, 2026 11:28
@JavSaia JavSaia changed the title feat: LKM-style KPM symbol system, standalone SELinux hide, and credential hardening feat(kpm): LKM-style cross-module symbol export and dependency tracking Oct 1, 2026
@Admirepowered
Admirepowered requested a balanced review from Copilot October 1, 2026 11:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Dependency reference leaks, unload races, incomplete ELF bounds checks, and compatibility-gate false results must be corrected.

Review effort: Balanced
Findings: 5 High severity · 5 Medium severity

Open (10)
What changed in this PR

Adds LKM-style cross-KPM symbol exports, dependency tracking, and safer AArch64 relocation handling across both loader backends.

Changes:

  • Introduces export resolution, dependency accounting, and PLT/GOT link arenas.
  • Strengthens ELF validation and adds compatibility symbols.
  • Adds demonstrations, documentation, and host compatibility tests.
File Description
lkm/​kpm/​symbols.c Adds legacy ABI compatibility symbols.
lkm/​kpm/​relo.c Supports PLT/GOT relocations and rejects REL sections.
lkm/​kpm/​module.h Stores link, export, and dependency state.
lkm/​kpm/​module.c Implements LKM-backend loading and dependency logic.
lkm/​Kbuild Changes fallback version values.
kpms/​demo-import/​Makefile Builds the importer demonstration.
kpms/​demo-import/​import.lds Defines importer linker sections.
kpms/​demo-import/​import.c Demonstrates consuming an exported symbol.
kpms/​demo-exports/​Makefile Builds the provider demonstration.
kpms/​demo-exports/​exports.lds Defines provider linker sections.
kpms/​demo-exports/​exports.c Demonstrates exporting a symbol.
kernel/​patch/​module/​relo.c Adds kpimg PLT/GOT relocation support.
kernel/​patch/​module/​module.c Implements kpimg exports and dependencies.
kernel/​patch/​include/​module.h Extends kpimg module state.
kernel/​include/​kpmsymbol.h Defines the shared resolution pipeline.
kernel/​include/​kpmodule.h Adds the public KPM_EXPORT API.
kernel/​include/​kpmlink.h Implements the per-module link arena.
kernel/​include/​kpmdep.h Provides dependency-table helpers.
doc/​zh-CN/​module.md Documents exports in Chinese.
doc/​en/​module.md Documents exports in English.
checks/​test_kpm.c Tests linking, resolution, and dependency helpers.
checks/​symbol_compat.py Checks symbol compatibility across backends.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +547 to +556
Elf_Shdr *symbols = &info->sechdrs[info->index.sym];
if (symbols->sh_size % sizeof(Elf_Sym) || symbols->sh_link >= info->hdr->e_shnum) {
set_load_error(info, "invalid ELF symbol table");
return -ENOEXEC;
}
Elf_Shdr *strings = &info->sechdrs[symbols->sh_link];
if (strings->sh_type != SHT_STRTAB || !strings->sh_size) {
set_load_error(info, "invalid ELF symbol strings");
return -ENOEXEC;
}
Comment thread kernel/patch/module/module.c Outdated
Comment on lines +698 to +702
if (mod->export_refs) {
/* LKM rmmod semantics: refuse while other KPMs still import us. */
logkfe("module %s is in use by %u other KPM(s)\n", name, mod->export_refs);
rc = -EBUSY;
goto out;
Comment thread lkm/kpm/module.c Outdated
unsigned long found = 0;

if (!importer) return 0;
spin_lock(&module_lock);
Comment thread lkm/kpm/module.c
Comment on lines +705 to +714
Elf_Shdr *symbols = &info->sechdrs[info->index.sym];
if (symbols->sh_size % sizeof(Elf_Sym) || symbols->sh_link >= info->hdr->e_shnum) {
set_load_error(info, "invalid ELF symbol table");
return -ENOEXEC;
}
Elf_Shdr *strings = &info->sechdrs[symbols->sh_link];
if (strings->sh_type != SHT_STRTAB || !strings->sh_size) {
set_load_error(info, "invalid ELF symbol strings");
return -ENOEXEC;
}
Comment thread lkm/kpm/module.c Outdated
Comment on lines +1013 to +1017
if (mod->export_refs) {
/* LKM rmmod semantics: refuse while other KPMs still import us. */
logkfe("module %s is in use by %u other KPM(s)\n", name, mod->export_refs);
rc = -EBUSY;
goto out;
Comment thread checks/symbol_compat.py Outdated
Comment on lines +80 to +86
kpms = {}
for p in sorted((ROOT / 'kpms').glob('*/*.kpm')):
kpms[p] = 'current'
for arg in sys.argv[1:] or ([OLD_DEMOS] if OLD_DEMOS.exists() else []):
base = Path(arg)
for p in sorted(base.glob('*.kpm')):
kpms.setdefault(p, 'previous-generation')
Comment thread checks/symbol_compat.py Outdated
Comment on lines +88 to +90
providers = set()
for p in kpms:
providers |= elf_syms(p, 'global')
Comment thread kernel/patch/module/module.c Outdated
Comment on lines +223 to +230
if (kpm_dep_record((void **)importer->deps, &importer->dep_count, KPM_DEP_MAX, pos) < 0) {
/* Table full: fail the resolution so the KPM load aborts.
* Silently succeeding here would allow the provider to be
* unloaded while the consumer still holds its symbols. */
logke("dependency table full; cannot import %s from %s\n", name, pos->info.name);
goto out;
}
pos->export_refs++;
Comment thread lkm/Kbuild Outdated
Comment on lines +57 to +60
KP_LKM_MINOR := 14
endif
ifeq ($(strip $(KP_LKM_PATCH)),)
KP_LKM_PATCH := 3
KP_LKM_PATCH := 0
Comment thread lkm/kpm/module.c Outdated
Comment on lines +388 to +394
if (kpm_dep_record((void **)importer->deps, &importer->dep_count, KPM_DEP_MAX, pos) < 0) {
/* Fail resolution: silently succeeding would allow the
* provider to be unloaded while consumer still uses it. */
logke("dependency table full; cannot import %s from %s\n", name, pos->info.name);
goto out;
}
pos->export_refs++;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

现在呢,你这家伙

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants