Files
Andrew LeFevreandAndrew LeFevre 7e0e6c9794 Fix unreadable Enhanced Session Recording events (#64581)
* refactor command and disk eBPF programs to fix some events not getting emitted

* fixed handling of flags in disk events

* remove unused constants to appease linter

* handle failing to read filename in enter_execve correctly

* added more tests

* prevent potential panic if args_len exceed args buffer size

* handle empty args in command events correctly

* minor changes from feedback

* update comments in eBPF files

---------

Co-authored-by: Andrew LeFevre <andrewlefevre@goteleport.com>
2026-05-04 12:25:25 +00:00

217 lines
5.1 KiB
Go

//go:build bpf && !386
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package bpf
import (
"context"
"io"
"sync"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/ringbuf"
"github.com/cilium/ebpf/rlimit"
"github.com/gravitational/trace"
"github.com/prometheus/client_golang/prometheus"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/observability/metrics"
)
var lostCommandEvents = prometheus.NewCounter(
prometheus.CounterOpts{
Name: teleport.MetricLostCommandEvents,
Help: "Number of lost command events.",
},
)
type exec struct {
// session
objs commandObjects
bpfEvents chan []byte
lostCounter *Counter
toClose []io.Closer
closed bool
flushBuf func() error
mtx sync.Mutex
wg sync.WaitGroup
}
// startExec will load, start, and pull events off the ring buffer
// for the BPF program.
func startExec(bufferSize int) (*exec, error) {
err := metrics.RegisterPrometheusCollectors(lostCommandEvents)
if err != nil {
return nil, trace.Wrap(err)
}
// Remove resource limits for kernels <5.11.
if err := rlimit.RemoveMemlock(); err != nil {
return nil, trace.WrapWithMessage(err, "Removing memlock")
}
var objs commandObjects
if err := loadCommandObjects(&objs, nil); err != nil {
return nil, trace.Wrap(err)
}
lostCtr, err := NewCounter(objs.LostCounter, objs.LostDoorbell, lostCommandEvents)
if err != nil {
return nil, trace.Wrap(err)
}
toClose := make([]io.Closer, 0)
tps := []struct {
name string
tracepoint *ebpf.Program
}{
{
name: "sys_enter_execve",
tracepoint: objs.TracepointSyscallsSysEnterExecve,
},
{
name: "sys_exit_execve",
tracepoint: objs.TracepointSyscallsSysExitExecve,
},
{
name: "sys_enter_execveat",
tracepoint: objs.TracepointSyscallsSysEnterExecveat,
},
{
name: "sys_exit_execveat",
tracepoint: objs.TracepointSyscallsSysExitExecveat,
},
}
for _, tp := range tps {
tp, err := link.Tracepoint("syscalls", tp.name, tp.tracepoint, nil)
if err != nil {
return nil, trace.Wrap(err)
}
toClose = append(toClose, tp)
}
lk, err := link.AttachTracing(link.TracingOptions{
Program: objs.BprmExecveExit,
AttachType: ebpf.AttachTraceFExit,
})
if err != nil {
return nil, trace.Wrap(err)
}
toClose = append(toClose, lk)
eventBuf, err := ringbuf.NewReader(objs.ExecveEvents)
if err != nil {
return nil, trace.Wrap(err)
}
e := &exec{
objs: objs,
lostCounter: lostCtr,
toClose: toClose,
flushBuf: eventBuf.Flush,
}
e.bpfEvents = make(chan []byte, bufferSize)
e.wg.Go(func() { sendEvents("command", e.bpfEvents, eventBuf) })
return e, nil
}
func (e *exec) startSession(auditSessionID uint32) error {
e.mtx.Lock()
defer e.mtx.Unlock()
if e.closed {
return trace.BadParameter("open session already closed")
}
if err := e.objs.MonitoredSessionids.Put(auditSessionID, uint8(0)); err != nil {
return trace.Wrap(err)
}
return nil
}
func (e *exec) endSession(auditSessionID uint32) error {
e.mtx.Lock()
defer e.mtx.Unlock()
if e.closed {
return nil
}
if err := e.objs.MonitoredSessionids.Delete(&auditSessionID); err != nil {
return trace.Wrap(err)
}
return nil
}
// close will stop reading events off the ring buffer and unload the BPF
// program. The ring buffer is closed as part of the module being closed.
func (e *exec) close() {
e.mtx.Lock()
if e.closed {
e.mtx.Unlock()
return
}
e.closed = true
if err := e.flushBuf(); err != nil {
logger.WarnContext(context.Background(), "failed to flush command ring buffer", "error", err)
} else {
logger.DebugContext(context.Background(), "Flushed command ring buffer, waiting for pending events to be processed")
}
for _, link := range e.toClose {
if err := link.Close(); err != nil {
logger.WarnContext(context.Background(), "failed to close link", "error", err)
}
}
if err := e.objs.Close(); err != nil {
logger.WarnContext(context.Background(), "failed to close command objects", "error", err)
}
if err := e.lostCounter.Close(); err != nil {
logger.WarnContext(context.Background(), "failed to close command lost counter", "error", err)
}
// Unlock before waiting for the goroutines to finish to avoid
// startSession/endSession blocking for potentially a long time.
e.mtx.Unlock()
e.wg.Wait()
logger.DebugContext(context.Background(), "Closed command BPF module")
}
// events contains raw events off the perf buffer.
func (e *exec) events() <-chan []byte {
return e.bpfEvents
}