[wrong8007] Wrong Boot

docs · development(9)

Development

How to implement and register a new trigger, including its lifecycle, execution contract, calling-context rules, parameter handling and testing requirements.

This page documents the contract a new trigger must follow. It assumes familiarity with LKM development and basic kernel subsystems.

Responsibilities

componentowns
coremodule init/teardown, parameter validation for EXEC, deferred execution via workqueue, invoking call_usermodehelper()
triggerdetecting one condition; deciding whether to call wrong8007_activate(); never executing anything itself

Triggers are intentionally stateless or minimally stateful: the keyboard trigger's match counter and the network trigger's heartbeat timestamp are the only meaningful per-trigger state in the current source and both are guarded by a spinlock.

struct wrong8007_trigger

struct wrong8007_trigger {
    const char *name;
    int (*init)(void);
    void (*exit)(void);
};

include/wrong8007.h

fieldcontract
init()Must return 0 on success or a negative errno on failure. Called once per module load, in array order. Should validate its own module parameters and refuse to proceed on malformed input rather than guessing at intent.
exit()Must be safe to call even if init() partially failed or was never called for this trigger (e.g. because an earlier trigger's init() failed first). Called once at module unload and during unwind after a failed load.

Triggers must not call wrong8007_activate() from either init() or exit(); only from a live callback (notifier, hook, timer) after successful initialization.

Registering a trigger

Add the trigger's exported symbol to the core's static array:

extern struct wrong8007_trigger example_trigger;

static struct wrong8007_trigger *triggers[] = {
    &keyboard_trigger,
    &usb_trigger,
    &network_trigger,
    &example_trigger,
};

core/wrong8007.c

On load failure partway through this array, the core calls exit() for every trigger whose init() already succeeded, in reverse order, before returning the error from module_init.

Scheduling execution

Triggers must not call call_usermodehelper() or anything execution-related directly. The only sanctioned path from a trigger callback to execution is:

wrong8007_activate();

This keeps execution context and ordering entirely inside the core, regardless of which trigger fired or what context its callback runs in.

Calling-context rules

  • wrong8007_activate() is safe to call from process context (keyboard, usb) or softirq context (network's netfilter callback); it performs a single atomic compare-and-swap and, at most, a schedule_work() call, neither of which sleeps.
  • A trigger's init()/exit() run in process context (module load/unload) and may sleep; allocation with GFP_KERNEL is fine there, as seen in keyboard.c's kstrdup() of the phrase parameter.
  • A trigger's live callback must not sleep if it can run in atomic or softirq context. The network trigger's nf_hook_fn never blocks; all header access is preceded by pskb_may_pull() and pointers are re-read after each pull rather than assumed stable.
  • Avoid allocation in hot paths. The keyboard trigger's per-keystroke work is a table lookup and a byte compare under a spinlock; the network trigger's per-packet work is bounded header parsing with no allocation.

Parameter handling

Triggers may define module parameters, but must follow these rules:

  • Validate them inside init(), not lazily on first use.
  • Return an error for invalid configuration.
  • Do not silently reinterpret malformed configuration.
  • Treat parameters as read-only after initialization.
  • Prefer strict parsing over permissive behavior.
  • Allocate derived state during initialization rather than repeatedly parsing configuration in hot paths.
  • Release all trigger-owned allocations during teardown.

Example:

if (!param || !*param)
    return -EINVAL;

Logging

Use the project's logging macros consistently:

macrouse
wb_dbgDevelopment-only debug output, compiled in with -DDEBUG.
wb_infoInitialization and lifecycle events (module loaded/unloaded, trigger initialized).
wb_warnRecoverable or potentially unsafe conditions.
wb_errInitialization or runtime errors.

Avoid logging sensitive configuration values unless there is a clear debugging reason to do so.

Testing new triggers

Recommended workflow:

  1. Load the module with only the new trigger's parameters set (preferably).
  2. Verify successful initialization.
  3. Verify invalid configuration causes initialization failure.
  4. Verify exit() unregisters all resources cleanly.
  5. Verify activation in isolation before combining triggers.
  6. Confirm rmmod unloads cleanly (make remove), with no leaked memory or dangling notifier registration.
  7. Verify repeated and concurrent activation attempts preserve one-shot behavior.
  8. Combine the new trigger with existing triggers only after its isolated behavior is understood.

A trigger should be tested both for the condition that activates it and for the conditions that must not activate it.

Code style

Follow Linux kernel coding conventions.

Prefer:

  1. Well-defined ownership,
  2. Clear control flow,
  3. Strict validation,
  4. No coupling between triggers,
  5. Comments for non-obvious kernel behavior.

A trigger should be tested both for the condition that activates it and for the conditions that must not activate it.

If a trigger is hard to reason about in isolation, it is not ready to be included yet.