Skip to content

Commit 2935630

Browse files
fix(ebpf): prevent file descriptor leak in resource retrieval
Signed-off-by: Shivansh Sahu <sahushivansh142@gmail.com>
1 parent 7c77714 commit 2935630

File tree

2 files changed

+80
-12
lines changed

2 files changed

+80
-12
lines changed

pkg/utils/ebpf.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,23 @@ func GetProgramByName(name string) (*ebpf.Program, error) {
3434

3535
for {
3636
if progID, err = ebpf.ProgramGetNextID(progID); err != nil {
37-
err = fmt.Errorf("failed to get system next program id, err is %v\n", err)
38-
return nil, err
37+
return nil, fmt.Errorf("failed to get system next program id: %w", err)
3938
}
4039

4140
if targetProg, err = ebpf.NewProgramFromID(progID); err != nil {
42-
err = fmt.Errorf("failed to get new program from id:%v, err is %v\n", progID, err)
43-
return nil, err
41+
return nil, fmt.Errorf("failed to get new program from id:%v: %w", progID, err)
4442
}
4543

4644
if targetProgInfo, err = targetProg.Info(); err != nil {
47-
err = fmt.Errorf("failed to get new program info from fd:%v, err is %v\n", targetProg, err)
48-
return nil, err
45+
targetProg.Close()
46+
return nil, fmt.Errorf("failed to get new program info from fd:%v: %w", targetProg, err)
4947
}
5048

5149
if strings.Compare(targetProgInfo.Name, name) == 0 {
5250
return targetProg, nil
5351
}
52+
53+
targetProg.Close()
5454
}
5555
}
5656

@@ -66,22 +66,22 @@ func GetMapByName(name string) (*ebpf.Map, error) {
6666

6767
for {
6868
if mapID, err = ebpf.MapGetNextID(mapID); err != nil {
69-
err = fmt.Errorf("failed to get system next map id, err is %v\n", err)
70-
return nil, err
69+
return nil, fmt.Errorf("failed to get system next map id: %w", err)
7170
}
7271

7372
if targetMap, err = ebpf.NewMapFromID(mapID); err != nil {
74-
err = fmt.Errorf("failed to get new map from id:%v, err is %v\n", mapID, err)
75-
return nil, err
73+
return nil, fmt.Errorf("failed to get new map from id:%v: %w", mapID, err)
7674
}
7775

7876
if targetMapInfo, err = targetMap.Info(); err != nil {
79-
err = fmt.Errorf("failed to get new map info from fd:%v, err is %v\n", targetMap, err)
80-
return nil, err
77+
targetMap.Close()
78+
return nil, fmt.Errorf("failed to get new map info from fd:%v: %w", targetMap, err)
8179
}
8280

8381
if strings.Compare(targetMapInfo.Name, name) == 0 {
8482
return targetMap, nil
8583
}
84+
85+
targetMap.Close()
8686
}
8787
}

pkg/utils/ebpf_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//go:build linux
2+
// +build linux
3+
4+
package utils
5+
6+
import (
7+
"fmt"
8+
"os"
9+
"testing"
10+
)
11+
12+
// countOpenFDs returns the number of open file descriptors for the current process.
13+
// It returns an error if /proc is not available (e.g., non-Linux environments).
14+
func countOpenFDs(t *testing.T) int {
15+
t.Helper()
16+
pid := os.Getpid()
17+
fds, err := os.ReadDir(fmt.Sprintf("/proc/%d/fd", pid))
18+
if err != nil {
19+
t.Fatalf("failed to read /proc/%d/fd: %v", pid, err)
20+
}
21+
return len(fds)
22+
}
23+
24+
func TestGetProgramByName_FDLeak(t *testing.T) {
25+
initialFDs := countOpenFDs(t)
26+
t.Logf("Initial open File Descriptors: %d", initialFDs)
27+
28+
// Call the function in a loop.
29+
// Searching for a non-existent program forces it to iterate through ALL
30+
// loaded BPF programs, which would previously leak an FD for every single one.
31+
for i := 0; i < 100; i++ {
32+
_, err := GetProgramByName("non_existent_fake_prog_12345")
33+
if err == nil {
34+
t.Fatal("expected error for non-existent program, got nil")
35+
}
36+
}
37+
38+
finalFDs := countOpenFDs(t)
39+
t.Logf("Final open File Descriptors: %d", finalFDs)
40+
41+
if finalFDs > initialFDs+10 {
42+
t.Fatalf("FD LEAK DETECTED! Started with %d FDs, ended with %d FDs.", initialFDs, finalFDs)
43+
}
44+
t.Log("No leak detected.")
45+
}
46+
47+
func TestGetMapByName_FDLeak(t *testing.T) {
48+
initialFDs := countOpenFDs(t)
49+
t.Logf("Initial open File Descriptors: %d", initialFDs)
50+
51+
// Call the function in a loop.
52+
// Searching for a non-existent map forces it to iterate through ALL
53+
// loaded BPF maps, which would previously leak an FD for every single one.
54+
for i := 0; i < 100; i++ {
55+
_, err := GetMapByName("non_existent_fake_map_12345")
56+
if err == nil {
57+
t.Fatal("expected error for non-existent map, got nil")
58+
}
59+
}
60+
61+
finalFDs := countOpenFDs(t)
62+
t.Logf("Final open File Descriptors: %d", finalFDs)
63+
64+
if finalFDs > initialFDs+10 {
65+
t.Fatalf("FD LEAK DETECTED! Started with %d FDs, ended with %d FDs.", initialFDs, finalFDs)
66+
}
67+
t.Log("No leak detected.")
68+
}

0 commit comments

Comments
 (0)