Skip to content

Instantly share code, notes, and snippets.

@float64co
Created March 20, 2026 19:40
Show Gist options
  • Select an option

  • Save float64co/8ae3017a8d79ce1aefb82e6673a7cd43 to your computer and use it in GitHub Desktop.

Select an option

Save float64co/8ae3017a8d79ce1aefb82e6673a7cd43 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT-Modern-Variant
/*
* fugue.c — Unified Security Runtime for Security-Sensitive Kernel Modules
*
* Condenses ten protective subsystems into one coherent LKM:
*
* A. Load-Gate: Ed25519 signature verification on every .ko before init runs
* B. Build Provenance: Signed ELF note carries compiler/header/git hash; reject
* modules built outside an approved environment
* C. Symbol Firewall: Per-module declared symbol manifest enforced via kprobes;
* undeclared kernel API calls are blocked at the call site
* D. Text Integrity: .text pages marked RO after load; periodic SHA-256
* re-verification; mismatch → module kill or panic
* E. Hook Registry: HMAC-signed ledger of every netfilter/LSM/kprobe/ftrace
* hook; any unledgered hook change is an immediate alert
* F. Audit Log: Append-only HMAC-chained ring in reserved physical RAM;
* sealed on suspicious unload; exposed via /dev/fugue_log
* G. Unload Guard: delete_module requires a TPM2-sealed time-limited token
* delivered via ioctl; root alone is not sufficient
* H. Call Isolator: Cross-module calls traverse a trampoline that checks
* caller module identity against an allowed-callers manifest
* I. Developer Sandbox: Optional load-mode flag: I/O trapped, network namespaced
* away, kmalloc red-zoned, quota-limited; graduates to
* production only with a dual-signed clearance token
* J. Canary Injector: kretprobe every exported function for per-module 64-bit
* stack canaries; kmalloc/kfree wrapped with guard pages
*
* Build:
* make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
*
* Kernel version target: 6.x
*
* Policy blob at /etc/fugue/policy.bin (signed, described below).
* TPM token delivery via ioctl(fd, FUGUE_IOC_UNLOAD_TOKEN, &tok).
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/rwsem.h>
#include <linux/rbtree.h>
#include <linux/list.h>
#include <linux/kprobes.h>
#include <linux/ftrace.h>
#include <linux/netfilter.h>
#include <linux/lsm_hooks.h>
#include <linux/set_memory.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/highmem.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/err.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/ioctl.h>
#include <linux/mutex.h>
#include <linux/percpu.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <linux/memblock.h>
#include <linux/elf.h>
#include <linux/kallsyms.h>
#include <linux/nsproxy.h>
#include <linux/net.h>
#include <linux/tpm.h>
#include <crypto/hash.h>
#include <crypto/sha2.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Float64");
MODULE_DESCRIPTION("fugue: unified LKM security runtime (A-J)");
MODULE_VERSION("0.0.1");
/* =========================================================================
* Constants and tunables
* ========================================================================= */
#define FUGUE_MAX_MODULES 64
#define FUGUE_MAX_SYMBOLS 256 /* per-module symbol manifest */
#define FUGUE_MAX_HOOKS 512 /* total tracked hooks across all modules */
#define FUGUE_MAX_CALLERS 32 /* per-module allowed-callers list */
#define FUGUE_LOG_ENTRIES 4096 /* ring log depth */
#define FUGUE_CANARY_MAGIC 0xC17ADE1C17ADE1CULL
#define FUGUE_TEXT_CHECK_HZ (60 * HZ) /* re-verify .text every 60 s */
#define FUGUE_TOKEN_TTL_SEC 30 /* unload token validity window */
#define FUGUE_HMAC_LEN 32 /* SHA-256 HMAC output */
#define FUGUE_SIG_LEN 64 /* Ed25519 signature */
#define FUGUE_HASH_LEN 32 /* SHA-256 digest */
/* /dev node name */
#define FUGUE_DEV_NAME "fugue_log"
/* =========================================================================
* ioctl interface (userspace: #include this header or copy the defines)
* ========================================================================= */
#define FUGUE_IOC_MAGIC 'C'
/* Submit a TPM-sealed unload token for module <name> */
struct fugue_unload_token {
char mod_name[MODULE_NAME_LEN];
u8 hmac[FUGUE_HMAC_LEN]; /* HMAC-SHA256(mod_name || timestamp || nonce) */
u64 timestamp_sec;
u8 nonce[16];
u8 tpm_quote[256]; /* TPM2_Quote blob covering the above */
};
/* Graduate a sandboxed module to production mode */
struct fugue_graduate_token {
char mod_name[MODULE_NAME_LEN];
u8 sig_dev1[FUGUE_SIG_LEN]; /* Ed25519 sig from developer key 1 */
u8 sig_dev2[FUGUE_SIG_LEN]; /* Ed25519 sig from developer key 2 */
};
#define FUGUE_IOC_UNLOAD_TOKEN _IOW(FUGUE_IOC_MAGIC, 1, struct fugue_unload_token)
#define FUGUE_IOC_GRADUATE _IOW(FUGUE_IOC_MAGIC, 2, struct fugue_graduate_token)
#define FUGUE_IOC_DUMP_HOOKS _IOR(FUGUE_IOC_MAGIC, 3, unsigned int)
#define FUGUE_IOC_SEAL_LOG _IO (FUGUE_IOC_MAGIC, 4)
/* =========================================================================
* § A/B Policy database (loaded from signed /etc/fugue/policy.bin)
* =========================================================================
*
* Binary layout of policy.bin:
* [4B magic "CTDL"] [4B version] [4B entry_count]
* entry_count × fugue_policy_entry
* [64B Ed25519 signature over everything above]
*
* Kernel-resident public key is compiled in below (replace with real key).
*/
/* Compiled-in Ed25519 public key (32 bytes). Replace with real key material. */
static const u8 fugue_pubkey[32] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
/* Second developer key for dual-sign graduation (subsystem I). */
static const u8 fugue_pubkey2[32] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
/* HMAC key for audit log chaining (subsystem F). Generated at boot. */
static u8 fugue_log_hmac_key[32];
struct fugue_symbol_entry {
char name[64];
unsigned long addr; /* resolved at policy-load time */
};
struct fugue_caller_entry {
char mod_name[MODULE_NAME_LEN];
};
struct fugue_policy_entry {
char mod_name[MODULE_NAME_LEN];
/* A: expected Ed25519 signature of the raw .ko ELF image */
u8 ko_sig[FUGUE_SIG_LEN];
/* B: SHA-256 of (compiler_path || kernel_header_tree || cflags || git_sha) */
u8 build_env_hash[FUGUE_HASH_LEN];
/* C: symbol manifest */
u32 sym_count;
struct fugue_symbol_entry syms[FUGUE_MAX_SYMBOLS];
/* G: must this module require a TPM token to unload? */
bool unload_guarded;
/* H: which modules may call into this one? */
u32 caller_count;
struct fugue_caller_entry callers[FUGUE_MAX_CALLERS];
/* I: start in sandbox mode? */
bool sandbox_mode;
bool graduated; /* set true after dual-sign graduation */
/* D: stored .text SHA-256 baseline (filled after first load) */
u8 text_hash[FUGUE_HASH_LEN];
unsigned long text_start;
unsigned long text_len;
};
static struct fugue_policy_entry policy_db[FUGUE_MAX_MODULES];
static int policy_count;
static DECLARE_RWSEM(policy_rwsem);
/* =========================================================================
* § F Audit log (append-only HMAC-chained ring in reserved physical RAM)
* =========================================================================
*
* Reserve physical pages at boot with kernel parameter:
* memmap=4M$0x3f000000
* Then pass the same address as module parameter fugue_log_phys.
*
* If no reserved region is given, we fall back to a vmalloc'd ring.
* The fallback is less tamper-resistant but still functional.
*/
static ulong fugue_log_phys;
module_param(fugue_log_phys, ulong, 0400);
MODULE_PARM_DESC(fugue_log_phys, "Physical base of reserved audit-log RAM");
static ulong fugue_log_size = 4 * 1024 * 1024;
module_param(fugue_log_size, ulong, 0400);
MODULE_PARM_DESC(fugue_log_size, "Size of reserved audit-log region (bytes)");
#define FUGUE_LOG_RECORD_MAGIC 0xC10671C1UL
struct fugue_log_record {
u32 magic;
u32 seq;
u64 ktime_ns;
char mod_name[MODULE_NAME_LEN];
char event[128];
u8 prev_hmac[FUGUE_HMAC_LEN]; /* HMAC of previous record */
u8 this_hmac[FUGUE_HMAC_LEN]; /* HMAC of this record (excluding this field) */
};
struct fugue_log_header {
u32 magic; /* 0xC10671DE */
u32 version;
u32 head; /* index of oldest record */
u32 tail; /* index of next write slot */
u32 count;
u32 capacity;
bool sealed;
u8 seal_hmac[FUGUE_HMAC_LEN];
/* records follow immediately */
};
static struct fugue_log_header *fugue_log;
static struct fugue_log_record *fugue_log_records;
static spinlock_t fugue_log_lock;
static u32 fugue_log_seq;
static u8 fugue_log_prev_hmac[FUGUE_HMAC_LEN];
/* =========================================================================
* § E Hook Registry Ledger
* =========================================================================
*
* Every hook registration made by a fugue-managed module must go through
* fugue_hook_register(). We record it here and HMAC the entire table.
* A background work item re-verifies the table HMAC periodically.
*/
enum fugue_hook_type {
HOOK_NETFILTER,
HOOK_LSM,
HOOK_KPROBE,
HOOK_FTRACE,
HOOK_TRACEPOINT,
};
struct fugue_hook_record {
bool active;
enum fugue_hook_type type;
char mod_name[MODULE_NAME_LEN];
char hook_name[64];
unsigned long handler_addr;
u64 registered_ktime;
};
static struct fugue_hook_record hook_ledger[FUGUE_MAX_HOOKS];
static int hook_ledger_count;
static DEFINE_SPINLOCK(hook_ledger_lock);
static u8 hook_ledger_hmac[FUGUE_HMAC_LEN];
/* =========================================================================
* § J Canary table (per-module canary values and kretprobe list)
* ========================================================================= */
struct fugue_canary_entry {
char mod_name[MODULE_NAME_LEN];
u64 canary;
struct list_head probe_list; /* list of struct fugue_krp_node */
};
struct fugue_krp_node {
struct kretprobe krp;
struct list_head list;
};
struct fugue_krp_instance_data {
u64 saved_canary;
};
static struct fugue_canary_entry canary_table[FUGUE_MAX_MODULES];
static int canary_table_count;
static DEFINE_SPINLOCK(canary_lock);
/* =========================================================================
* § D Text integrity verification work item
* ========================================================================= */
static struct delayed_work fugue_text_check_work;
/* =========================================================================
* § G Pending unload tokens
* ========================================================================= */
struct fugue_pending_token {
char mod_name[MODULE_NAME_LEN];
u64 issued_ktime_sec;
u8 nonce[16];
bool valid;
};
#define FUGUE_MAX_PENDING_TOKENS 16
static struct fugue_pending_token pending_tokens[FUGUE_MAX_PENDING_TOKENS];
static DEFINE_SPINLOCK(token_lock);
/* =========================================================================
* § I Sandbox slab pool per module
* ========================================================================= */
struct fugue_sandbox_pool {
char mod_name[MODULE_NAME_LEN];
struct kmem_cache *cache;
atomic_t alloc_count;
u32 quota; /* max simultaneous allocations */
};
static struct fugue_sandbox_pool sandbox_pools[FUGUE_MAX_MODULES];
static int sandbox_pool_count;
static DEFINE_SPINLOCK(sandbox_lock);
/* =========================================================================
* Forward declarations
* ========================================================================= */
static int fugue_log_init(void);
static void fugue_log_write(const char *mod_name, const char *event);
static void fugue_log_seal(void);
static int fugue_policy_load(void);
static struct fugue_policy_entry *fugue_find_policy(const char *name);
static int fugue_verify_ko_signature(const void *ko_image, size_t ko_size,
const u8 *expected_sig);
static int fugue_verify_build_provenance(const void *ko_image, size_t ko_size,
const u8 *expected_env_hash);
static int fugue_hash_text_segment(unsigned long start, unsigned long len,
u8 *out_hash);
static void fugue_enforce_text_ro(unsigned long start, unsigned long len);
static int fugue_hook_ledger_add(enum fugue_hook_type type,
const char *mod_name,
const char *hook_name,
unsigned long handler_addr);
static void fugue_hook_ledger_remove(unsigned long handler_addr);
static void fugue_hook_ledger_recompute_hmac(void);
static bool fugue_hook_ledger_verify_hmac(void);
static int fugue_canary_install(struct fugue_policy_entry *pol,
struct module *mod);
static void fugue_canary_remove(const char *mod_name);
static int fugue_sandbox_create(struct fugue_policy_entry *pol);
static void fugue_sandbox_destroy(const char *mod_name);
static int fugue_token_consume(const struct fugue_unload_token *tok);
static int fugue_call_check(const char *callee_mod, unsigned long caller_addr);
/* =========================================================================
* § F Audit log implementation
* ========================================================================= */
static int fugue_compute_hmac(const void *data, size_t len,
const u8 *key, size_t key_len, u8 *out)
{
struct crypto_shash *tfm;
struct shash_desc *desc;
int ret;
tfm = crypto_alloc_shash("hmac(sha256)", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
ret = crypto_shash_setkey(tfm, key, key_len);
if (ret)
goto out_free_tfm;
desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_ATOMIC);
if (!desc) {
ret = -ENOMEM;
goto out_free_tfm;
}
desc->tfm = tfm;
ret = crypto_shash_digest(desc, data, len, out);
kfree(desc);
out_free_tfm:
crypto_free_shash(tfm);
return ret;
}
static int fugue_log_init(void)
{
size_t header_size = sizeof(struct fugue_log_header);
u32 capacity;
spin_lock_init(&fugue_log_lock);
fugue_log_seq = 0;
memset(fugue_log_prev_hmac, 0, FUGUE_HMAC_LEN);
/* Generate log HMAC key from kernel PRNG */
get_random_bytes(fugue_log_hmac_key, sizeof(fugue_log_hmac_key));
if (fugue_log_phys) {
/* Map the reserved physical region */
fugue_log = (struct fugue_log_header *)
ioremap_cache(fugue_log_phys, fugue_log_size);
if (!fugue_log) {
pr_err("fugue: failed to map reserved log region at 0x%lx\n",
fugue_log_phys);
goto fallback;
}
capacity = (fugue_log_size - header_size)
/ sizeof(struct fugue_log_record);
} else {
fallback:
pr_warn("fugue: no reserved RAM; using vmalloc fallback for audit log\n");
fugue_log = vmalloc(sizeof(struct fugue_log_header) +
FUGUE_LOG_ENTRIES *
sizeof(struct fugue_log_record));
if (!fugue_log)
return -ENOMEM;
capacity = FUGUE_LOG_ENTRIES;
}
fugue_log->magic = 0xC10671DEUL;
fugue_log->version = 1;
fugue_log->head = 0;
fugue_log->tail = 0;
fugue_log->count = 0;
fugue_log->capacity = capacity;
fugue_log->sealed = false;
fugue_log_records = (struct fugue_log_record *)(fugue_log + 1);
return 0;
}
static void fugue_log_write(const char *mod_name, const char *event)
{
struct fugue_log_record *rec;
unsigned long flags;
u32 slot;
if (!fugue_log || fugue_log->sealed)
return;
spin_lock_irqsave(&fugue_log_lock, flags);
slot = fugue_log->tail % fugue_log->capacity;
rec = &fugue_log_records[slot];
rec->magic = FUGUE_LOG_RECORD_MAGIC;
rec->seq = fugue_log_seq++;
rec->ktime_ns = ktime_get_ns();
strscpy(rec->mod_name, mod_name ? mod_name : "<fugue>",
MODULE_NAME_LEN);
strscpy(rec->event, event, sizeof(rec->event));
memcpy(rec->prev_hmac, fugue_log_prev_hmac, FUGUE_HMAC_LEN);
/* HMAC of everything except this_hmac field */
fugue_compute_hmac(rec,
offsetof(struct fugue_log_record, this_hmac),
fugue_log_hmac_key, sizeof(fugue_log_hmac_key),
rec->this_hmac);
memcpy(fugue_log_prev_hmac, rec->this_hmac, FUGUE_HMAC_LEN);
fugue_log->tail = (fugue_log->tail + 1) % fugue_log->capacity;
if (fugue_log->count < fugue_log->capacity)
fugue_log->count++;
else
fugue_log->head = (fugue_log->head + 1) % fugue_log->capacity;
spin_unlock_irqrestore(&fugue_log_lock, flags);
}
static void fugue_log_seal(void)
{
unsigned long flags;
if (!fugue_log)
return;
spin_lock_irqsave(&fugue_log_lock, flags);
fugue_log->sealed = true;
memcpy(fugue_log->seal_hmac, fugue_log_prev_hmac, FUGUE_HMAC_LEN);
spin_unlock_irqrestore(&fugue_log_lock, flags);
pr_info("fugue: audit log sealed\n");
}
/* =========================================================================
* § A .ko signature verification stub
* =========================================================================
*
* Full Ed25519 verification requires linking against the kernel's asymmetric
* key subsystem (CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE). The stub below
* shows the call structure; replace the body with the real crypto call once
* the kernel crypto API akcipher path for Ed25519 is confirmed for your
* kernel version.
*/
static int fugue_verify_ko_signature(const void *ko_image, size_t ko_size,
const u8 *expected_sig)
{
/*
* Production implementation:
* 1. SHA-512 hash the raw ko_image bytes.
* 2. Call crypto_akcipher_verify() with fugue_pubkey and expected_sig.
* 3. Return 0 on success, -EKEYREJECTED on failure.
*
* For kernels with CONFIG_MODULE_SIG the in-tree module signature check
* already provides this; fugue supplements it by also verifying against
* its own out-of-band key so a compromised distro key cannot bypass us.
*/
if (!ko_image || !expected_sig)
return -EINVAL;
/* Stub: always pass in this skeleton — replace with real verify */
pr_debug("fugue: [A] signature check (stub) for %zu-byte image\n",
ko_size);
return 0;
}
/* =========================================================================
* § B Build provenance verification
* =========================================================================
*
* The .ko ELF must contain a note section named "FUGUE_PROV" holding:
* SHA-256(compiler_abspath || '\0' || kernel_header_tree_hash ||
* cflags_string || '\0' || git_commit_sha40)
* signed with the same Ed25519 key as the module.
*
* This stub locates the ELF note and compares its hash to the policy entry.
*/
static int fugue_verify_build_provenance(const void *ko_image,
size_t ko_size,
const u8 *expected_env_hash)
{
const Elf64_Ehdr *ehdr = ko_image;
const Elf64_Shdr *shdr;
const char *shstrtab;
int i;
if (ko_size < sizeof(*ehdr) ||
memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0)
return -ENOEXEC;
if (ehdr->e_shoff == 0 || ehdr->e_shstrndx == SHN_UNDEF)
return -EINVAL;
shdr = (const Elf64_Shdr *)((const u8 *)ko_image + ehdr->e_shoff);
shstrtab = (const char *)ko_image +
shdr[ehdr->e_shstrndx].sh_offset;
for (i = 0; i < ehdr->e_shnum; i++) {
if (shdr[i].sh_type != SHT_NOTE)
continue;
if (strcmp(shstrtab + shdr[i].sh_name, ".note.fugue_prov") != 0)
continue;
/*
* Found the provenance note. Its desc field is a 32-byte SHA-256
* of the build environment. Compare to policy.
*
* Real implementation: parse the Elf64_Nhdr + name + desc, then
* memcmp(desc, expected_env_hash, FUGUE_HASH_LEN).
*/
pr_debug("fugue: [B] provenance note found at section %d\n", i);
/* Stub: assume match */
return 0;
}
pr_warn("fugue: [B] no .note.fugue_prov section found — rejecting\n");
return -ENOKEY;
}
/* =========================================================================
* § D Text segment hashing and RO enforcement
* ========================================================================= */
static int fugue_hash_text_segment(unsigned long start, unsigned long len,
u8 *out_hash)
{
struct crypto_shash *tfm;
struct shash_desc *desc;
int ret;
tfm = crypto_alloc_shash("sha256", 0, 0);
if (IS_ERR(tfm))
return PTR_ERR(tfm);
desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL);
if (!desc) {
crypto_free_shash(tfm);
return -ENOMEM;
}
desc->tfm = tfm;
ret = crypto_shash_init(desc);
if (ret)
goto out;
/*
* Hash in PAGE_SIZE chunks to avoid holding RCU or disabling preemption
* for the entire duration.
*/
while (len > 0) {
size_t chunk = min_t(size_t, len, PAGE_SIZE);
ret = crypto_shash_update(desc, (const u8 *)start, chunk);
if (ret)
goto out;
start += chunk;
len -= chunk;
}
ret = crypto_shash_final(desc, out_hash);
out:
kfree(desc);
crypto_free_shash(tfm);
return ret;
}
static void fugue_enforce_text_ro(unsigned long start, unsigned long len)
{
unsigned long addr = round_down(start, PAGE_SIZE);
unsigned long end = round_up(start + len, PAGE_SIZE);
unsigned long npages = (end - addr) / PAGE_SIZE;
if (set_memory_ro(addr, npages) != 0)
pr_warn("fugue: [D] failed to mark .text RO at %lx+%lu\n",
addr, len);
else
pr_info("fugue: [D] .text marked RO: %lx + %lu pages\n",
addr, npages);
}
/* Periodic text re-verification */
static void fugue_text_check_fn(struct work_struct *w)
{
int i;
u8 current_hash[FUGUE_HASH_LEN];
char event[128];
down_read(&policy_rwsem);
for (i = 0; i < policy_count; i++) {
struct fugue_policy_entry *pol = &policy_db[i];
if (!pol->text_start || !pol->text_len)
continue;
if (fugue_hash_text_segment(pol->text_start, pol->text_len,
current_hash) != 0)
continue;
if (memcmp(current_hash, pol->text_hash, FUGUE_HASH_LEN) != 0) {
snprintf(event, sizeof(event),
"TEXT INTEGRITY VIOLATION: .text hash mismatch");
fugue_log_write(pol->mod_name, event);
pr_crit("fugue: [D] .text hash mismatch for module %s! "
"Possible runtime patch.\n", pol->mod_name);
/*
* Policy choice: panic() for high-assurance deployments,
* or just log and alert. Uncomment the next line for panic mode.
*/
/* panic("fugue: LKM text integrity violated"); */
}
}
up_read(&policy_rwsem);
schedule_delayed_work(&fugue_text_check_work, FUGUE_TEXT_CHECK_HZ);
}
/* =========================================================================
* § E Hook Registry Ledger
* ========================================================================= */
static void fugue_hook_ledger_recompute_hmac(void)
{
/* HMAC over the active portion of the ledger */
fugue_compute_hmac(hook_ledger,
hook_ledger_count * sizeof(struct fugue_hook_record),
fugue_log_hmac_key, sizeof(fugue_log_hmac_key),
hook_ledger_hmac);
}
static bool fugue_hook_ledger_verify_hmac(void)
{
u8 current[FUGUE_HMAC_LEN];
fugue_compute_hmac(hook_ledger,
hook_ledger_count * sizeof(struct fugue_hook_record),
fugue_log_hmac_key, sizeof(fugue_log_hmac_key),
current);
return memcmp(current, hook_ledger_hmac, FUGUE_HMAC_LEN) == 0;
}
int fugue_hook_register(enum fugue_hook_type type,
const char *mod_name,
const char *hook_name,
unsigned long handler_addr)
{
unsigned long flags;
int slot = -1, i;
char event[128];
spin_lock_irqsave(&hook_ledger_lock, flags);
if (!fugue_hook_ledger_verify_hmac()) {
pr_crit("fugue: [E] hook ledger HMAC tampered before registration!\n");
fugue_log_write(mod_name, "HOOK LEDGER HMAC TAMPERED");
spin_unlock_irqrestore(&hook_ledger_lock, flags);
return -EACCES;
}
/* Find a free slot */
for (i = 0; i < FUGUE_MAX_HOOKS; i++) {
if (!hook_ledger[i].active) {
slot = i;
break;
}
}
if (slot < 0) {
spin_unlock_irqrestore(&hook_ledger_lock, flags);
return -ENOMEM;
}
hook_ledger[slot].active = true;
hook_ledger[slot].type = type;
hook_ledger[slot].handler_addr = handler_addr;
hook_ledger[slot].registered_ktime = ktime_get_ns();
strscpy(hook_ledger[slot].mod_name, mod_name, MODULE_NAME_LEN);
strscpy(hook_ledger[slot].hook_name, hook_name, 64);
if (slot >= hook_ledger_count)
hook_ledger_count = slot + 1;
fugue_hook_ledger_recompute_hmac();
spin_unlock_irqrestore(&hook_ledger_lock, flags);
snprintf(event, sizeof(event), "HOOK REGISTERED type=%d name=%s addr=%lx",
type, hook_name, handler_addr);
fugue_log_write(mod_name, event);
return 0;
}
EXPORT_SYMBOL_GPL(fugue_hook_register);
void fugue_hook_deregister(unsigned long handler_addr)
{
unsigned long flags;
int i;
char mod_name[MODULE_NAME_LEN] = "<unknown>";
char hook_name[64] = "<unknown>";
char event[128];
spin_lock_irqsave(&hook_ledger_lock, flags);
if (!fugue_hook_ledger_verify_hmac()) {
pr_crit("fugue: [E] hook ledger HMAC tampered before deregistration!\n");
fugue_log_write(NULL, "HOOK LEDGER HMAC TAMPERED");
}
for (i = 0; i < hook_ledger_count; i++) {
if (hook_ledger[i].active &&
hook_ledger[i].handler_addr == handler_addr) {
strscpy(mod_name, hook_ledger[i].mod_name, MODULE_NAME_LEN);
strscpy(hook_name, hook_ledger[i].hook_name, 64);
hook_ledger[i].active = false;
break;
}
}
fugue_hook_ledger_recompute_hmac();
spin_unlock_irqrestore(&hook_ledger_lock, flags);
snprintf(event, sizeof(event), "HOOK DEREGISTERED name=%s addr=%lx",
hook_name, handler_addr);
fugue_log_write(mod_name, event);
}
EXPORT_SYMBOL_GPL(fugue_hook_deregister);
/* kprobe on register_nf_hook / register_kprobe etc. to catch hooks that
* bypass the ledger API. Handler addresses not in our ledger get logged. */
static int fugue_nf_hook_kprobe_pre(struct kprobe *kp, struct pt_regs *regs)
{
unsigned long flags;
unsigned long handler;
bool found = false;
int i;
/*
* rdi = first argument on x86-64 = pointer to nf_hook_ops.
* Dereference .hook to get the handler address.
* Adjust for other architectures as needed.
*/
#ifdef CONFIG_X86_64
struct nf_hook_ops *ops = (struct nf_hook_ops *)regs->di;
if (!ops || IS_ERR_OR_NULL(ops))
return 0;
handler = (unsigned long)ops->hook;
#else
return 0; /* add arch-specific arg extraction */
#endif
spin_lock_irqsave(&hook_ledger_lock, flags);
for (i = 0; i < hook_ledger_count; i++) {
if (hook_ledger[i].active &&
hook_ledger[i].handler_addr == handler) {
found = true;
break;
}
}
spin_unlock_irqrestore(&hook_ledger_lock, flags);
if (!found) {
pr_warn("fugue: [E] UNLEDGERED netfilter hook at %lx — possible rogue module\n",
handler);
fugue_log_write(NULL, "UNLEDGERED NF HOOK DETECTED");
}
return 0;
}
static struct kprobe fugue_nf_hook_kp = {
.symbol_name = "nf_register_net_hook",
.pre_handler = fugue_nf_hook_kprobe_pre,
};
/* =========================================================================
* § C Symbol Firewall
* =========================================================================
*
* For each protected module, we install kprobes on every kernel symbol
* NOT in its manifest. The pre_handler checks the call's return address
* to determine if the caller is the protected module; if so, block it.
*
* This is a best-effort implementation. The full implementation would
* instrument every kernel export (kallsyms_on_each_symbol) and filter
* by module-of-caller using __module_address().
*/
static int fugue_sym_firewall_pre(struct kprobe *kp, struct pt_regs *regs)
{
unsigned long caller;
struct module *caller_mod;
#ifdef CONFIG_X86_64
caller = regs->ip - 5; /* approximate: back up past the call instruction */
#elif defined(CONFIG_ARM64)
caller = regs->pc - 4;
#else
return 0;
#endif
caller_mod = __module_address(caller);
if (!caller_mod)
return 0; /* caller is core kernel — not our concern */
down_read(&policy_rwsem);
{
struct fugue_policy_entry *pol =
fugue_find_policy(caller_mod->name);
if (pol) {
/*
* The caller is a fugue-managed module. Check if this
* symbol is in its manifest. kp->symbol_name is the symbol.
*/
bool permitted = false;
u32 i;
for (i = 0; i < pol->sym_count; i++) {
if (strcmp(pol->syms[i].name, kp->symbol_name) == 0) {
permitted = true;
break;
}
}
if (!permitted) {
char event[128];
snprintf(event, sizeof(event),
"SYMBOL FIREWALL BLOCK: %s called undeclared %s",
caller_mod->name, kp->symbol_name);
fugue_log_write(caller_mod->name, event);
pr_warn("fugue: [C] %s\n", event);
/*
* To actually block the call, set the instruction pointer
* to a safe return stub. Uncomment and implement as needed:
* regs->ip = (unsigned long)fugue_blocked_call_stub;
*/
}
}
}
up_read(&policy_rwsem);
return 0;
}
/* =========================================================================
* § G Unload Guard
* =========================================================================
*
* We kprobe delete_module (the kernel side of rmmod). If the target module
* is guarded, we check for a valid pending token.
*/
static int fugue_delete_module_pre(struct kprobe *kp, struct pt_regs *regs)
{
const char __user *uname_ptr;
char mod_name[MODULE_NAME_LEN];
struct fugue_policy_entry *pol;
#ifdef CONFIG_X86_64
uname_ptr = (const char __user *)regs->di;
#elif defined(CONFIG_ARM64)
uname_ptr = (const char __user *)regs->regs[0];
#else
return 0;
#endif
if (strncpy_from_user(mod_name, uname_ptr, MODULE_NAME_LEN) < 0)
return 0;
mod_name[MODULE_NAME_LEN - 1] = '\0';
down_read(&policy_rwsem);
pol = fugue_find_policy(mod_name);
if (pol && pol->unload_guarded) {
unsigned long flags;
bool token_ok = false;
int i;
spin_lock_irqsave(&token_lock, flags);
for (i = 0; i < FUGUE_MAX_PENDING_TOKENS; i++) {
if (pending_tokens[i].valid &&
strcmp(pending_tokens[i].mod_name, mod_name) == 0) {
u64 now = ktime_get_seconds();
if (now - pending_tokens[i].issued_ktime_sec
<= FUGUE_TOKEN_TTL_SEC) {
token_ok = true;
pending_tokens[i].valid = false;
}
break;
}
}
spin_unlock_irqrestore(&token_lock, flags);
if (!token_ok) {
pr_warn("fugue: [G] rmmod of guarded module %s rejected — "
"no valid TPM token\n", mod_name);
fugue_log_write(mod_name, "UNLOAD REJECTED: no valid token");
up_read(&policy_rwsem);
/*
* Prevent the delete_module syscall from proceeding by
* overwriting its first argument to an empty string so the
* kernel returns -ENOENT. Cleaner alternatives: return a
* non-zero value from the pre_handler to skip the original
* function (supported in some kernel versions), or use
* LSM security_module_free hook.
*/
#ifdef CONFIG_X86_64
regs->di = (unsigned long)"";
#endif
return 0;
}
fugue_log_write(mod_name, "UNLOAD PERMITTED: valid token consumed");
}
up_read(&policy_rwsem);
return 0;
}
static struct kprobe fugue_delete_module_kp = {
.symbol_name = "do_init_module", /* hook delete_module path */
.pre_handler = fugue_delete_module_pre,
};
/* =========================================================================
* § H Inter-Module Call Isolator
* =========================================================================
*
* fugue_call_check() is called from trampolines installed at each
* exported symbol of a fugue-managed module. It verifies that the
* physical caller's module is on the callee's allowed-callers list.
*/
int fugue_call_check(const char *callee_mod, unsigned long caller_addr)
{
struct module *caller_mod;
struct fugue_policy_entry *pol;
bool permitted = false;
u32 i;
caller_mod = __module_address(caller_addr);
if (!caller_mod)
return 0; /* core kernel — always permit */
if (strcmp(caller_mod->name, callee_mod) == 0)
return 0; /* self-call — permit */
down_read(&policy_rwsem);
pol = fugue_find_policy(callee_mod);
if (!pol) {
up_read(&policy_rwsem);
return 0; /* callee not managed — permit */
}
for (i = 0; i < pol->caller_count; i++) {
if (strcmp(pol->callers[i].mod_name, caller_mod->name) == 0) {
permitted = true;
break;
}
}
up_read(&policy_rwsem);
if (!permitted) {
char event[128];
snprintf(event, sizeof(event),
"CALL ISOLATION BLOCK: %s → %s denied",
caller_mod->name, callee_mod);
fugue_log_write(callee_mod, event);
pr_warn("fugue: [H] %s\n", event);
return -EACCES;
}
return 0;
}
EXPORT_SYMBOL_GPL(fugue_call_check);
/* =========================================================================
* § I Developer Sandbox
* =========================================================================
*
* When sandbox_mode is true, the module's kmalloc calls are routed through
* a quota-limited, red-zoned slab cache created here. Network access is
* handled by moving the module's worker threads into a private netns
* (requires the module to cooperate by using fugue_sandbox_sock_create()
* instead of sock_create() directly).
*
* Graduation to production mode requires two Ed25519 signatures (stub).
*/
static int fugue_sandbox_create(struct fugue_policy_entry *pol)
{
unsigned long flags;
int i;
char cache_name[64];
struct fugue_sandbox_pool *pool = NULL;
spin_lock_irqsave(&sandbox_lock, flags);
for (i = 0; i < FUGUE_MAX_MODULES; i++) {
if (!sandbox_pools[i].cache) {
pool = &sandbox_pools[i];
break;
}
}
if (!pool) {
spin_unlock_irqrestore(&sandbox_lock, flags);
return -ENOMEM;
}
strscpy(pool->mod_name, pol->mod_name, MODULE_NAME_LEN);
pool->quota = 1024;
atomic_set(&pool->alloc_count, 0);
spin_unlock_irqrestore(&sandbox_lock, flags);
snprintf(cache_name, sizeof(cache_name), "fugue_sb_%s", pol->mod_name);
/*
* SLAB_POISON fills freed objects with 0x6b; SLAB_RED_ZONE adds
* guard bytes around each allocation (CONFIG_SLUB_DEBUG required).
*/
pool->cache = kmem_cache_create(cache_name, 256, 0,
SLAB_POISON | SLAB_CONSISTENCY_CHECKS,
NULL);
if (!pool->cache)
return -ENOMEM;
sandbox_pool_count++;
fugue_log_write(pol->mod_name, "SANDBOX CREATED");
pr_info("fugue: [I] sandbox slab pool created for %s\n", pol->mod_name);
return 0;
}
static void fugue_sandbox_destroy(const char *mod_name)
{
unsigned long flags;
int i;
spin_lock_irqsave(&sandbox_lock, flags);
for (i = 0; i < FUGUE_MAX_MODULES; i++) {
if (sandbox_pools[i].cache &&
strcmp(sandbox_pools[i].mod_name, mod_name) == 0) {
kmem_cache_destroy(sandbox_pools[i].cache);
sandbox_pools[i].cache = NULL;
sandbox_pool_count--;
break;
}
}
spin_unlock_irqrestore(&sandbox_lock, flags);
fugue_log_write(mod_name, "SANDBOX DESTROYED");
}
/* Public API: sandboxed modules call this instead of kmalloc */
void *fugue_sandbox_alloc(const char *mod_name, size_t size)
{
unsigned long flags;
int i;
struct fugue_sandbox_pool *pool = NULL;
spin_lock_irqsave(&sandbox_lock, flags);
for (i = 0; i < FUGUE_MAX_MODULES; i++) {
if (sandbox_pools[i].cache &&
strcmp(sandbox_pools[i].mod_name, mod_name) == 0) {
pool = &sandbox_pools[i];
break;
}
}
spin_unlock_irqrestore(&sandbox_lock, flags);
if (!pool)
return kmalloc(size, GFP_KERNEL); /* not sandboxed — normal alloc */
if (atomic_inc_return(&pool->alloc_count) > (int)pool->quota) {
atomic_dec(&pool->alloc_count);
pr_warn("fugue: [I] sandbox quota exhausted for %s\n", mod_name);
fugue_log_write(mod_name, "SANDBOX QUOTA EXHAUSTED");
return NULL;
}
return kmem_cache_alloc(pool->cache, GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(fugue_sandbox_alloc);
void fugue_sandbox_free(const char *mod_name, void *ptr)
{
unsigned long flags;
int i;
struct fugue_sandbox_pool *pool = NULL;
spin_lock_irqsave(&sandbox_lock, flags);
for (i = 0; i < FUGUE_MAX_MODULES; i++) {
if (sandbox_pools[i].cache &&
strcmp(sandbox_pools[i].mod_name, mod_name) == 0) {
pool = &sandbox_pools[i];
break;
}
}
spin_unlock_irqrestore(&sandbox_lock, flags);
if (!pool) {
kfree(ptr);
return;
}
atomic_dec(&pool->alloc_count);
kmem_cache_free(pool->cache, ptr);
}
EXPORT_SYMBOL_GPL(fugue_sandbox_free);
/* =========================================================================
* § J Stack and Heap Canary Injector
* ========================================================================= */
static int fugue_canary_entry_handler(struct kretprobe_instance *ri,
struct pt_regs *regs)
{
struct fugue_krp_instance_data *data =
(struct fugue_krp_instance_data *)ri->data;
struct fugue_canary_entry *ce = (struct fugue_canary_entry *)
container_of(ri->rp, struct fugue_krp_node, krp)->list.next; /* placeholder */
(void)ce;
data->saved_canary = FUGUE_CANARY_MAGIC ^ (u64)(uintptr_t)ri;
return 0;
}
static int fugue_canary_ret_handler(struct kretprobe_instance *ri,
struct pt_regs *regs)
{
struct fugue_krp_instance_data *data =
(struct fugue_krp_instance_data *)ri->data;
u64 expected = FUGUE_CANARY_MAGIC ^ (u64)(uintptr_t)ri;
if (data->saved_canary != expected) {
pr_crit("fugue: [J] STACK CANARY VIOLATION in LKM function!\n");
fugue_log_write(NULL, "STACK CANARY VIOLATION");
/* panic() in high-assurance mode */
}
return 0;
}
static int fugue_canary_install(struct fugue_policy_entry *pol,
struct module *mod)
{
/*
* Iterate the module's exported symbols (mod->syms) and install a
* kretprobe on each one. We keep the probes in the per-module
* fugue_canary_entry's probe_list.
*
* For brevity we show the structure; real iteration uses
* mod->syms and mod->num_syms.
*/
struct fugue_canary_entry *ce = NULL;
unsigned long flags;
int i;
spin_lock_irqsave(&canary_lock, flags);
for (i = 0; i < FUGUE_MAX_MODULES; i++) {
if (!canary_table[i].canary) {
ce = &canary_table[i];
break;
}
}
if (!ce) {
spin_unlock_irqrestore(&canary_lock, flags);
return -ENOMEM;
}
strscpy(ce->mod_name, pol->mod_name, MODULE_NAME_LEN);
get_random_bytes(&ce->canary, sizeof(ce->canary));
INIT_LIST_HEAD(&ce->probe_list);
canary_table_count++;
spin_unlock_irqrestore(&canary_lock, flags);
pr_info("fugue: [J] canary 0x%llx installed for %s\n",
ce->canary, pol->mod_name);
fugue_log_write(pol->mod_name, "CANARY INSTALLED");
return 0;
}
static void fugue_canary_remove(const char *mod_name)
{
unsigned long flags;
int i;
spin_lock_irqsave(&canary_lock, flags);
for (i = 0; i < FUGUE_MAX_MODULES; i++) {
if (canary_table[i].canary &&
strcmp(canary_table[i].mod_name, mod_name) == 0) {
/*
* Unregister kretprobes in probe_list here in a real
* implementation.
*/
struct fugue_krp_node *node, *tmp;
list_for_each_entry_safe(node, tmp, &canary_table[i].probe_list,
list) {
unregister_kretprobe(&node->krp);
list_del(&node->list);
kfree(node);
}
canary_table[i].canary = 0;
canary_table_count--;
break;
}
}
spin_unlock_irqrestore(&canary_lock, flags);
}
/* =========================================================================
* § Policy helpers
* ========================================================================= */
static struct fugue_policy_entry *fugue_find_policy(const char *name)
{
int i;
/* Caller must hold policy_rwsem */
for (i = 0; i < policy_count; i++) {
if (strcmp(policy_db[i].mod_name, name) == 0)
return &policy_db[i];
}
return NULL;
}
static int fugue_policy_load(void)
{
/*
* In production: open /etc/fugue/policy.bin, verify its Ed25519
* signature, parse entries into policy_db[], resolve symbol addresses
* via kallsyms_lookup_name() for each sym manifest entry.
*
* Here we demonstrate the structure with one built-in example entry
* so the module compiles and runs for testing.
*/
down_write(&policy_rwsem);
policy_count = 0;
/* Example: protect a hypothetical "sec_dns_interceptor" module */
strscpy(policy_db[0].mod_name, "sec_dns_interceptor", MODULE_NAME_LEN);
policy_db[0].unload_guarded = true;
policy_db[0].sandbox_mode = false;
policy_db[0].graduated = true;
policy_db[0].sym_count = 2;
strscpy(policy_db[0].syms[0].name, "kmalloc", 64);
strscpy(policy_db[0].syms[1].name, "kfree", 64);
policy_db[0].caller_count = 0;
policy_count = 1;
up_write(&policy_rwsem);
pr_info("fugue: policy loaded (%d entries)\n", policy_count);
return 0;
}
/* =========================================================================
* § G Token consumption
* ========================================================================= */
static int fugue_token_consume(const struct fugue_unload_token *tok)
{
unsigned long flags;
u64 now = ktime_get_seconds();
int i, free_slot = -1;
if (now - tok->timestamp_sec > FUGUE_TOKEN_TTL_SEC)
return -ETIMEDOUT;
/*
* Production: verify tok->tpm_quote against the TPM's AIK and verify
* tok->hmac = HMAC-SHA256(mod_name||timestamp||nonce) with a pre-shared
* key sealed to a known PCR set.
*
* Stub: accept any token with a sane timestamp.
*/
spin_lock_irqsave(&token_lock, flags);
for (i = 0; i < FUGUE_MAX_PENDING_TOKENS; i++) {
if (pending_tokens[i].valid &&
strcmp(pending_tokens[i].mod_name, tok->mod_name) == 0) {
/* Replace existing */
free_slot = i;
break;
}
if (!pending_tokens[i].valid && free_slot < 0)
free_slot = i;
}
if (free_slot < 0) {
spin_unlock_irqrestore(&token_lock, flags);
return -ENOMEM;
}
strscpy(pending_tokens[free_slot].mod_name, tok->mod_name, MODULE_NAME_LEN);
pending_tokens[free_slot].issued_ktime_sec = tok->timestamp_sec;
memcpy(pending_tokens[free_slot].nonce, tok->nonce, 16);
pending_tokens[free_slot].valid = true;
spin_unlock_irqrestore(&token_lock, flags);
fugue_log_write(tok->mod_name, "UNLOAD TOKEN ACCEPTED");
return 0;
}
/* =========================================================================
* § /dev/fugue_log character device for log export and ioctl
* ========================================================================= */
static ssize_t fugue_dev_read(struct file *f, char __user *buf,
size_t count, loff_t *ppos)
{
/*
* Expose the raw log region starting at *ppos. In production, seal
* the log first and only permit reads after sealing.
*/
size_t total = sizeof(struct fugue_log_header) +
fugue_log->capacity * sizeof(struct fugue_log_record);
size_t remaining;
size_t to_copy;
if (*ppos >= total)
return 0;
remaining = total - *ppos;
to_copy = min(count, remaining);
if (copy_to_user(buf, (const char *)fugue_log + *ppos, to_copy))
return -EFAULT;
*ppos += to_copy;
return to_copy;
}
static long fugue_dev_ioctl(struct file *f, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case FUGUE_IOC_UNLOAD_TOKEN: {
struct fugue_unload_token tok;
if (copy_from_user(&tok, (void __user *)arg, sizeof(tok)))
return -EFAULT;
return fugue_token_consume(&tok);
}
case FUGUE_IOC_GRADUATE: {
struct fugue_graduate_token gtok;
struct fugue_policy_entry *pol;
int ret = 0;
if (copy_from_user(&gtok, (void __user *)arg, sizeof(gtok)))
return -EFAULT;
/*
* Verify gtok.sig_dev1 with fugue_pubkey and
* gtok.sig_dev2 with fugue_pubkey2.
* Both must be valid Ed25519 signatures over mod_name.
* Stub: assume valid.
*/
(void)fugue_pubkey;
(void)fugue_pubkey2;
down_write(&policy_rwsem);
pol = fugue_find_policy(gtok.mod_name);
if (!pol) {
ret = -ENOENT;
} else if (!pol->sandbox_mode) {
ret = -EINVAL; /* not in sandbox */
} else {
pol->sandbox_mode = false;
pol->graduated = true;
fugue_sandbox_destroy(gtok.mod_name);
fugue_log_write(gtok.mod_name, "GRADUATED TO PRODUCTION");
pr_info("fugue: [I] %s graduated to production mode\n",
gtok.mod_name);
}
up_write(&policy_rwsem);
return ret;
}
case FUGUE_IOC_DUMP_HOOKS: {
unsigned int active = 0;
unsigned long flags;
int i;
spin_lock_irqsave(&hook_ledger_lock, flags);
for (i = 0; i < hook_ledger_count; i++)
if (hook_ledger[i].active)
active++;
spin_unlock_irqrestore(&hook_ledger_lock, flags);
return put_user(active, (unsigned int __user *)arg) ? -EFAULT : 0;
}
case FUGUE_IOC_SEAL_LOG:
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
fugue_log_seal();
return 0;
default:
return -ENOTTY;
}
}
static const struct file_operations fugue_fops = {
.owner = THIS_MODULE,
.read = fugue_dev_read,
.unlocked_ioctl = fugue_dev_ioctl,
.llseek = generic_file_llseek,
};
static struct miscdevice fugue_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = FUGUE_DEV_NAME,
.fops = &fugue_fops,
.mode = 0600,
};
/* =========================================================================
* Module load / unload
* ========================================================================= */
static int __init fugue_init(void)
{
int ret;
int i;
pr_info("fugue: initializing unified LKM security runtime\n");
/* F: audit log */
ret = fugue_log_init();
if (ret) {
pr_err("fugue: audit log init failed: %d\n", ret);
return ret;
}
fugue_log_write(NULL, "FUGUE INIT BEGIN");
/* Policy */
ret = fugue_policy_load();
if (ret) {
pr_err("fugue: policy load failed: %d\n", ret);
goto err_log;
}
/* E: install kprobe to catch unledgered netfilter hooks */
ret = register_kprobe(&fugue_nf_hook_kp);
if (ret) {
pr_warn("fugue: [E] failed to install nf_register_net_hook kprobe: %d\n",
ret);
/* Non-fatal: continue */
}
/* G: install kprobe on delete_module path */
/* Note: the real target should be "do_syscall_64" path for
* delete_module or the syscall entry; adjust symbol_name per kernel. */
fugue_delete_module_kp.symbol_name = "__x64_sys_delete_module";
ret = register_kprobe(&fugue_delete_module_kp);
if (ret) {
pr_warn("fugue: [G] failed to install delete_module kprobe: %d\n",
ret);
}
/* D: schedule periodic text integrity checks */
INIT_DELAYED_WORK(&fugue_text_check_work, fugue_text_check_fn);
schedule_delayed_work(&fugue_text_check_work, FUGUE_TEXT_CHECK_HZ);
/* I: create sandbox pools for modules that need it */
down_read(&policy_rwsem);
for (i = 0; i < policy_count; i++) {
if (policy_db[i].sandbox_mode)
fugue_sandbox_create(&policy_db[i]);
}
up_read(&policy_rwsem);
/* /dev node */
ret = misc_register(&fugue_misc);
if (ret) {
pr_err("fugue: misc_register failed: %d\n", ret);
goto err_kprobes;
}
fugue_log_write(NULL, "FUGUE INIT COMPLETE");
pr_info("fugue: ready. /dev/%s available.\n", FUGUE_DEV_NAME);
return 0;
err_kprobes:
unregister_kprobe(&fugue_nf_hook_kp);
unregister_kprobe(&fugue_delete_module_kp);
cancel_delayed_work_sync(&fugue_text_check_work);
err_log:
if (fugue_log) {
if (fugue_log_phys)
iounmap(fugue_log);
else
vfree(fugue_log);
}
return ret;
}
static void __exit fugue_exit(void)
{
int i;
pr_info("fugue: shutting down\n");
fugue_log_write(NULL, "FUGUE EXIT BEGIN");
cancel_delayed_work_sync(&fugue_text_check_work);
misc_deregister(&fugue_misc);
unregister_kprobe(&fugue_nf_hook_kp);
unregister_kprobe(&fugue_delete_module_kp);
/* Remove all canaries */
down_read(&policy_rwsem);
for (i = 0; i < policy_count; i++)
fugue_canary_remove(policy_db[i].mod_name);
up_read(&policy_rwsem);
/* Destroy sandbox pools */
for (i = 0; i < FUGUE_MAX_MODULES; i++) {
if (sandbox_pools[i].cache) {
kmem_cache_destroy(sandbox_pools[i].cache);
sandbox_pools[i].cache = NULL;
}
}
fugue_log_write(NULL, "FUGUE EXIT COMPLETE");
fugue_log_seal();
if (fugue_log) {
if (fugue_log_phys)
iounmap(fugue_log);
else
vfree(fugue_log);
}
}
module_init(fugue_init);
module_exit(fugue_exit);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment