Skip to content
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
3 changes: 1 addition & 2 deletions pkg/ebpf/capture.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ebpf

import (
"bytes"
"context"
"fmt"
"io"
Expand Down Expand Up @@ -136,7 +135,7 @@ func (t *Tracee) handleFileCaptures(ctx context.Context) {
t.handleError(err)
continue
}
bpfName := string(bytes.TrimRight(bpfObjectMeta.Name[:], "\x00"))
bpfName := utils.TrimTrailingNUL(bpfObjectMeta.Name[:])
filename = fmt.Sprintf("bpf.name-%s", bpfName)
if bpfObjectMeta.Pid != 0 {
filename = fmt.Sprintf("%s.pid-%d", filename, bpfObjectMeta.Pid)
Expand Down
18 changes: 12 additions & 6 deletions pkg/ebpf/events_pipeline.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ebpf

import (
"bytes"
"context"
"encoding/binary"
"fmt"
Expand Down Expand Up @@ -211,6 +210,9 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch
PodUID: containerInfo.Pod.UID,
}

commStr := string(utils.TrimTrailingNUL(eCtx.Comm[:])) // clean potential trailing null
utsNameStr := string(utils.TrimTrailingNUL(eCtx.UtsName[:])) // clean potential trailing null

flags := parseContextFlags(containerData.ID, eCtx.Flags)
syscall := ""
if eCtx.Syscall != noSyscall {
Expand All @@ -221,8 +223,6 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch
id := events.ID(eCtx.Syscall)
syscallDef := events.Core.GetDefinitionByID(id)
if syscallDef.NotValid() {
commStr := string(eCtx.Comm[:bytes.IndexByte(eCtx.Comm[:], 0)])
utsNameStr := string(eCtx.UtsName[:bytes.IndexByte(eCtx.UtsName[:], 0)])
logger.Debugw(
fmt.Sprintf("Event %s with an invalid syscall id %d", evtName, id),
"Comm", commStr,
Expand Down Expand Up @@ -254,8 +254,8 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch
evt.UserID = int(eCtx.Uid)
evt.MountNS = int(eCtx.MntID)
evt.PIDNS = int(eCtx.PidID)
evt.ProcessName = string(bytes.TrimRight(eCtx.Comm[:], "\x00")) // set and clean potential trailing null
evt.HostName = string(bytes.TrimRight(eCtx.UtsName[:], "\x00")) // set and clean potential trailing null
evt.ProcessName = commStr
evt.HostName = utsNameStr
evt.CgroupID = uint(eCtx.CgroupID)
evt.ContainerID = containerData.ID
evt.Container = containerData
Expand All @@ -275,7 +275,13 @@ func (t *Tracee) decodeEvents(ctx context.Context, sourceChan chan []byte) (<-ch
evt.Metadata = nil
// compute hashes using normalized times
evt.ThreadEntityId = utils.HashTaskID(eCtx.HostTid, uint64(evt.ThreadStartTime))
evt.ProcessEntityId = utils.HashTaskID(eCtx.HostPid, traceetime.BootToEpochNS(eCtx.LeaderStartTime))
if eCtx.HostTid == eCtx.HostPid && eCtx.StartTime == eCtx.LeaderStartTime {
// If the thread is the leader (i.e., HostTid == HostPid and StartTime == LeaderStartTime),
// then ProcessEntityId and ThreadEntityId are identical and can be shared.
evt.ProcessEntityId = evt.ThreadEntityId
} else {
evt.ProcessEntityId = utils.HashTaskID(eCtx.HostPid, traceetime.BootToEpochNS(eCtx.LeaderStartTime))
}
evt.ParentEntityId = utils.HashTaskID(eCtx.HostPpid, traceetime.BootToEpochNS(eCtx.ParentStartTime))

// If there aren't any policies that need filtering in userland, tracee **may** skip
Expand Down
9 changes: 4 additions & 5 deletions pkg/ebpf/events_pipeline_bench_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ebpf

import (
"bytes"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -87,8 +86,8 @@ func BenchmarkGetEventFromPool(b *testing.B) {
evt.UserID = int(ctx.Uid)
evt.MountNS = int(ctx.MntID)
evt.PIDNS = int(ctx.PidID)
evt.ProcessName = string(bytes.TrimRight(ctx.Comm[:], "\x00"))
evt.HostName = string(bytes.TrimRight(ctx.UtsName[:], "\x00"))
evt.ProcessName = string(utils.TrimTrailingNUL(ctx.Comm[:]))
evt.HostName = string(utils.TrimTrailingNUL(ctx.UtsName[:]))
evt.CgroupID = uint(ctx.CgroupID)
evt.ContainerID = containerData.ID
evt.Container = containerData
Expand Down Expand Up @@ -247,8 +246,8 @@ func BenchmarkNewEventObject(b *testing.B) {
UserID: int(ctx.Uid),
MountNS: int(ctx.MntID),
PIDNS: int(ctx.PidID),
ProcessName: string(bytes.TrimRight(ctx.Comm[:], "\x00")),
HostName: string(bytes.TrimRight(ctx.UtsName[:], "\x00")),
ProcessName: string(utils.TrimTrailingNUL(ctx.Comm[:])),
HostName: string(utils.TrimTrailingNUL(ctx.UtsName[:])),
CgroupID: uint(ctx.CgroupID),
ContainerID: containerData.ID,
Container: containerData,
Expand Down
3 changes: 1 addition & 2 deletions pkg/ebpf/processor_funcs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ebpf

import (
"bytes"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -334,7 +333,7 @@ func (t *Tracee) processPrintMemDump(event *trace.Event) error {
if err := unix.Uname(&utsName); err != nil {
return errfmt.WrapError(err)
}
arch = string(bytes.TrimRight(utsName.Machine[:], "\x00"))
arch = string(utils.TrimTrailingNUL(utsName.Machine[:]))
err = events.SetArgValue(event, "arch", arch)
if err != nil {
return err
Expand Down
20 changes: 20 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,23 @@ func ReverseString(s string) string {
}
return string(bytes)
}

// TrimTrailingNUL returns a subslice of the input with all trailing NUL bytes (0x00) removed.
// It performs a reverse scan and returns b[:end], avoiding any allocations.
//
// This function is optimized for fixed-size, ASCII-compatible C-style buffers where padding
// with trailing NULs may occur.
//
// Note:
// - The returned slice shares memory with the original input.
// - If you need an independent string or slice, copy it manually.
// - This function is not safe for UTF-8 or multibyte character data; it assumes ASCII content only.
func TrimTrailingNUL(b []byte) []byte {
end := len(b)

for end > 0 && b[end-1] == 0 {
end--
}

return b[:end]
}
58 changes: 58 additions & 0 deletions pkg/utils/utils_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package utils

import (
"bytes"
"testing"
)

var result []byte

func BenchmarkBytesTrimRight_WithNUL(b *testing.B) {
var arr [64]byte
for i := 0; i < 61; i++ {
arr[i] = 'A'
}
// last 3 bytes are \x00 by default

for b.Loop() {
result = bytes.TrimRight(arr[:], "\x00")
}
_ = result
}

func BenchmarkBytesTrimRight_NoNUL(b *testing.B) {
var arr [64]byte
for i := 0; i < 64; i++ {
arr[i] = 'A'
}

for b.Loop() {
result = bytes.TrimRight(arr[:], "\x00")
}
_ = result
}

func BenchmarkTrimTrailingNUL_WithNUL(b *testing.B) {
var arr [64]byte
for i := 0; i < 61; i++ {
arr[i] = 'A'
}
// last 3 bytes are \x00 by default

for b.Loop() {
result = TrimTrailingNUL(arr[:])
}
_ = result
}

func BenchmarkTrimTrailingNUL_NoNUL(b *testing.B) {
var arr [64]byte
for i := 0; i < 64; i++ {
arr[i] = 'A'
}

for b.Loop() {
result = TrimTrailingNUL(arr[:])
}
_ = result
}
59 changes: 59 additions & 0 deletions pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package utils

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestTrimTrailingNUL(t *testing.T) {
t.Parallel()

tests := []struct {
name string
input []byte
expected []byte
}{
{
name: "no NUL",
input: []byte("hello"),
expected: []byte("hello"),
},
{
name: "single trailing NUL",
input: []byte("hello\x00"),
expected: []byte("hello"),
},
{
name: "multiple trailing NULs",
input: []byte("hello\x00\x00\x00"),
expected: []byte("hello"),
},
{
name: "intermediate NUL (preserved)",
input: []byte("he\x00llo\x00"),
expected: []byte("he\x00llo"),
},
{
name: "all NULs",
input: []byte("\x00\x00\x00"),
expected: []byte(""),
},
{
name: "empty slice",
input: []byte(""),
expected: []byte(""),
},
{
name: "no trailing NUL but contains middle NUL",
input: []byte("\x00he\x00llo"),
expected: []byte("\x00he\x00llo"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.expected, TrimTrailingNUL(tt.input))
})
}
}