projection: begin the exact-path public epoch

The public tree and its history contain only the listed paths. Earlier projection history remains preserved internally.

Source-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Policy-Sha: 913fde029b935671833254797f0f20f1eb9fabba

Tree-Digest: 180ae530c1058a2a5c89837bdce2d323ae83e669e38590ca72e75b8d92b7262f
This commit is contained in:
Fimeg 2026-09-08 21:59:33 -04:00
commit 67e26be2d9
838 changed files with 157302 additions and 0 deletions

View file

@ -0,0 +1,310 @@
package kernel
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"os"
"os/signal"
"sync"
"syscall"
"time"
"unsafe"
"github.com/Fimeg/RedFlag/agent/internal/config"
"github.com/Fimeg/RedFlag/agent/internal/event"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/ringbuf"
"github.com/cilium/ebpf/rlimit"
)
const (
// MAGIC_NUMBER matches the eBPF program
MAGIC_NUMBER = 0xDEADBEEF
)
// EBPFConsumer reads events from the eBPF ring buffer and forwards them to the policy checker
type EBPFConsumer struct {
config *config.Config
running bool
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
wgLock sync.Mutex
rd *ringbuf.Reader
elfPath string
teeLogger *event.TeeLogger
}
// NewEBPFConsumer creates a new eBPF ring buffer consumer
func NewEBPFConsumer(cfg *config.Config) (*EBPFConsumer, error) {
return NewEBPFConsumerWithLogger(cfg, nil)
}
func NewEBPFConsumerWithLogger(cfg *config.Config, teeLogger *event.TeeLogger) (*EBPFConsumer, error) {
ctx, cancel := context.WithCancel(context.Background())
return &EBPFConsumer{
config: cfg,
ctx: ctx,
cancel: cancel,
elfPath: cfg.KernelEnforcement.RingBufferPath + "/pkg-gate.o",
teeLogger: teeLogger,
}, nil
}
// Start begins consuming events from the eBPF ring buffer
func (e *EBPFConsumer) Start() error {
e.wgLock.Lock()
if e.running {
e.wgLock.Unlock()
return nil
}
e.running = true
e.wgLock.Unlock()
log.Printf("[INFO] [kernel] [ebpf] consumer_started")
// Remove memlock limit for eBPF
if err := rlimit.RemoveMemlock(); err != nil {
return fmt.Errorf("failed to remove memlock: %w", err)
}
// Load the eBPF collection from the ELF file
spec, err := ebpf.LoadCollectionSpec(e.elfPath)
if err != nil {
return fmt.Errorf("failed to load eBPF collection spec: %w", err)
}
// Find the ring buffer map by name
_, ok := spec.Maps["rb_map"]
if !ok {
return fmt.Errorf("ring buffer map 'rb_map' not found in eBPF collection")
}
// Load collection to get loaded maps
coll, err := ebpf.NewCollection(spec)
if err != nil {
return fmt.Errorf("failed to load eBPF collection: %w", err)
}
// Access the loaded ring buffer map
loadedMap := coll.Maps["rb_map"]
if loadedMap == nil {
coll.Close()
return fmt.Errorf("ring buffer map 'rb_map' not found in loaded collection")
}
// Create ring buffer reader
rd, err := ringbuf.NewReader(loadedMap)
if err != nil {
coll.Close()
return fmt.Errorf("failed to create ring buffer: %w", err)
}
e.rd = rd
e.elfPath = "" // Don't load again on stop
// Set up signal handling for graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigChan
log.Printf("[INFO] [kernel] [ebpf] shutdown_signal_received")
e.cancel()
e.wg.Wait()
e.rd.Close()
}()
e.wg.Add(1)
go e.consumeEvents()
return nil
}
// Stop stops the consumer
func (e *EBPFConsumer) Stop() error {
e.cancel()
e.wgLock.Lock()
e.running = false
e.wgLock.Unlock()
e.wg.Wait()
if e.rd != nil {
e.rd.Close()
}
log.Printf("[INFO] [kernel] [ebpf] consumer_stopped")
return nil
}
// consumeEvents reads from the ring buffer and forwards events to the policy checker
func (e *EBPFConsumer) consumeEvents() {
defer e.wg.Done()
for {
select {
case <-e.ctx.Done():
return
default:
}
// Read event from ring buffer
record, err := e.rd.Read()
if err != nil {
if errors.Is(err, ringbuf.ErrClosed) {
return
}
e.teeLogger.Error("agent", "kernel", "kernel_enforcer", fmt.Sprintf("ringbuf_read_failed error=%v", err), map[string]interface{}{"error": err.Error()})
continue
}
e.processEvent(record.RawSample)
}
}
// processEvent handles a single eBPF event
func (e *EBPFConsumer) processEvent(data []byte) {
// Decode using the struct from eBPF (must match exactly)
// Layout: timestamp(8) pid(4) pgid(4) uid(8) gid(8) parent_pid(4) comm(16) cmdline(256) parent_comm(16) magic(4) = 328 bytes
event := &redflagEvent{}
// Use unsafe to copy raw bytes into the struct
bytess := (*[328]byte)(unsafe.Pointer(event))
copy(bytess[0:8], data[0:8])
copy(bytess[8:12], data[8:12])
copy(bytess[12:16], data[12:16])
copy(bytess[16:24], data[16:24])
copy(bytess[24:32], data[24:32])
copy(bytess[32:36], data[32:36])
copy(bytess[36:52], data[36:52])
copy(bytess[52:308], data[52:308])
copy(bytess[308:324], data[308:324])
copy(bytess[324:328], data[324:328])
// Validate magic number
if event.Magic != MAGIC_NUMBER {
e.teeLogger.Error("agent", "kernel", "kernel_enforcer", fmt.Sprintf("invalid_magic magic=0x%x expected=0x%x", event.Magic, MAGIC_NUMBER), map[string]interface{}{
"magic": fmt.Sprintf("0x%x", event.Magic),
"expected": fmt.Sprintf("0x%x", MAGIC_NUMBER),
})
return
}
// Forward to rs-helper for policy decision
decision, reason, err := e.checkPolicy(event)
if err != nil {
e.teeLogger.Error("agent", "kernel", "kernel_enforcer", fmt.Sprintf("policy_check_failed error=%v", err), map[string]interface{}{"error": err.Error()})
return
}
if decision == "deny" {
e.teeLogger.Error("agent", "kernel", "kernel_enforcer", fmt.Sprintf("execve_deny pid=%d comm=%s reason=%s", event.PID, event.Comm, reason), map[string]interface{}{
"pid": event.PID,
"comm": fmt.Sprintf("%s", event.Comm),
"reason": reason,
})
} else {
log.Printf("[INFO] [kernel] [ebpf] execve_allowed pid=%d comm=%s",
event.PID, event.Comm)
}
}
// redflagEvent matches the eBPF struct redflag_event layout
type redflagEvent struct {
Timestamp uint64
PID uint32
PGID uint32
UID uint64
GID uint64
ParentPID uint32
Comm [16]byte
Cmdline [256]byte
ParentComm [16]byte
Magic uint32
}
// checkPolicy evaluates the policy for a package manager invocation
func (e *EBPFConsumer) checkPolicy(event *redflagEvent) (string, string, error) {
// Decode strings for policy check
comm := *(*string)(unsafe.Pointer(&event.Comm))
cmdline := *(*string)(unsafe.Pointer(&event.Cmdline))
parentComm := *(*string)(unsafe.Pointer(&event.ParentComm))
log.Printf("[DEBUG] [kernel] [ebpf] policy_check comm=%s cmdline=%s parent=%s uid=%d",
comm, cmdline, parentComm, event.UID)
// Check if this is a package manager command
isPackageManager := isPackageManager(comm)
if !isPackageManager {
log.Printf("[INFO] [kernel] [ebpf] not_package_manager comm=%s", comm)
return "allow", "not_package_manager", nil
}
// TODO: Call rs-helper via Unix socket for policy decision
// For now, return allow (agent should have rs-helper running)
return "allow", "policy_evaluation_pending", nil
}
// isPackageManager checks if a command is a known package manager
func isPackageManager(comm string) bool {
pkgManagers := []string{"apt", "apt-get", "apt-cache", "dnf", "yum", "rpm-ostree",
"npm", "pnpm", "bun", "pip", "pip3", "uv",
"docker", "crun", "containerd"}
for _, pm := range pkgManagers {
if comm == pm {
return true
}
}
return false
}
// Event represents a typed intercept event for API use
type Event struct {
Timestamp time.Time
PID uint32
PGID uint32
Comm string
Cmdline string
UID uint64
GID uint64
ParentPID uint32
ParentComm string
}
// FromBinary creates an Event from binary data (deprecated, use direct struct access)
func FromBinary(data []byte) (*Event, error) {
if len(data) < 328 {
return nil, fmt.Errorf("data too short: %d", len(data))
}
event := &redflagEvent{}
// Use unsafe to copy raw bytes into the struct
bytess := (*[328]byte)(unsafe.Pointer(event))
copy(bytess[0:8], data[0:8])
copy(bytess[8:12], data[8:12])
copy(bytess[12:16], data[12:16])
copy(bytess[16:24], data[16:24])
copy(bytess[24:32], data[24:32])
copy(bytess[32:36], data[32:36])
copy(bytess[36:52], data[36:52])
copy(bytess[52:308], data[52:308])
copy(bytess[308:324], data[308:324])
copy(bytess[324:328], data[324:328])
return &Event{
Timestamp: time.Unix(0, int64(event.Timestamp)),
PID: event.PID,
PGID: event.PGID,
Comm: string(bytes.TrimRight(event.Comm[:], "\x00")),
Cmdline: string(bytes.TrimRight(event.Cmdline[:], "\x00")),
UID: event.UID,
GID: event.GID,
ParentPID: event.ParentPID,
ParentComm: string(bytes.TrimRight(event.ParentComm[:], "\x00")),
}, nil
}

View file

@ -0,0 +1,84 @@
package kernel
import (
"context"
"log"
"github.com/Fimeg/RedFlag/agent/internal/config"
"github.com/Fimeg/RedFlag/agent/internal/event"
)
type Enforcer interface {
Start(ctx context.Context) error
Stop() error
GetPackageType() string
}
type EnforcerFactory func(*config.Config, *event.TeeLogger) (Enforcer, error)
var enforcers = map[string]EnforcerFactory{}
func RegisterEnforcer(pkgType string, factory EnforcerFactory) {
enforcers[pkgType] = factory
}
func NewEnforcerWithLogger(cfg *config.Config, teeLogger *event.TeeLogger) (Enforcer, error) {
if !cfg.KernelEnforcement.Enabled {
return &noopEnforcer{}, nil
}
// Determine package type based on platform
pkgType := "linux"
if cfg.OS.Type == "windows" {
pkgType = "windows"
}
if factory, ok := enforcers[pkgType]; ok {
return factory(cfg, teeLogger)
}
log.Printf("[WARNING] [kernel] unknown_package_type=%s", pkgType)
return &noopEnforcer{}, nil
}
type noopEnforcer struct{}
func (n *noopEnforcer) Start(ctx context.Context) error { return nil }
func (n *noopEnforcer) Stop() error { return nil }
func (n *noopEnforcer) GetPackageType() string { return "noop" }
type EBPFEnforcer struct {
consumer *EBPFConsumer
}
func NewEBPFEnforcer(cfg *config.Config) (*EBPFEnforcer, error) {
return NewEBPFEnforcerWithLogger(cfg, nil)
}
func NewEBPFEnforcerWithLogger(cfg *config.Config, teeLogger *event.TeeLogger) (*EBPFEnforcer, error) {
consumer, err := NewEBPFConsumerWithLogger(cfg, teeLogger)
if err != nil {
return nil, err
}
return &EBPFEnforcer{consumer: consumer}, nil
}
func (e *EBPFEnforcer) Start(ctx context.Context) error {
return e.consumer.Start()
}
func (e *EBPFEnforcer) Stop() error {
return e.consumer.Stop()
}
func (e *EBPFEnforcer) GetPackageType() string { return "linux" }
func init() {
RegisterEnforcer("linux", func(cfg *config.Config, teeLogger *event.TeeLogger) (Enforcer, error) {
consumer, err := NewEBPFConsumerWithLogger(cfg, teeLogger)
if err != nil {
return nil, err
}
return &EBPFEnforcer{consumer: consumer}, nil
})
}

View file

@ -0,0 +1,164 @@
package kernel
import (
"context"
"fmt"
"log"
"time"
"github.com/Fimeg/RedFlag/agent/internal/client"
"github.com/Fimeg/RedFlag/agent/internal/config"
)
// WDACPolicy handles Windows WDAC (Windows Defender Application Control) enforcement
type WDACPolicy struct {
client *client.Client
config *config.Config
policyName string
policyHash string
}
// NewWDACPolicy creates a new WDACPolicy enforcer
func NewWDACPolicy(c *client.Client, cfg *config.Config) *WDACPolicy {
return &WDACPolicy{
client: c,
config: cfg,
policyName: "RedFlag-Package-Manager",
}
}
// GetPackageType returns the package type this enforcer handles
func (w *WDACPolicy) GetPackageType() string {
return "wdac"
}
// IsAvailable checks if WDAC is available on this system
func (w *WDACPolicy) IsAvailable() bool {
// WDAC only available on Windows
return true // Platform check done by caller
}
// CheckPolicy evaluates if a package manager invocation is allowed
func (w *WDACPolicy) CheckPolicy(packageType, packageName, packageVersion string) (bool, string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
log.Printf("[INFO] [kernel] [wdac] policy_check package=%s type=%s version=%s", packageName, packageType, packageVersion)
result, err := w.fetchPolicy(ctx)
if err != nil {
log.Printf("[ERROR] [kernel] [wdac] policy_fetch_failed package=%s error=%v", packageName, err)
return false, "policy_fetch_failed: " + err.Error(), nil
}
if result == "" {
return false, "no_policy", nil
}
// Parse policy and check if package manager is allowed
allowed, reason := w.evaluatePolicy(packageType, packageName, result)
if !allowed {
log.Printf("[ERROR] [kernel] [wdac] policy_denied package=%s reason=%s", packageName, reason)
} else {
log.Printf("[INFO] [kernel] [wdac] policy_allowed package=%s", packageName)
}
return allowed, reason, nil
}
// fetchPolicy retrieves the current WDAC policy from the server
func (w *WDACPolicy) fetchPolicy(ctx context.Context) (string, error) {
// TODO: Replace with actual WDAC COM API calls via go-ole
// For now, fetch policy hash from server and validate against local policy
hash, err := w.client.GetWdacPolicyHash(w.config.AgentID, w.config.ServerURL)
if err != nil {
log.Printf("[WARNING] [kernel] [wdac] policy_hash_fetch_skipped error=%v", err)
// For now, treat as no policy available (fail-closed)
return "", fmt.Errorf("no_policy")
}
if hash == "" {
return "", fmt.Errorf("no WDAC policy available from server")
}
if hash != w.policyHash {
log.Printf("[WARNING] [kernel] [wdac] policy_hash_mismatch local=%s server=%s", w.policyHash, hash)
// Policy needs update - return empty to trigger update
return "", fmt.Errorf("policy_hash_mismatch")
}
return w.policyHash, nil
}
// evaluatePolicy checks if the package manager is allowed by the current policy
func (w *WDACPolicy) evaluatePolicy(packageType, packageName, policyHash string) (bool, string) {
// Allowed package managers (fail-closed: deny all others)
allowed := map[string]bool{
"apt": true,
"apt-get": true,
"apt-cache": true,
"dnf": true,
"yum": true,
"rpm-ostree": true,
"npm": true,
"pnpm": true,
"bun": true,
"pip": true,
"pip3": true,
"uv": true,
"docker": true,
"crun": true,
"containerd": true,
}
if allowed[packageType] {
return true, "allowed_by_policy"
}
return false, "package_manager_not_in_policy"
}
// UpdatePolicy downloads and installs a new WDAC policy from the server
func (w *WDACPolicy) UpdatePolicy() error {
log.Printf("[INFO] [kernel] [wdac] update_policy_start")
newHash, err := w.client.GetWdacPolicyHash(w.config.AgentID, w.config.ServerURL)
if err != nil {
// For now, keep existing policy hash (no update)
log.Printf("[WARNING] [kernel] [wdac] policy_update_skipped error=%v", err)
return nil
}
if newHash == "" {
return fmt.Errorf("no policy available from server")
}
// TODO: Download policy file and apply via Set-CIPolicy
// For now, just update the hash
w.policyHash = newHash
log.Printf("[INFO] [kernel] [wdac] update_policy_complete hash=%s", newHash[:16]+"...")
return nil
}
// InstallPolicy installs a WDAC policy from a binary or XML file
func (w *WDACPolicy) InstallPolicy(policyPath string) error {
// TODO: Implement actual WDAC policy installation via COM API
// Uses Microsoft.Management.Ops or similar COM interface
log.Printf("[INFO] [kernel] [wdac] install_policy path=%s", policyPath)
return fmt.Errorf("install_policy_not_implemented_yet")
}
// GetPolicyStatus returns the current WDAC policy status
func (w *WDACPolicy) GetPolicyStatus() (string, error) {
// TODO: Query WDAC service for status
return "unknown", nil
}
// ResetPolicy resets the WDAC policy to a known-good state
func (w *WDACPolicy) ResetPolicy() error {
log.Printf("[WARNING] [kernel] [wdac] reset_policy_initiated")
return fmt.Errorf("reset_policy_not_implemented_yet")
}