

ICARAX Tech Blog – Practical Guide for Developers & Ops
⚠️ Disclaimer – The code below is defensive only. It helps you detect whether a host is running a vulnerable kernel version and apply basic mitigations (e.g., sysctl hardening, audit rules, eBPF filters). It does not contain or facilitate exploitation. Use it responsibly on systems you own or have explicit permission to test.
<a name="prerequisites"></a>
| Item | Why you need it | Minimum version |
|---|---|---|
| Linux host (any distro) | To run the detection scripts | Kernel ≥ 3.10 (for /proc/version access) |
| Python 3.8+ | For the Python detector | 3.8 |
| Node.js 14+ (or Deno) | For the JS/TS detector | 14 LTS |
| Git | To clone the example repo (optional) | any |
| Root / sudo | To apply sysctl/audit/eBPF mitigations | – |
| Optional – Monitoring endpoint (e.g., Slack webhook, Datadog, Prometheus Pushgateway) | To send alerts when a vulnerable host is found | – |
Tip: If you are running inside a container, you still need access to the host’s
/proc/version(usually bind‑mounted) or the ability to run privileged commands (nsenter,docker run --privileged, etc.).
<a name="installation--setup"></a>
# Update package index
sudo apt-get update
# Install core utilities
sudo apt-get install -y curl gnupg lsb-release
# Install Python 3 (if not present)
sudo apt-get install -y python3 python3-pip
# Install Node.js (using NodeSource)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install optional audit utilities (for auditd rules)
sudo apt-get install -y auditd audispd-plugins
# Install eBPF tools (bpftrace) – useful for live monitoring
sudo apt-get install -y bpftrace
Create a virtual environment (recommended) and install the tiny helper library psutil for parsing /proc/version in a cross‑platform way.
# Inside your project folder
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install psutil python-dotenv
mkdir -p icaraix-detector/js
cd icaraix-detector/js
npm init -y
npm install dotenv child_process # child_process is built‑in; dotenv for env vars
<a name="basic-implementation"></a>
Both implementations follow the same logic:
/proc/version.Known fixed versions (as of the public disclosure date for this 19‑year‑old bug):
- 5.10.0‑rc1 and newer (all 5.10+ releases)
- 5.4.0‑rc1 and newer (if your distro back‑ported the fix)
- 4.19.0‑rc1 and newer (some long‑term kernels)
Adjust the
SAFE_VERSIONSlist to match your distro’s patch policy.
detect_vuln.py)#!/usr/bin/env python3
"""
detect_vuln.py – Defensive check for the 19‑year‑old Linux kernel vulnerability.
Usage:
python3 detect_vuln.py # prints JSON to stdout
python3 detect_vuln.py --apply # also attempts basic sysctl hardening
"""
import json
import re
import sys
import argparse
from typing import Tuple, List
import psutil # only for graceful fallback; not strictly needed
from dotenv import load_dotenv
import os
load_dotenv() # loads .env file if present
# ----------------------------------------------------------------------
# Configuration (can be overridden via env vars)
# ----------------------------------------------------------------------
# List of kernel versions known to be *safe* (major, minor, patch, …)
# Format: tuple of ints; missing components are treated as 0.
SAFE_VERSIONS: List[Tuple[int, ...]] = [
(5, 10, 0), # 5.10.0
(5, 4, 0), # 5.4.0
(4, 19, 0), # 4.19.0
]
# Optional alerting – e.g., Slack webhook URL
SLACK_WEBHOOK = os.getenv("SLACK_WEBHOOK_URL")
# ----------------------------------------------------------------------
# Helper functions
# ----------------------------------------------------------------------
def parse_kernel_version(raw: str) -> Tuple[int, ...]:
"""
Extract version numbers from a string like:
Linux version 5.15.0-76-generic (buildd@lcy02-amd64-007) ...
Returns a tuple of ints (major, minor, patch, …).
"""
# Grab the first sequence of numbers separated by dots
match = re.search(r"Linux version\s+([0-9]+(?:\.[0-9]+)+)", raw)
if not match:
raise ValueError("Unable to parse kernel version from /proc/version")
version_str = match.group(1)
parts = tuple(int(p) for p in version_str.split("."))
return parts
def is_version_safe(current: Tuple[int, ...], safe_list: List[Tuple[int, ...]]) -> bool:
"""
Returns True if `current` >= any entry in `safe_list`.
Comparison is lexicographic on the tuple.
"""
for safe in safe_list:
if current >= safe:
return True
return False
def read_proc_version() -> str:
try:
with open("/proc/version", "r", encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
raise RuntimeError("/proc/version not found – are you running on Linux?")
def send_slack_alert(message: str) -> None:
if not SLACK_WEBHOOK:
return # silently skip if not configured
try:
import urllib.request
import json as jsonlib
data = jsonlib.dumps({"text": message}).encode("utf-8")
req = urllib.request.Request(
SLACK_WEBHOOK,
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=5) as resp:
resp.read() # discard
except Exception as exc: # pragma: no cover – network failures are non‑critical
sys.stderr.write(f"[WARN] Failed to send Slack alert: {exc}\n")
def apply_sysctl_hardening() -> None:
"""
Very basic hardening: enable kernel.exec-shield and restrict ptrace.
These are *defensive* measures; they do not guarantee safety.
"""
cmds = [
("kernel.exec-shield", "1"),
("kernel.yama.ptrace_scope", "2"), # restrict ptrace to parent only
]
for key, value in cmds:
try:
with open(f"/proc/sys/{key.replace('.', '/')}", "w") as f:
f.write(value + "\n")
print(f"[INFO] Set {key}={value}")
except PermissionError:
print(f"[ERROR] Permission denied setting {key}. Run as root/sudo.", file=sys.stderr)
except FileNotFoundError:
print(f"[WARN] Sysctl {key} not available on this kernel.", file=sys.stderr)
# ----------------------------------------------------------------------
# Main routine
# ----------------------------------------------------------------------
def main() -> None:
parser = argparse.ArgumentParser(
description="Detect if the running kernel is vulnerable to the 19‑year‑old LPE."
)
parser.add_argument(
"--apply",
action="store_true",
help="Apply basic sysctl hardening after detection (requires root).",
)
args = parser.parse_args()
try:
raw = read_proc_version()
cur_ver = parse_kernel_version(raw)
safe = is_version_safe(cur_ver, SAFE_VERSIONS)
result = {
"kernel_raw": raw.strip(),
"kernel_version_tuple": cur_ver,
"is_safe": safe,
"checked_against": [str(v) for v in SAFE_VERSIONS],
}
print(json.dumps(result, indent=2))
if not safe:
msg = (
f":rotating_light: *Vulnerable kernel detected* on host `{os.uname().nodename}`\n"
f"Version: `{'.'.join(map(str, cur_ver))}`\n"
f"Consider upgrading to a safe kernel (≥ {'.'.join(map(str, SAFE_VERSIONS[0]))})."
)
print("\n[ALERT] " + msg, file=sys.stderr)
send_slack_alert(msg)
if args.apply:
if os.geteuid() != 0:
sys.stderr.write("[ERROR] --apply requires root privileges.\n")
sys.exit(1)
apply_sysctl_hardening()
except Exception as exc: # pragma: no cover
print(json.dumps({"error": str(exc)}), indent=2)
sys.exit(1)
if __name__ == "__main__":
main()
How to run
# Make it executable (optional)
chmod +x detect_vuln.py
# Basic detection
python3 detect_vuln.py
# Detection + apply hardening (needs sudo)
sudo python3 detect_vuln.py --apply
detectVuln.ts)The TS version compiles to plain JavaScript; you can also run the
.jsfile directly withnode.
#!/usr/bin/env node
/**
* detectVuln.ts – Node.js/TypeScript defensive check for the 19‑year‑old Linux kernel LPE.
*
* Usage:
* npx ts-node detectVuln.ts # detection only
* npx ts-node detectVuln.ts --apply # detection + sysctl hardening (requires root)
*/
import { readFileSync } from "fs";
import { execSync } from "child_process";
import * as dotenv from "dotenv";
import { parse } from "path";
dotenv.config(); // loads .env if present
// ----------------------------------------------------------------------
// Configuration (override via .env)
// ----------------------------------------------------------------------
const SAFE_VERSIONS: number[][] = [
[5, 10, 0], // 5.10.0
[5, 4, 0], // 5.4.0
[4, 19, 0], // 4.19.0
];
const SLACK_WEBHOOK = process.env.SLACK_WEBHOOK_URL ?? "";
// ----------------------------------------------------------------------
// Helper functions
// ----------------------------------------------------------------------
function parseKernelVersion(raw: string): number[] {
const match = raw.match(/Linux version\s+([0-9]+(?:\.[0-9]+)+)/);
if (!match) {
throw new Error("Unable to parse kernel version from /proc/version");
}
return match[1].split(".").map(Number);
}
function isVersionSafe(current: number[], safeList: number[][]): boolean {
return safeList.some((safe) => {
// lexicographic compare
for (let i = 0; i < Math.max(current.length, safe.length); i++) {
const cur = current[i] ?? 0;
const saf = safe[i] ?? 0;
if (cur > saf) return true;
if (cur < saf) return false;
}
return true; // equal
});
}
function readProcVersion(): string {
try {
return readFileSync("/proc/version", "utf8");
} catch (e) {
throw new Error("/proc/version not found – are you running on Linux?");
}
}
function sendSlackAlert(message: string): void {
if (!SLACK_WEBHOOK) return;
try {
const https = require("https");
const data = JSON.stringify({ text: message });
const options = {
hostname: new URL(SLACK_WEBHOOK).hostname,
path: new URL(SLACK_WEBHOOK).pathname,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(data),
},
};
const req = https.request(options, (res) => {
let body = "";
res.on("data", (chunk) => (body += chunk));
res.on("end", () => {
// Ignore response; failures are logged to stderr later
});
});
req.on("error", (err: any) => {
console.error("[WARN] Slack alert failed:", err.message);
});
req.write(data);
req.end();
} catch (err: any) {
console.error("[WARN] Slack alert failed:", err.message);
}
}
function applySysctlHardening(): void {
const cmds: [string, string][] = [
["kernel.exec-shield", "1"],
["kernel.yama.ptrace_scope", "2"],
];
for (const [key, value] of cmds) {
try {
const path = `/proc/sys/${key.replace(/\./g, "/")}`;
execSync(`echo ${value} > ${path}`, { stdio: "ignore" });
console.log(`[INFO] Set ${key}=${value}`);
} catch (err: any) {
if (err.code === "EACCES") {
console.error(
`[ERROR] Permission denied setting ${key}. Run as root/sudo.`
);
} else if (err.code === "ENOENT") {
console.warn(`[WARN] Sysctl ${key} not available on this kernel.`);
} else {
console.error(`[ERROR] Failed to set ${key}:`, err.message);
}
}
}
}
// ----------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------
async function main() {
const args = process.argv.slice(2);
const applyFlag = args.includes("--apply");
try {
const raw = readProcVersion();
const cur = parseKernelVersion(raw);
const safe = isVersionSafe(cur, SAFE_VERSIONS);
const result = {
kernel_raw: raw.trim(),
kernel_version_tuple: cur,
is_safe: safe,
checked_against: SAFE_VERSIONS.map((v) => v.join(".")),
};
console.log(JSON.stringify(result, null, 2));
if (!safe) {
const msg =
`:rotating_light: *Vulnerable kernel detected* on host ${require("os")
.hostname()}\n` +
`Version: ${cur.join(".")}\n` +
`Consider upgrading to a safe kernel (≥ ${SAFE_VERSIONS[0]
.join(".")}).`;
console.error("\n[ALERT] " + msg);
sendSlackAlert(msg);
}
if (applyFlag) {
if (process.getuid && process.getuid() !== 0) {
console.error("[ERROR] --apply requires root privileges.");
process.exit(1);
}
applySysctlHardening();
}
} catch (err: any) {
console.error(JSON.stringify({ error: err.message }, null, 2));
process.exit(1);
}
}
main().catch((e) => {
console.error("Unexpected error:", e);
process.exit(1);
});
How to run
# Install TS-node if you don't have it globally
npm install -g ts-node typescript @types/node
# Detection only
ts-node detectVuln.ts
# Detection + hardening (needs sudo)
sudo ts-node detectVuln.ts --apply
If you prefer plain JavaScript, compile with tsc and run the generated detectVuln.js with node.
<a name="configuration"></a>
Create a .env file in the project root (next to the scripts) to store optional alerting credentials.
# .env – keep this file out of version control (add to .gitignore)
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX
You can also export the variable directly:
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
Other knobs
| Variable | Purpose | Example |
|---|---|---|
SAFE_VERSIONS | Override the list of safe kernels (comma‑separated, each as major.minor.patch) | SAFE_VERSIONS="5.10.0,5.4.0,4.19.0" |
ALERT_COOLDOWN_SECONDS | Minimum time between Slack alerts to avoid spam | ALERT_COOLDOWN_SECONDS=300 |
SYSCTL_KEYS | Space‑separated list of sysctl keys to harden when --apply is used | SYSCTL_KEYS="kernel.exec-shield kernel.yama.ptrace_scope" |
The scripts read these values via dotenv (process.env in Node, os.getenv in Python). Adjust them to match your environment’s policy.
<a name="common-patterns"></a>
Add a cron entry to run the detector every hour and log the output.
# /etc/cron.d/ICARAX_kernel_vuln_detector
0 * * * * root /usr/local/bin/detect_vuln.py >> /var/log/kernel_vuln_detect.log 2>&1
Service file (/etc/systemd/system/kernel-vuln-detector.service)
[Unit]
Description=Detect 19‑year‑old Linux kernel vulnerability
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/detect_vuln.py --apply
# Optional: send output to journald
StandardOutput=journal
StandardError=journal
Timer file (/etc/systemd/system/kernel-vuln-detector.timer)
[Unit]
Description=Run kernel vulnerability detector hourly
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
Persistent=true
[Install]
WantedBy=timers.target
Enable & start:
sudo systemctl daemon-reload
sudo systemctl enable --now kernel-vuln-detector.timer
If you expose a metric like kernel_vulnerable{host="myhost"} 1 you can scrape it.
Python snippet (add to detect_vuln.py after evaluation)
from prometheus_client import CollectorRegistry, Gauge, push_to_gateway
REGISTRY = CollectorRegistry()
GAUGE = Gauge(
"kernel_vulnerable",
"1 if the running kernel is considered vulnerable, 0 otherwise",
["host"],
registry=REGISTRY,
)
GAUGE.labels(host=os.uname().nodename).set(0 if safe else 1)
push_to_gateway("pushgateway.example.com:9091", job="kernel_vuln_check", registry=REGISTRY)
Install the client: pip install prometheus_client.
<a name="troubleshooting"></a>
| Symptom | Likely Cause | Fix |
|---|---|---|
FileNotFoundError: [Errno 2] No such file or directory: '/proc/version' | Running inside a container without host /proc mounted. | Start container with --pid=host or bind‑mount /proc: docker run -v /proc:/host/proc ... and adjust script to read /host/proc/version. |
Permission denied when applying sysctl | Script executed without root. | Use sudo or run as root. |
| Slack alerts never arrive | SLACK_WEBHOOK_URL not set or network blocked. | Verify .env is loaded (echo $SLACK_WEBHOOK_URL). Test with curl -X POST -H 'Content-type: application/json' --data '{"text":"test"}' $SLACK_WEBHOOK_URL. |
| Detector reports “safe” but you know the kernel is vulnerable | SAFE_VERSIONS list outdated for your distro’s backported patches. | Add the exact version string reported by uname -r to SAFE_VERSIONS (or consult your vendor’s CVE advisory). |
High CPU usage when running via bpftrace (if you added a live eBPF filter) | Over‑broad tracepoint (e.g., tracing all syscalls). | Narrow the tracepoint to the specific syscall used by the exploit (e.g., tracepoint:syscalls:sys_enter_ptrace). |
npm ERR! code ERESOLVE when installing dependencies | Version conflict with existing lockfile. | Delete package-lock.json and node_modules, then npm install. |
ts-node: command not found | TypeScript not installed globally or locally. | Install locally: npm install -D ts-node typescript @types/node and run via npx ts-node detectVuln.ts. |
<a name="production-checklist"></a>
Before you roll the detector out to fleets, verify the following:
| ✅ Item | Why it matters |
|---|---|
Least‑privilege execution – run the detector as an unprivileged user for detection only; elevate only when --apply is needed (via sudoers rule with NOPASSWD for the specific script). | |
Log rotation – ensure /var/log/kernel_vuln_detect.log (or journald) is rotated to avoid disk exhaustion. | |
Alert deduplication – implement a cooldown (e.g., store last alert timestamp in /var/lib/kernel_vuln_detector/last_alert) to prevent alert storms during mass patching windows. | |
| Test on a staging host – confirm the script correctly flags a known‑vulnerable kernel (you can temporarily boot an older kernel in a VM). | |
Version pinning – lock the Python/Node dependencies (pip freeze > requirements.txt, npm shrinkwrap) to avoid surprise breaks from upstream updates. | |
Integrate with change‑management – tie the detector’s output to your patch‑management workflow (e.g., create a JIRA ticket when is_safe == false). | |
| Document the sysctl hardening – record which sysctl keys you modify and why, so auditors can verify they align with your security baseline. | |
Backup /proc/sys – before applying sysctl changes, snapshot current values (sysctl -a > /etc/sysctl.d/backup_$(date +%F).conf). | |
| Monitor the detector itself – set up a heartbeat (e.g., a simple cron that writes a timestamp) to alert if the detector stops running. | |
| Review eBPF/bpftrace usage – if you add live tracing, ensure the probe is attached to the correct tracepoint and does not cause measurable performance degradation (>1 % CPU). | |
| Legal / policy check – verify that running detection scripts on production hosts complies with your organization’s acceptable‑use policy. |
You now have:
detect_vuln.py) that checks the kernel version, reports vulnerability status, optionally applies basic sysctl hardening, and can send Slack alerts.detectVuln.ts) with the same capabilities, suitable for JavaScript‑centric stacks.Deploy these defenders, keep your kernels patched, and let the alerts give you early warning when a host falls behind the curve. Stay safe!
Happy securing,
The ICARAX Team
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
