A security research document grounded in a real system incident. On May 27, 2026, the machine worlock experienced a cascading crash of all SSL-linked processes — including sort, grep, lsof, dpkg-query, ollama, and the claude CLI — due to a corrupted OpenSSL OQS (Open Quantum Safe) provider propagating via /etc/environment. This repository documents the incident, maps the attack surface to 7 primary threat vectors, and expands the analysis into 32 Graphviz diagrams covering architecture, topology, empirical data, causality chains, and future defensive directions.
All artifact paths, library versions, and crash sequences are verified against the live system.
- Incident Overview
- The 7 Threat Vectors
- Architecture Diagrams
- System Topology Diagrams
- Empirical Results
- Causality Chains
- Future Directions
- Defensive Countermeasures
- Affected Artifacts
- Reproduction Notes
- References
| Boot Index | Start (CDT) | End (CDT) | Duration | Status |
|---|---|---|---|---|
| -2 | Wed May 27 09:59 | Wed May 27 23:04 | 13h 4m | CRASH BOOT |
| -1 | Wed May 27 23:04 | Thu May 29 11:23 | ~36h | Partial stability |
| 0 | Thu May 29 11:23 | (running) | — | STABLE |
May 28 09:47:35 ollama SIGSEGV (×6 total)
May 28 11:15:57 claude SIGSEGV
May 28 11:16:23 dpkg-query SIGABRT ← KEY INDICATOR
May 28 11:16:41 claude SIGILL
May 28 21:02:47 sort SIGSEGV ← KEY INDICATOR
May 28 21:42:09 grep SIGSEGV ← KEY INDICATOR
May 28 21:42:46 lsof SIGSEGV ← KEY INDICATOR
Smoking gun: sort, grep, lsof, and dpkg-query have no GPU or ML dependencies. Their only shared exposure with ollama and claude is the crypto stack — specifically libssl/libcrypto. When unrelated system utilities crash simultaneously, a shared library is poisoned.
/etc/environment
└─ OPENSSL_CONF=/usr/local/ssl/openssl.cnf ← dev build left behind
└─ loads oqsprovider.so linked to liboqs.so.8 (dev ABI)
└─ every process that calls OPENSSL_init_ssl() → SIGSEGV
Two concurrent faults:
- OQS dev build —
liboqs.so.8installed alongside stableliboqs.so.9;OPENSSL_CONFpointing at the dev config was left in/etc/environmentafter agryphgenbuild session - APT mirror corruption —
us.archive.ubuntu.comserving malformed packages with bad UTF-8 metadata, compounding package manager instability
Both were fixed on May 27-29. See session.md for the full incident narrative.
OPENSSL_CONF in /etc/environment is inherited by every process started by PAM. A single line written by an attacker with root access (e.g., via a malicious apt postinst script) poisons the crypto environment of all login sessions, shell children, cron jobs, and system services.
Privilege ladder:
/etc/environment(root) → all users, all processes~/.pam_environment(user) → that user's sessions, no root needed- systemd
Environment=(root, per-unit) → correct, scoped pattern
/usr/lib/x86_64-linux-gnu/ossl-modules/ is the OpenSSL plugin directory. Any .so placed here and referenced in openssl.cnf is loaded in-process into every application calling OPENSSL_init_ssl() — with no separate PID, no auditd entry, and no debsums baseline unless manually configured.
Why this beats LD_PRELOAD for stealth:
- No separate process ID
- Not logged by
auditdexecve hooks - Not visible in
/proc/<pid>/environ - Not tracked by
debsums(if not dpkg-installed) - Affects all SSL processes, not just one
Both liboqs.so.0.15.0 and oqsprovider.so on worlock are built from GitHub source. Neither is dpkg-tracked. This means: no debsums integrity check, no apt signature verification, no dpkg --verify, and no automatic removal on apt purge.
A compromised open-quantum-safe/oqs-provider upstream — via maintainer account compromise, dependency confusion, or a malicious CMake toolchain — propagates silently to every source-builder. The resulting .so is loaded into sshd, nginx, curl, dpkg, and every other SSL-linked process simultaneously.
The May 27 incident is an unintentional but fully functional proof-of-concept system DoS. A single bad OPENSSL_CONF entry brought down the entire SSL process tree in minutes, including the package manager needed to fix the problem.
The recovery paradox: dpkg-query itself crashed, meaning apt could not be used to fix the broken library. Recovery required the out-of-band WAN SSH path on port 57291 (ryleh-worlock.duckdns.org).
The actual crash mechanism: oqsprovider.so was compiled against liboqs.so.9 (stable ABI), but OPENSSL_CONF pointed to a dev config that loaded liboqs.so.8 (dev ABI). The dynamic linker resolves symbols by name, not by offset — so the wrong .so loads, and the first call into a mismatched function produces SIGSEGV, SIGILL, or SIGABRT depending on how the offset lands.
An attacker who can plant a .so with the correct SONAME but a subtly wrong dispatch table can trigger crashes on specific codepaths only — e.g., only during RSA-PSS signing, only during TLS client auth — making the attack highly targeted and difficult to reproduce in a test environment.
The worlock incident crashed. A sophisticated attacker would not crash — they would intercept silently. An OpenSSL provider sits between the application and the hardware crypto layer, with hooks at every key generation, signing, decapsulation, and PRNG call.
| Hook | What the Attacker Gets |
|---|---|
OSSL_FUNC_KEYMGMT_GEN |
Full private key at birth |
OSSL_FUNC_KEM_DECAPSULATE |
Session pre-master secret |
OSSL_FUNC_SIGNATURE_SIGN |
Private key usage + signed data |
OSSL_FUNC_RAND_GENERATE |
Ability to bias randomness |
OSSL_FUNC_SIGNATURE_VERIFY |
Ability to accept forged certs |
Detection gap: openssl list -providers shows the provider as active but cannot verify its behavior. No standard audit tool performs behavioral attestation of loaded providers.
OQS provider enables TLS 1.3 hybrid key exchange (e.g., X25519Kyber768). A malicious provider can advertise PQ support during negotiation but silently compute only the classical X25519 component, skipping Kyber768 entirely and filling the PQ ciphertext slot with fake bytes.
The remote server cannot detect this — it receives a valid-length ciphertext and derives its half of the session key normally. The result is a TLS session that appears post-quantum but has only classical X25519 forward secrecy. A state-level adversary harvesting traffic today can decrypt it once a cryptographically-relevant quantum computer exists (~2035+).
These diagrams document the internal structure of the OpenSSL 3.x provider system that makes all of the above threat vectors possible.
The full layered stack from application through libssl → libcrypto → provider dispatch → algorithm implementation, including the path a malicious provider takes.
Every OpenSSL 3.x provider must implement an OSSL_DISPATCH table mapping function IDs to function pointers. This diagram maps every major function ID to what it exposes — and which are the highest-value intercept points for an attacker.
How OpenSSL finds and parses its configuration, from environment variable through config file sections through provider loading — and where the worlock incident's attack path entered this chain.
The six phases of plugin loading: discovery → dlopen → symbol resolution → initialization → algorithm registration → active dispatch. Includes the failure paths that produced the worlock crash.
Maps each TLS 1.3 handshake step to the specific provider function calls it triggers, annotated with what a malicious provider intercepts at each step, and the OQS hybrid KEM downgrade opportunity.
These diagrams show the physical and logical structure of worlock and how the attack surface maps onto it.
Hardware through kernel through systemd services, with attack surface nodes marked. Includes GPU configuration, NIC state, and the specific services affected by the OQS incident.
Which processes link against libssl/libcrypto, and which link libcrypto only. The blast radius of a poisoned provider is every process in this graph. Includes the surprising entries: sort, grep, lsof.
The systemd service dependency tree for worlock, annotated with which services touch SSL and how a provider failure propagates through the dependency graph.
External-facing attack surface: UFW rules, exposed ports, VPN state, WAN SSH out-of-band path, and the Cloudflare tunnel. Maps what an external attacker sees vs. what the OQS internal attack surface is.
How environment variables flow from /etc/environment through PAM into every child process at login. Includes the user-level attack via ~/.pam_environment and the systemd escape hatch.
These diagrams are grounded in actual data from the May 27-29 incident logs.
Chronological map of every crash event from boot to stable recovery, with timestamps from journalctl -b -1 -p err.
Ranked breakdown of the 12 core dumps across 6 processes, with signal types and timestamps. The coreutils entries are the diagnostic key.
Cross-matrix of which processes were exposed to which root causes (OPENSSL_CONF scope, APT mirror, ABI mismatch, missing baseline). Identifies the minimum fix set needed.
Boot-index view of all recent boots from -13 to 0, with uptime bars and crash markers. Shows the affected boot window vs. stable history.
Mean Time to Detect and Mean Time to Recover — actual vs. projected with monitoring in place. Quantifies the value of the out-of-band SSH path and the cost of the missing monitoring layer.
Step-by-step causal flows from root cause to observed effect.
Formal fault tree with AND/OR gates from the top event (system DoS) through intermediate events to the two root causes and their enabling conditions.
Ten-step walk through the exact mechanism: process start → OPENSSL_init_ssl() → config resolution → dlopen → rtld ABI mismatch → SIGSEGV/SIGILL/SIGABRT → repeat for all processes.
The parallel causal chains — OQS dev build (primary) and APT mirror corruption (concurrent) — converging to produce the crash cascade, including the recovery paradox where dpkg-query itself crashed.
What each of the five fix steps resolved and what remained as gaps afterward. Includes priority ordering and the remaining TODO items.
Step-by-step detection flow: if you suspect an OQS provider issue, what to check in what order, and what each check result implies about which threat vector is active.
Defensive and architectural evolution paths for the OQS provider ecosystem.
NIST PQC finalization (2024) → TLS 1.3 hybrid default (2027) → distro packaging (2028) → classical deprecation (2033) → CRHQ horizon (~2035). Anchored to worlock's current position on this timeline.
TPM-backed provider verification: build-time GPG signing → install-time hash verification → boot-time PCR extension → runtime remote attestation. Maps the gap between worlock's current state (Level 0) and SLSA Level 3.
Three-layer monitoring pipeline: inotifywait filesystem events (instant) → systemd timer hash check (15-minute periodic) → strace behavioral audit (on-demand). With automated quarantine and service restart response.
Current state (SLSA Level 0: no provenance) → Level 1 (documented build) → Level 2 (verified source via git verify-tag) → Level 3 (reproducible, signed, attested) → ideal state (distro package, ETA 2028).
Per-service OPENSSL_CONF isolation eliminates the blast radius of any single provider compromise. The anti-pattern (global OPENSSL_CONF) vs. the target architecture (each service declares its own crypto config), with the containment result demonstrated by the worlock fix.
# 1. Remove OPENSSL_CONF from global environment
grep -n OPENSSL_CONF /etc/environment # should return nothing
# If set: remove or comment it out
# 2. Scope to the specific service that needs it
# /etc/systemd/system/my-oqs-service.service
# [Service]
# Environment=OPENSSL_CONF=/etc/my-service-openssl.cnf
# 3. Verify only the default provider loads globally
openssl list -providers
# Expected: only "default" (active)
# 4. Create a baseline of ossl-modules
sha256sum /usr/lib/x86_64-linux-gnu/ossl-modules/*.so > /etc/ossl-modules.sha256
chmod 600 /etc/ossl-modules.sha256# 5. Periodic integrity check (add to cron or systemd timer)
sha256sum -c /etc/ossl-modules.sha256
# 6. Filesystem watch (install inotify-tools)
# inotifywait -m /usr/lib/x86_64-linux-gnu/ossl-modules/ -e create,modify,delete
# 7. Verify OQS provider is NOT loaded by default processes
strace -e trace=network openssl speed aes-256-gcm 2>&1 | grep -c connect
# Expected: 0
# 8. Check for unexpected ossl-modules entries
ls /usr/lib/x86_64-linux-gnu/ossl-modules/
# Should only contain: legacy.so, oqsprovider.so (if intentional)# 9. Pin OQS builds to GPG-signed tags
cd liboqs && git verify-tag 0.15.0 # import OQS maintainer keys first
# 10. Behavioral audit before enabling any new provider
strace -e trace=write,open,network openssl genrsa 2048 2>/dev/null | head -20
# 11. Disable stale services
systemctl disable network-queue-optimization.service # DONE 2026-05-29Before enabling any non-default OpenSSL provider in production:
- Provider
.sohash recorded in/etc/ossl-modules.sha256 -
OPENSSL_CONFscoped to the specific service — not in/etc/environment - Build verified against a GPG-signed upstream tag
-
openssl list -providers -verboseshows expected name and version -
stracebehavioral check: no unexpectedconnect()/write()calls - Recovery path tested: can you reach the system if all SSL processes crash?
-
inotifywaitwatch active onossl-modules/
| Artifact | Path | State |
|---|---|---|
| OQS provider | /usr/lib/x86_64-linux-gnu/ossl-modules/oqsprovider.so |
Stable 0.11.0 (rebuilt May 27) |
| liboqs | /usr/local/lib/liboqs.so.9 (→ liboqs.so.0.15.0) |
Stable (rebuilt May 27) |
| liboqs dev (removed) | /usr/local/lib/liboqs.so.8 |
Removed |
| OpenSSL config (dev) | /usr/local/ssl/openssl.cnf |
Still exists but not referenced |
| Global env | /etc/environment |
OPENSSL_CONF removed |
| gryphgen service | /etc/systemd/system/gryphgen-agentic.service |
Environment=OPENSSL_CONF=... scoped |
| Failing service | network-queue-optimization.service |
Disabled May 29 |
| Core dumps | /var/lib/systemd/coredump/ |
12 dumps (May 28-29) |
To reproduce the ABI confusion crash (in a safe test environment):
# Build two versions of liboqs with different SONAME
# Install both to /usr/local/lib/
# Create openssl.cnf pointing to oqsprovider compiled against v1
# but with RPATH/NEEDED referencing v2
# Set OPENSSL_CONF=/path/to/that/cnf
# Run: openssl list -providers → SIGSEGV
# DO NOT do this on a production system
# The crash will affect every SSL process on the machineTo reproduce the detection gap:
# Even with a "malicious" provider, this shows it as active:
openssl list -providers
# Output: default (active), oqsprovider (active) ← no behavioral info
# Behavioral check (the right way):
strace -f -e trace=network openssl speed kyber768 2>&1 | grep -E "connect|send|recv"
# A clean provider: no output
# A malicious exfil provider: connect() to attacker IP- Open Quantum Safe Project — liboqs and oqs-provider
- NIST PQC Standards — FIPS 203/204/205 (ML-KEM, ML-DSA, SLH-DSA)
- OpenSSL 3.x Provider API — OSSL_DISPATCH, provider hooks
- SLSA Framework — Supply chain security levels
- NIST SP 800-208 — Recommendation for Stateful Hash-Based Signature Schemes
- RFC 9180 — HPKE (Hybrid Public Key Encryption)
- draft-ietf-tls-hybrid-design — TLS Hybrid Key Exchange
- Sigstore / cosign — Software artifact signing
| Category | Files |
|---|---|
| Session doc | session.md |
| Logo | logo.svg, logo.png |
| Threat Vectors (7) | tv1–tv7 × .dot + .png + .svg |
| Architecture (5) | arch1–arch5 × .dot + .png + .svg |
| Topology (5) | topo1–topo5 × .dot + .png + .svg |
| Empirical (5) | emp1–emp5 × .dot + .png + .svg |
| Causality (5) | cause1–cause5 × .dot + .png + .svg |
| Future (5) | future1–future5 × .dot + .png + .svg |
| Total | 32 diagrams × 3 formats = 96 diagram files |
Generated 2026-05-29 on worlock (Linux 6.8.12). All artifact paths verified against live system. No fabricated CVEs. Intended for defensive security research and operator awareness.
































