Skip to content

feat(handlers): kernel_module_disable direct kernel IO (Phase 6, PR 3/3)#111

Merged
remyluslosius merged 1 commit into
mainfrom
feat/kernelio-module
Jun 19, 2026
Merged

feat(handlers): kernel_module_disable direct kernel IO (Phase 6, PR 3/3)#111
remyluslosius merged 1 commit into
mainfrom
feat/kernelio-module

Conversation

@remyluslosius

Copy link
Copy Markdown
Contributor

Phase 6, PR 3 of 3 — completes Phase 6. kernel_module_disable now writes the /etc/modprobe.d blacklist drop-in atomically (fsatomic) and unloads the running module via delete_module(2), instead of the shell printf + modprobe -r pipeline, in agent mode.

What

  • kernelio: DeleteModule (delete_module(2), O_NONBLOCK, best-effort; ENOENTErrModuleNotLoaded). ModuleTransport capability (FileTransport + DeleteModule). FakeSysctlTransport gains DeleteModule + a recorder.
  • local.Transport implements ModuleTransport.
  • kernel_module_disable Apply/Capture/Rollback gain a kernel-IO branch via transport.(kernelio.ModuleTransport), falling back to the modprobe + shell path. The runtime unload is best-effort on both paths (the persistent blacklist+install /bin/true is the load-bearing change). Both paths write a byte-identical drop-in and an identical PreState shape.
  • spec kernelio-module (Tier 1, 4 ACs) + tests (apply writes+unloads, unload-failure-is-best-effort, remove-when-absent, restore-when-existed, shell fallback).

Verification

go test ./... green; go build ./... clean; golangci-lint 0; comment-lint clean; specter sync all pass.

Failure-mode analysis

  1. Wrong in prod? Removing an operator's pre-existing blacklist, or failing to keep a module out. Mitigated: atomic write; rollback restores prior content when the file existed at capture and removes it only when it didn't (file_existed flag) — a host with an operator blacklist is left exactly as found; the load-bearing change is the persistent blacklist, not the best-effort unload.
  2. Captured-state sufficiency: rollback consumes file_existed + prior_content (same tuple Capture records on both paths); a read error → ErrCaptureIncomplete.
  3. Edge case / gated: delete_module(2) removes only the named module (not deps) — adequate since the blacklist is load-bearing; in-use/not-loaded is a best-effort no-op. Module reload on rollback is not attempted on either path (may need reboot) — disclosed, shared. The live delete_module(2) success path needs root + a loaded module → ⚠️ live validation required; kensa-fuzz + two-human rollback-handler review (CONTRIBUTING) are the founder's gate.

🤖 Generated with Claude Code

Third and final direct-kernel-IO handler port, completing Phase 6.
kernel_module_disable now writes the /etc/modprobe.d blacklist drop-in
atomically (fsatomic) and unloads the running module via delete_module(2),
instead of the shell printf + modprobe -r pipeline, when running in
agent mode.

- kernelio: DeleteModule (delete_module(2), O_NONBLOCK, best-effort;
  ENOENT → ErrModuleNotLoaded). ModuleTransport capability (FileTransport
  + DeleteModule). FakeSysctlTransport gains DeleteModule + a recorder so
  it satisfies ModuleTransport.
- local.Transport implements ModuleTransport (DeleteModule delegate).
- kernel_module_disable Apply/Capture/Rollback gain a kernel-IO branch
  selected by transport.(kernelio.ModuleTransport), falling back to the
  modprobe + shell file-write path. The runtime unload is best-effort on
  BOTH paths (the persistent blacklist+install-/bin/true entry is the
  load-bearing change). Both paths write a byte-identical drop-in and
  record an identical PreState shape.
- spec kernelio-module (Tier 1, 4 ACs); DeleteModule guard test + handler
  round-trip tests (apply writes+unloads, unload-failure-is-best-effort,
  remove-when-absent, restore-when-existed, shell fallback). Existing
  shell-path tests unchanged.

Failure-mode analysis:
1. What could this do wrong in production? Removing an operator's
   pre-existing blacklist file, or failing to keep a module out.
   Mitigated: the blacklist write is atomic (fsatomic); rollback restores
   prior content when the file existed at capture and removes it only when
   it did not (file_existed flag), so a host with an operator blacklist is
   left exactly as found; the load-bearing change is the persistent
   blacklist, not the best-effort unload.
2. Captured-state sufficiency: rollback consumes file_existed +
   prior_content (same tuple Capture records on both paths). A capture
   read error surfaces ErrCaptureIncomplete rather than an empty prior.
3. Edge case not safe for / gated: delete_module(2) removes only the named
   module (not deps) — adequate since the blacklist is load-bearing; a
   module in use (EBUSY) or not loaded is a best-effort no-op, never a
   failed Apply. Module RELOAD on rollback is not attempted on either path
   (may need reboot) — a disclosed, shared limitation. The live
   delete_module(2) success path needs root + a loaded module and is left
   to live validation; kensa-fuzz atomicity + the two-human
   rollback-handler review (CONTRIBUTING) remain the founder's gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@remyluslosius remyluslosius merged commit 2ce8d67 into main Jun 19, 2026
18 checks passed
@remyluslosius remyluslosius deleted the feat/kernelio-module branch June 19, 2026 22:19
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.

1 participant