@@ -36,6 +36,12 @@ const (
36
36
AvailableOffset
37
37
)
38
38
39
+ /*
40
+ Steps:
41
+ 1. Compile pcap expresion to cbpf using libpcap
42
+ 2. Convert cbpf to ebpf using cloudflare/cbpfc
43
+ 3. [!DirectRead] Convert direct memory load to bpf_probe_read_kernel call
44
+ */
39
45
func CompileEbpf (expr string , opts Options ) (insts asm.Instructions , err error ) {
40
46
if expr == "__reject_all__" {
41
47
return asm.Instructions {
@@ -48,10 +54,12 @@ func CompileEbpf(expr string, opts Options) (insts asm.Instructions, err error)
48
54
}
49
55
50
56
ebpfInsts , err := cbpfc .ToEBPF (cbpfInsts , cbpfc.EBPFOpts {
57
+ // skb->data is at r4, skb->data_end is at r5.
51
58
PacketStart : asm .R4 ,
52
59
PacketEnd : asm .R5 ,
53
60
Result : opts .result (),
54
61
ResultLabel : opts .resultLabel (),
62
+ // _skb is at R0, __skb is at R1, ___skb is at R2.
55
63
Working : [4 ]asm.Register {asm .R0 , asm .R1 , asm .R2 , asm .R3 },
56
64
LabelPrefix : opts .labelPrefix (),
57
65
StackOffset : - int (AvailableOffset ),
@@ -98,6 +106,24 @@ func CompileCbpf(expr string, l2 bool) (insts []bpf.Instruction, err error) {
98
106
return
99
107
}
100
108
109
+ /*
110
+ If !DirectRead, We have to adjust the ebpf instructions because verifier prevents us from
111
+ directly loading data from memory. For example, the instruction "r0 = *(u8 *)(r4 +0)"
112
+ will break verifier with error "R4 invalid mem access 'scalar", we therefore
113
+ need to convert this direct memory load to bpf_probe_read_kernel function call:
114
+
115
+ - r1 = r10 // r10 is stack top
116
+ - r1 += -8 // r1 = r10-8
117
+ - r2 = 1 // r2 = sizeof(u8)
118
+ - r3 = r4 // r4 is start of packet data, aka L3 header
119
+ - r3 += 0 // r3 = r4+0
120
+ - call bpf_probe_read_kernel // *(r10-8) = *(u8 *)(r4+0)
121
+ - r0 = *(u8 *)(r10 -8) // r0 = *(r10-8)
122
+
123
+ To safely borrow R1, R2 and R3 for setting up the arguments for
124
+ bpf_probe_read_kernel(), we need to save the original values of R1, R2 and R3
125
+ on stack, and restore them after the function call.
126
+ */
101
127
func adjustEbpf (insts asm.Instructions , opts Options ) (newInsts asm.Instructions , err error ) {
102
128
if ! opts .DirectRead {
103
129
replaceIdx := []int {}
@@ -107,23 +133,28 @@ func adjustEbpf(insts asm.Instructions, opts Options) (newInsts asm.Instructions
107
133
replaceIdx = append (replaceIdx , idx )
108
134
replaceInsts [idx ] = append (replaceInsts [idx ],
109
135
136
+ // Store R1, R2, R3 on stack.
110
137
asm .StoreMem (asm .RFP , int16 (R1Offset ), asm .R1 , asm .DWord ),
111
138
asm .StoreMem (asm .RFP , int16 (R2Offset ), asm .R2 , asm .DWord ),
112
139
asm .StoreMem (asm .RFP , int16 (R3Offset ), asm .R3 , asm .DWord ),
113
140
141
+ // bpf_probe_read_kernel(RFP-8, size, inst.Src)
114
142
asm .Mov .Reg (asm .R1 , asm .RFP ),
115
143
asm .Add .Imm (asm .R1 , int32 (BpfReadKernelOffset )),
116
144
asm .Mov .Imm (asm .R2 , int32 (inst .OpCode .Size ().Sizeof ())),
117
145
asm .Mov .Reg (asm .R3 , inst .Src ),
118
146
asm .Add .Imm (asm .R3 , int32 (inst .Offset )),
119
147
asm .FnProbeReadKernel .Call (),
120
148
149
+ // inst.Dst = *(RFP-8)
121
150
asm .LoadMem (inst .Dst , asm .RFP , int16 (BpfReadKernelOffset ), inst .OpCode .Size ()),
122
151
152
+ // Restore R4, R5 from stack. This is needed because bpf_probe_read_kernel always resets R4 and R5 even if they are not used by bpf_probe_read_kernel.
123
153
asm .LoadMem (asm .R4 , asm .RFP , int16 (R4Offset ), asm .DWord ),
124
154
asm .LoadMem (asm .R5 , asm .RFP , int16 (R5Offset ), asm .DWord ),
125
155
)
126
156
157
+ // Restore R1, R2, R3 from stack
127
158
restoreInsts := asm.Instructions {
128
159
asm .LoadMem (asm .R1 , asm .RFP , int16 (R1Offset ), asm .DWord ),
129
160
asm .LoadMem (asm .R2 , asm .RFP , int16 (R2Offset ), asm .DWord ),
@@ -136,26 +167,33 @@ func adjustEbpf(insts asm.Instructions, opts Options) (newInsts asm.Instructions
136
167
}
137
168
138
169
replaceInsts [idx ] = append (replaceInsts [idx ], restoreInsts ... )
170
+
171
+ // Metadata is crucial for adjusting jump offsets. We
172
+ // ditched original instructions, which could hold symbol
173
+ // names targeted by other jump instructions, so here we
174
+ // inherit the metadata from the ditched ones.
139
175
replaceInsts [idx ][0 ].Metadata = inst .Metadata
140
176
}
141
177
}
142
178
179
+ // Replace the memory load instructions with the new ones
143
180
for i := len (replaceIdx ) - 1 ; i >= 0 ; i -- {
144
181
idx := replaceIdx [i ]
145
182
insts = append (insts [:idx ], append (replaceInsts [idx ], insts [idx + 1 :]... )... )
146
183
}
147
184
185
+ // Store R4, R5 on stack.
148
186
insts = append ([]asm.Instruction {
149
187
asm .StoreMem (asm .RFP , int16 (R4Offset ), asm .R4 , asm .DWord ),
150
188
asm .StoreMem (asm .RFP , int16 (R5Offset ), asm .R5 , asm .DWord ),
151
189
}, insts ... )
152
190
}
153
191
154
192
return append (insts ,
155
- asm .Mov .Imm (asm .R1 , 0 ).WithSymbol (opts .resultLabel ()),
156
- asm .Mov .Imm (asm .R2 , 0 ),
157
- asm .Mov .Imm (asm .R3 , 0 ),
158
- asm .Mov .Reg (asm .R4 , opts .result ()),
159
- asm .Mov .Imm (asm .R5 , 0 ),
193
+ asm .Mov .Imm (asm .R1 , 0 ).WithSymbol (opts .resultLabel ()), // r1 = 0 (_skb)
194
+ asm .Mov .Imm (asm .R2 , 0 ), // r2 = 0 (__skb)
195
+ asm .Mov .Imm (asm .R3 , 0 ), // r3 = 0 (___skb)
196
+ asm .Mov .Reg (asm .R4 , opts .result ()), // r4 = $result (data)
197
+ asm .Mov .Imm (asm .R5 , 0 ), // r5 = 0 (data_end)
160
198
), nil
161
199
}
0 commit comments