Cybersecurity-Projects/PROJECTS/beginner/linux-ebpf-security-tracer/src/loader.py

130 lines
3.3 KiB
Python

"""
©AngelaMos | 2026
loader.py
"""
from __future__ import annotations
import os
import signal
import sys
from collections.abc import Callable
from typing import Any
from .config import (
EBPF_DIR,
MIN_KERNEL_MAJOR,
MIN_KERNEL_MINOR,
TracerType,
)
TRACER_FILES: dict[str, str] = {
"process": "process_tracer.c",
"file": "file_tracer.c",
"network": "network_tracer.c",
"privilege": "privilege_tracer.c",
"system": "system_tracer.c",
}
def check_privileges() -> None:
"""
Verify the process has root privileges
"""
if os.geteuid() != 0:
sys.stderr.write("Error: eBPF tracing requires root privileges.\n"
"Run with: sudo uv run ebpf-tracer\n")
sys.exit(1)
def check_kernel_version() -> None:
"""
Verify the kernel version supports ring buffers
"""
release = os.uname().release
parts = release.split(".")
major = int(parts[0])
minor = int(parts[1].split("-")[0])
if (major < MIN_KERNEL_MAJOR
or (major == MIN_KERNEL_MAJOR and minor < MIN_KERNEL_MINOR)):
sys.stderr.write(f"Error: Kernel {release} detected. "
f"Requires {MIN_KERNEL_MAJOR}."
f"{MIN_KERNEL_MINOR}+ for ring buffer.\n")
sys.exit(1)
def _resolve_tracers(tracer_type: TracerType, ) -> list[str]:
"""
Determine which tracer files to load
"""
if tracer_type == "all":
return list(TRACER_FILES.keys())
return [tracer_type]
class TracerLoader:
"""
Loads and manages eBPF programs via BCC
"""
def __init__(
self,
tracer_type: TracerType,
callback: Callable[..., None],
) -> None:
"""
Initialize the loader with tracer selection
"""
self._bpf_objects: list[Any] = []
self._tracer_type = tracer_type
self._callback = callback
self._running = False
def load(self) -> None:
"""
Compile and load all selected eBPF programs
"""
from bcc import BPF # type: ignore[import-untyped]
tracers = _resolve_tracers(self._tracer_type)
for name in tracers:
filename = TRACER_FILES[name]
src_path = EBPF_DIR / filename
c_text = src_path.read_text()
bpf = BPF(text=c_text)
bpf["events"].open_ring_buffer(self._callback)
self._bpf_objects.append(bpf)
def poll(self) -> None:
"""
Start polling all ring buffers for events
"""
self._running = True
original_sigint = signal.getsignal(signal.SIGINT)
original_sigterm = signal.getsignal(signal.SIGTERM)
def _handle_stop(signum: int, frame: Any) -> None:
self._running = False
signal.signal(signal.SIGINT, _handle_stop)
signal.signal(signal.SIGTERM, _handle_stop)
try:
while self._running:
for bpf in self._bpf_objects:
bpf.ring_buffer_poll(timeout=100)
finally:
signal.signal(signal.SIGINT, original_sigint)
signal.signal(signal.SIGTERM, original_sigterm)
self.cleanup()
def cleanup(self) -> None:
"""
Detach all eBPF programs and free resources
"""
for bpf in self._bpf_objects:
bpf.cleanup()
self._bpf_objects.clear()