diff --git a/interpreter/go/go.go b/interpreter/go/go.go index 0cde865dc..ff5d54fbf 100644 --- a/interpreter/go/go.go +++ b/interpreter/go/go.go @@ -7,6 +7,7 @@ import ( "fmt" "sync/atomic" + "go.opentelemetry.io/ebpf-profiler/host" "go.opentelemetry.io/ebpf-profiler/interpreter" "go.opentelemetry.io/ebpf-profiler/libpf" "go.opentelemetry.io/ebpf-profiler/metrics" @@ -24,6 +25,7 @@ var ( type goData struct { refs atomic.Int32 + fileID host.FileID version string pclntab *elfunwindinfo.Gopclntab @@ -56,6 +58,7 @@ func Loader(_ interpreter.EbpfHandler, info *interpreter.LoaderInfo) ( } g := &goData{ + fileID: info.FileID(), version: goVersion, pclntab: pclntab, } @@ -105,6 +108,10 @@ func (g *goInstance) Symbolize(ef libpf.EbpfFrame, frames *libpf.Frames, mapping if !ef.Type().IsInterpType(libpf.Native) { return interpreter.ErrMismatchInterpreterType } + // Skip native frames that do not belong to this Go binary. + if host.FileID(ef.Variable(0)) != g.d.fileID { + return interpreter.ErrMismatchInterpreterType + } sfCounter := successfailurecounter.New(&g.successCount, &g.failCount) defer sfCounter.DefaultToFailure() diff --git a/processmanager/manager_test.go b/processmanager/manager_test.go new file mode 100644 index 000000000..0be3c7c19 --- /dev/null +++ b/processmanager/manager_test.go @@ -0,0 +1,157 @@ +package processmanager + +import ( + "os" + "runtime" + "slices" + "testing" + + lru "github.com/elastic/go-freelru" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/ebpf-profiler/host" + "go.opentelemetry.io/ebpf-profiler/interpreter" + golang "go.opentelemetry.io/ebpf-profiler/interpreter/go" + "go.opentelemetry.io/ebpf-profiler/libpf" + "go.opentelemetry.io/ebpf-profiler/libpf/pfelf" + "go.opentelemetry.io/ebpf-profiler/process" + "go.opentelemetry.io/ebpf-profiler/remotememory" + "go.opentelemetry.io/ebpf-profiler/reporter/samples" + "go.opentelemetry.io/ebpf-profiler/util" +) + +type traceCapture struct { + traces []*libpf.Trace +} + +func (tc *traceCapture) ReportTraceEvent(trace *libpf.Trace, _ *samples.TraceEventMeta) error { + tc.traces = append(tc.traces, trace) + return nil +} + +func TestFrameCacheCrossProcessPollution(t *testing.T) { + require := require.New(t) + + exec, err := os.Executable() + require.NoError(err) + + pc, _, _, ok := runtime.Caller(0) + require.True(ok) + + goPID := libpf.PID(1000) + catPID := libpf.PID(2000) + + goHostFileID, err := host.FileIDFromBytes( + []byte{0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55}) + require.NoError(err) + catHostFileID, err := host.FileIDFromBytes( + []byte{0xCA, 0x7C, 0xA7, 0xCA, 0x7C, 0xA7, 0xCA, 0x7C}) + require.NoError(err) + libcHostFileID, err := host.FileIDFromBytes( + []byte{0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE}) + require.NoError(err) + + realPID := libpf.PID(os.Getpid()) + pid := process.New(realPID, realPID) + elfRef := pfelf.NewReference(exec, pid) + loaderInfo := interpreter.NewLoaderInfo(goHostFileID, elfRef) + rm := remotememory.NewProcessVirtualMemory(realPID) + + goData, err := golang.Loader(nil, loaderInfo) + require.NoError(err) + goInstance, err := goData.Attach(nil, realPID, 0x0, rm) + require.NoError(err) + + goODID := util.OnDiskFileIdentifier{DeviceID: 1, InodeNum: 1} + + frameCache, err := lru.New[frameCacheKey, libpf.Frames](1024, hashFrameCacheKey) + require.NoError(err) + frameCache.SetLifetime(frameCacheLifetime) + + goMappings := []Mapping{ + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(goHostFileID), 0), + FileName: libpf.Intern("go-binary"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(libcHostFileID), 0), + FileName: libpf.Intern("libc.so.6"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + } + slices.SortFunc(goMappings, compareMapping) + + catMappings := []Mapping{ + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(catHostFileID), 0), + FileName: libpf.Intern("cat"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + {FrameMapping: libpf.NewFrameMapping(libpf.FrameMappingData{ + File: libpf.NewFrameMappingFile(libpf.FrameMappingFileData{ + FileID: libpf.NewFileID(uint64(libcHostFileID), 0), + FileName: libpf.Intern("libc.so.6"), + }), + Start: 0, + End: 0xFFFFFFF, + })}, + } + slices.SortFunc(catMappings, compareMapping) + + capture := &traceCapture{} + pm := &ProcessManager{ + interpreters: map[libpf.PID]map[util.OnDiskFileIdentifier]interpreter.Instance{ + goPID: {goODID: goInstance}, + }, + pidToProcessInfo: map[libpf.PID]*processInfo{ + goPID: {mappings: goMappings}, + catPID: {mappings: catMappings}, + }, + frameCache: frameCache, + traceReporter: capture, + } + + libcFrame := libpf.NewEbpfFrame(libpf.NativeFrame, 0, 2, uint64(pc)) + libcFrame[1] = uint64(libcHostFileID) + + pm.HandleTrace(&libpf.EbpfTrace{ + PID: goPID, + TID: goPID, + NumFrames: 1, + FrameData: libcFrame, + }) + + require.Len(capture.traces, 1) + goTrace := capture.traces[0] + require.NotEmpty(goTrace.Frames) + + goFrame := goTrace.Frames[0].Value() + assert.Equal(t, libpf.NativeFrame, goFrame.Type) + assert.Equal(t, "", goFrame.FunctionName.String()) + + pm.HandleTrace(&libpf.EbpfTrace{ + PID: catPID, + TID: catPID, + NumFrames: 1, + FrameData: libcFrame, + }) + + require.Len(capture.traces, 2) + catTrace := capture.traces[1] + require.NotEmpty(catTrace.Frames) + + catFrame := catTrace.Frames[0].Value() + assert.Equal(t, libpf.NativeFrame, catFrame.Type) + assert.Equal(t, "", catFrame.FunctionName.String()) +}