Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tetragon: improve how we handle TIDs and GetProcessCopy() #1256

Merged
merged 3 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions bpf/process/bpf_execve_event.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,11 @@ event_execve(struct sched_execve_args *ctx)

p = &event->process;
p->flags = EVENT_EXECVE;
/* Send the TGID and TID, as during an execve all threads other
* than the calling thread are destroyed, but since we hook late
* during the execve then the calling thread at the hook time is
* already the new thread group leader.
/**
* Per thread tracking rules TID == PID :
* At exec all threads other than the calling one are destroyed, so
* current becomes the new thread leader since we hook late during
* execve.
*/
p->pid = pid >> 32;
p->tid = (__u32)pid;
Expand Down
7 changes: 6 additions & 1 deletion bpf/process/bpf_exit.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ static inline __attribute__((always_inline)) void event_exit_send(void *ctx, __u
exit->current.pad[3] = 0;
exit->current.ktime = enter->key.ktime;

/* We track and report only thread leader so here tgid == tid */
/**
* Per thread tracking rules TID == PID :
* We want the exit event to match the exec one, and since during exec
* we report the thread group leader, do same here as we read the exec
* entry from the execve_map anyway and explicitly set it to the to tgid.
*/
exit->info.tid = tgid;
probe_read(&exit->info.code, sizeof(exit->info.code),
_(&task->exit_code));
Expand Down
8 changes: 5 additions & 3 deletions bpf/process/bpf_fork.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ BPF_KPROBE(event_wake_up_new_task, struct task_struct *task)
.common.ktime = curr->key.ktime,
.parent = curr->pkey,
.tgid = curr->key.pid,
/* Since we generate one event per thread group, then when
* the task wakes up, there will be only one thread here:
* the thread group leader. Pass its thread id to user-space.
/**
* Per thread tracking rules TID == PID :
* Since we generate one event per thread group, then when this task
* wakes up it will be the only one in the thread group, and it is
* the leader. Ensure to pass TID to user space.
*/
.tid = BPF_CORE_READ(task, pid),
.ktime = curr->key.ktime,
Expand Down
5 changes: 4 additions & 1 deletion bpf/process/generic_calls.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ generic_process_init(struct msg_generic_kprobe *e, u8 op, struct event_config *c

e->action = 0;

/* Initialize with the calling TID */
/**
* Per thread tracking rules TID is the calling thread:
* At kprobes, tracpoints etc we report the calling thread ID to user space.
*/
e->tid = (__u32)get_current_pid_tgid();
}

Expand Down
20 changes: 14 additions & 6 deletions pkg/eventcache/eventcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,14 @@ func HandleGenericInternal(ev notify.Event, pid uint32, tid *uint32, timestamp u
}

if internal != nil {
// When we report the per thread fields, take a copy
// of the thread leader from the cache then update the corresponding
// per thread fields.
//
// The cost to get this is relatively high because it requires a
// deep copy of all the fields of the thread leader from the cache in
// order to safely modify them, to not corrupt gRPC streams.
proc := internal.GetProcessCopy()
// The TID of the cached process can be different from the
// TID that triggered the event, so always use the recorded
// one from bpf.
process.UpdateEventProcessTid(proc, tid)
ev.SetProcess(proc)
} else {
Expand All @@ -97,10 +101,14 @@ func HandleGenericEvent(internal *process.ProcessInternal, ev notify.Event, tid
return ErrFailedToGetPodInfo
}

// When we report the per thread fields, take a copy
// of the thread leader from the cache then update the corresponding
// per thread fields.
//
// The cost to get this is relatively high because it requires a
// deep copy of all the fields of the thread leader from the cache in
// order to safely modify them, to not corrupt gRPC streams.
proc := internal.GetProcessCopy()
// The TID of the cached process can be different from the
// TID that triggered the event, so always use the recorded
// one from bpf.
process.UpdateEventProcessTid(proc, tid)
ev.SetProcess(proc)
return nil
Expand Down
38 changes: 31 additions & 7 deletions pkg/grpc/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,34 @@ func GetProcessExit(event *MsgExitEventUnix) *tetragon.ProcessExit {
code := event.Info.Code >> 8
signal := readerexec.Signal(event.Info.Code & 0xFF)

// Per thread tracking rules PID == TID.
//
// Exit events should have TID==PID at same time we want to correlate
// the {TID,PID} of the exit event with the {TID,PID} pair from the exec
// event. They must match because its how we link the exit event to the
// exec one.
//
// The exit event is constructed when looking up the process by its PID
// from user space cache, so we endup with the TID that was pushed
// into the process cache during clone or exec.
//
// Add extra logic to WARN on conditions where TID!=PID to aid debugging
// and catch this unexpected case. Typically this indicates a bug either
// in BPF or userspace caching logic. When this condition is encountered
// we warn about it, but for the exit event the TID of the cache process
// will be re-used.
//
// Check must be against event.Info.Tid so we cover all the cases of
// the tetragonProcess.Pid against BPF.
if tetragonProcess.Pid.GetValue() != event.Info.Tid {
logger.GetLogger().WithFields(logrus.Fields{
tixxdz marked this conversation as resolved.
Show resolved Hide resolved
"event.name": "Exit",
"event.process.pid": event.ProcessKey.Pid,
"event.process.tid": event.Info.Tid,
"event.process.binary": tetragonProcess.Binary,
}).Warn("ExitEvent: process PID and TID mismatch")
}
tixxdz marked this conversation as resolved.
Show resolved Hide resolved

tetragonEvent := &tetragon.ProcessExit{
Process: tetragonProcess,
Parent: tetragonParent,
Expand All @@ -306,10 +334,7 @@ func GetProcessExit(event *MsgExitEventUnix) *tetragon.ProcessExit {
parent.RefDec()
}
if proc != nil {
tetragonEvent.Process = proc.GetProcessCopy()
proc.RefDec()
tixxdz marked this conversation as resolved.
Show resolved Hide resolved
// Use the bpf recorded TID to update the event
process.UpdateEventProcessTid(tetragonEvent.Process, &event.Info.Tid)
}
return tetragonEvent
}
Expand Down Expand Up @@ -339,13 +364,12 @@ func (msg *MsgExitEventUnix) RetryInternal(ev notify.Event, timestamp uint64) (*
}

if internal != nil {
// Use cached version of the process
ev.SetProcess(internal.UnsafeGetProcess())
tixxdz marked this conversation as resolved.
Show resolved Hide resolved
if !msg.RefCntDone[ProcessRefCnt] {
internal.RefDec()
msg.RefCntDone[ProcessRefCnt] = true
}
proc := internal.GetProcessCopy()
// Update the Process TID with the recorded one from BPF side
process.UpdateEventProcessTid(proc, &msg.Info.Tid)
} else {
errormetrics.ErrorTotalInc(errormetrics.EventCacheProcessInfoFailed)
err = eventcache.ErrFailedToGetProcessInfo
Expand All @@ -358,7 +382,7 @@ func (msg *MsgExitEventUnix) RetryInternal(ev notify.Event, timestamp uint64) (*
}

func (msg *MsgExitEventUnix) Retry(internal *process.ProcessInternal, ev notify.Event) error {
return eventcache.HandleGenericEvent(internal, ev, &msg.Info.Tid)
return eventcache.HandleGenericEvent(internal, ev, nil)
}

func (msg *MsgExitEventUnix) HandleMessage() *tetragon.GetEventsResponse {
Expand Down
26 changes: 21 additions & 5 deletions pkg/grpc/tracing/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,14 @@ func GetProcessKprobe(event *MsgGenericKprobeUnix) *tetragon.ProcessKprobe {
}

if proc != nil {
// At kprobes we report the per thread fields, so take a copy
// of the thread leader from the cache then update the corresponding
// per thread fields.
//
// The cost to get this is relatively high because it requires a
// deep copy of all the fields of the thread leader from the cache in
// order to safely modify them, to not corrupt gRPC streams.
tetragonEvent.Process = proc.GetProcessCopy()
// Use the bpf recorded TID to update the event
process.UpdateEventProcessTid(tetragonEvent.Process, &event.Tid)
}
if parent != nil {
Expand Down Expand Up @@ -373,10 +379,14 @@ func (msg *MsgGenericTracepointUnix) HandleMessage() *tetragon.GetEventsResponse
}

if proc != nil {
tetragonEvent.Process = proc.GetProcessCopy()
// Use the bpf recorded TID to update the event
// At tracepoints we report the per thread fields, so take a copy
// of the thread leader from the cache then update the corresponding
// per thread fields.
//
// The cost to get this is relatively high because it requires a
// deep copyo of the process in order to safely modify it.
// deep copy of all the fields of the thread leader from the cache in
// order to safely modify them, to not corrupt gRPC streams.
tetragonEvent.Process = proc.GetProcessCopy()
process.UpdateEventProcessTid(tetragonEvent.Process, &msg.Tid)
}

Expand Down Expand Up @@ -593,8 +603,14 @@ func GetProcessUprobe(event *MsgGenericUprobeUnix) *tetragon.ProcessUprobe {
}

if proc != nil {
// At uprobes we report the per thread fields, so take a copy
// of the thread leader from the cache then update the corresponding
// per thread fields.
//
// The cost to get this is relatively high because it requires a
// deep copy of all the fields of the thread leader from the cache in
// order to safely modify them, to not corrupt gRPC streams.
tetragonEvent.Process = proc.GetProcessCopy()
// Use the bpf recorded TID to update the event
process.UpdateEventProcessTid(tetragonEvent.Process, &event.Tid)
}
return tetragonEvent
Expand Down
47 changes: 27 additions & 20 deletions pkg/process/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,32 @@ func initProcessInternalExec(
protoPod := GetPodInfo(containerID, process.Filename, args, process.NSPID)
caps := caps.GetMsgCapabilities(capabilities)
ns := namespace.GetMsgNamespaces(namespaces)
binary := path.GetBinaryAbsolutePath(process.Filename, cwd)

// Per thread tracking rules PID == TID
//
// Ensure that exported events have the TID set. For events generated by
// kernel threads PID will be 0, so instead of checking against 0,
// assert that TGID == TID
if process.PID != process.TID {
logger.GetLogger().WithFields(logrus.Fields{
"event.name": "Execve",
"event.process.pid": process.PID,
"event.process.tid": process.TID,
"event.process.binary": binary,
"event.process.exec_id": execID,
"event.parent.exec_id": parentExecID,
}).Warn("ExecveEvent: process PID and TID mismatch")
Copy link
Contributor

@jrfastab jrfastab Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These warnings need to be metric error counters so we can actually find them. No one will notice a rare and random warning in the logs. (Note I wouldn't block this PR on the metric implementation but I do think we should get in soon to catch this.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So all fixed except for this adding metrics, will do in separate PR, so I don't mess up this one ;-)

// Explicitly reset TID to be PID
process.TID = process.PID
}
return &ProcessInternal{
process: &tetragon.Process{
Pid: &wrapperspb.UInt32Value{Value: process.PID},
Tid: &wrapperspb.UInt32Value{Value: process.TID},
Uid: &wrapperspb.UInt32Value{Value: process.UID},
Cwd: cwd,
Binary: path.GetBinaryAbsolutePath(process.Filename, cwd),
Binary: binary,
Arguments: args,
Flags: strings.Join(exec.DecodeCommonFlags(process.Flags), " "),
StartTime: ktime.ToProtoOpt(process.Ktime, (process.Flags&api.EventProcFS) == 0),
Expand Down Expand Up @@ -243,8 +262,9 @@ func initProcessInternalClone(event *tetragonAPI.MsgCloneEvent,
pi.process.ParentExecId = parentExecId
pi.process.ExecId = GetProcessID(event.PID, event.Ktime)
pi.process.Pid = &wrapperspb.UInt32Value{Value: event.PID}
// Since from BPF side we only generate one clone event per
// thread group that is for the leader, assert on that.
// Per thread tracking rules PID == TID: ensure that we get TID equals PID.
// Since from BPF side we only generate one clone event per
// thread group that is for the leader, assert on that.
if event.PID != event.TID {
logger.GetLogger().WithFields(logrus.Fields{
"event.name": "Clone",
Expand All @@ -254,11 +274,11 @@ func initProcessInternalClone(event *tetragonAPI.MsgCloneEvent,
"event.parent.exec_id": parentExecId,
}).Debug("CloneEvent: process PID and TID mismatch")
}
// Set the TID here and if we have an exit without an exec we report
// directly this TID without copying again objects.
// At kprobe times we use the returned TIDs from bpf side.
pi.process.Tid = &wrapperspb.UInt32Value{Value: event.PID}

// This TID will be updated by the TID of the bpf execve event later,
// so set it to zero here and ensure that it will be updated later.
// Exported events must always be generated with a non zero TID.
pi.process.Tid = &wrapperspb.UInt32Value{Value: 0}
pi.process.Flags = strings.Join(exec.DecodeCommonFlags(event.Flags), " ")
pi.process.StartTime = ktime.ToProto(event.Ktime)
pi.process.Refcnt = 1
Expand Down Expand Up @@ -310,19 +330,6 @@ func AddExecEvent(event *tetragonAPI.MsgExecveEventUnix) *ProcessInternal {
proc = initProcessInternalExec(event.Process, event.Kube.Docker, event.CleanupProcess, event.Capabilities, event.Namespaces)
}

// Ensure that exported events have the TID set. For events from Kernel
// we usually use PID == 0, so instead of checking against 0, assert that
// TGID == TID
if proc.process.Pid.GetValue() != proc.process.Tid.GetValue() {
logger.GetLogger().WithFields(logrus.Fields{
"event.name": "Execve",
"event.process.pid": proc.process.Pid.GetValue(),
"event.process.tid": proc.process.Tid.GetValue(),
"event.process.exec_id": proc.process.ExecId,
"event.process.binary": proc.process.Binary,
}).Warn("ExecveEvent: process PID and TID mismatch")
}

procCache.add(proc)
return proc
}
Expand Down
Loading