github.com/sagernet/gvisor@v0.0.0-20240428053021-e691de28565f/pkg/bpf/program_builder.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package bpf 16 17 import ( 18 "fmt" 19 "math" 20 "sort" 21 "strings" 22 23 "github.com/sagernet/gvisor/pkg/abi/linux" 24 ) 25 26 const ( 27 labelTarget = math.MaxUint8 28 labelDirectTarget = math.MaxUint32 29 ) 30 31 // ProgramBuilder assists with building a BPF program with jump 32 // labels that are resolved to their proper offsets. 33 type ProgramBuilder struct { 34 // Maps label names to label objects. 35 labels map[string]*label 36 37 // Maps label sources to the label name it references. 38 jumpSourceToLabel map[source]string 39 40 // unusableLabels are labels that are added before being referenced in a 41 // jump. Any labels added this way cannot be referenced later in order to 42 // avoid backwards references. 43 unusableLabels map[string]bool 44 45 // Array of BPF instructions that makes up the program. 46 instructions []Instruction 47 } 48 49 // NewProgramBuilder creates a new ProgramBuilder instance. 50 func NewProgramBuilder() *ProgramBuilder { 51 return &ProgramBuilder{ 52 labels: map[string]*label{}, 53 jumpSourceToLabel: map[source]string{}, 54 unusableLabels: map[string]bool{}, 55 } 56 } 57 58 // label contains information to resolve a label to an offset. 59 type label struct { 60 // List of locations that reference the label in the program. 61 sources []source 62 63 // Program line when the label is located. 64 target int 65 } 66 67 // JumpType is the type of jump target that an instruction may use. 68 type JumpType int 69 70 // Types of jump that an instruction may use. 71 const ( 72 JumpDirect JumpType = iota 73 JumpTrue 74 JumpFalse 75 ) 76 77 // source contains information about a single reference to a label. 78 type source struct { 79 // Program line where the label reference is present. 80 line int 81 82 // Which type of jump is referencing this label. 83 jt JumpType 84 } 85 86 // AddStmt adds a new statement to the program. 87 func (b *ProgramBuilder) AddStmt(code uint16, k uint32) { 88 b.instructions = append(b.instructions, Stmt(code, k)) 89 } 90 91 // AddJump adds a new jump to the program. 92 func (b *ProgramBuilder) AddJump(code uint16, k uint32, jt, jf uint8) { 93 b.instructions = append(b.instructions, Jump(code, k, jt, jf)) 94 } 95 96 // AddDirectJumpLabel adds a new jump to the program where is labelled. 97 func (b *ProgramBuilder) AddDirectJumpLabel(labelName string) { 98 b.addLabelSource(labelName, JumpDirect) 99 b.AddJump(Jmp|Ja, labelDirectTarget, 0, 0) 100 } 101 102 // AddJumpTrueLabel adds a new jump to the program where 'jump if true' is a label. 103 func (b *ProgramBuilder) AddJumpTrueLabel(code uint16, k uint32, jtLabel string, jf uint8) { 104 b.addLabelSource(jtLabel, JumpTrue) 105 b.AddJump(code, k, labelTarget, jf) 106 } 107 108 // AddJumpFalseLabel adds a new jump to the program where 'jump if false' is a label. 109 func (b *ProgramBuilder) AddJumpFalseLabel(code uint16, k uint32, jt uint8, jfLabel string) { 110 b.addLabelSource(jfLabel, JumpFalse) 111 b.AddJump(code, k, jt, labelTarget) 112 } 113 114 // AddJumpLabels adds a new jump to the program where both jump targets are labels. 115 func (b *ProgramBuilder) AddJumpLabels(code uint16, k uint32, jtLabel, jfLabel string) { 116 b.addLabelSource(jtLabel, JumpTrue) 117 b.addLabelSource(jfLabel, JumpFalse) 118 b.AddJump(code, k, labelTarget, labelTarget) 119 } 120 121 // AddLabel sets the given label name at the current location. The next instruction is executed 122 // when the any code jumps to this label. More than one label can be added to the same location. 123 func (b *ProgramBuilder) AddLabel(name string) error { 124 l, ok := b.labels[name] 125 if !ok { 126 if _, ok = b.unusableLabels[name]; ok { 127 return fmt.Errorf("label %q already set", name) 128 } 129 // Mark the label as unusable. This is done to catch backwards jumps. 130 b.unusableLabels[name] = true 131 return nil 132 } 133 if l.target != -1 { 134 return fmt.Errorf("label %q target already set: %v", name, l.target) 135 } 136 l.target = len(b.instructions) 137 return nil 138 } 139 140 // Instructions returns an array of BPF instructions representing the program with all labels 141 // resolved. Return error in case label resolution failed due to an invalid program. 142 // 143 // N.B. Partial results will be returned in the error case, which is useful for debugging. 144 func (b *ProgramBuilder) Instructions() ([]Instruction, error) { 145 if err := b.resolveLabels(); err != nil { 146 return b.instructions, err 147 } 148 return b.instructions, nil 149 } 150 151 func (b *ProgramBuilder) addLabelSource(labelName string, t JumpType) { 152 l, ok := b.labels[labelName] 153 if !ok { 154 l = &label{sources: make([]source, 0), target: -1} 155 b.labels[labelName] = l 156 } 157 src := source{line: len(b.instructions), jt: t} 158 l.sources = append(l.sources, src) 159 if existingLabel, found := b.jumpSourceToLabel[src]; found { 160 panic(fmt.Sprintf("label %q already present at source %v; one source may only have one label", existingLabel, src)) 161 } 162 b.jumpSourceToLabel[src] = labelName 163 } 164 165 func (b *ProgramBuilder) resolveLabels() error { 166 for key, v := range b.labels { 167 if _, ok := b.unusableLabels[key]; ok { 168 return fmt.Errorf("backwards reference detected for label: %q", key) 169 } 170 171 if v.target == -1 { 172 return fmt.Errorf("label target not set: %v", key) 173 } 174 if v.target >= len(b.instructions) { 175 return fmt.Errorf("target is beyond end of ProgramBuilder") 176 } 177 for _, s := range v.sources { 178 // Finds jump instruction that references the label. 179 inst := b.instructions[s.line] 180 if s.line >= v.target { 181 return fmt.Errorf("cannot jump backwards") 182 } 183 // Calculates the jump offset from current line. 184 offset := v.target - s.line - 1 185 // Sets offset into jump instruction. 186 switch s.jt { 187 case JumpDirect: 188 if offset > labelDirectTarget { 189 return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line) 190 } 191 if inst.K != labelDirectTarget { 192 return fmt.Errorf("jump target is not a label") 193 } 194 inst.K = uint32(offset) 195 case JumpTrue: 196 if offset > labelTarget { 197 return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line) 198 } 199 if inst.JumpIfTrue != labelTarget { 200 return fmt.Errorf("jump target is not a label") 201 } 202 inst.JumpIfTrue = uint8(offset) 203 case JumpFalse: 204 if offset > labelTarget { 205 return fmt.Errorf("jump offset to label '%v' is too large: %v, inst: %v, lineno: %v", key, offset, inst, s.line) 206 } 207 if inst.JumpIfFalse != labelTarget { 208 return fmt.Errorf("jump target is not a label") 209 } 210 inst.JumpIfFalse = uint8(offset) 211 } 212 213 b.instructions[s.line] = inst 214 } 215 } 216 clear(b.labels) 217 return nil 218 } 219 220 // ProgramFragment is a set of not-compiled instructions that were added to 221 // a ProgramBuilder from the moment the `Record` function was called on it. 222 type ProgramFragment struct { 223 // b is a reference to the ProgramBuilder that this is a fragment from. 224 b *ProgramBuilder 225 226 // fromPC is the index of the first instruction that was recorded. 227 // If no instruction was recorded, this index will be equal to `toPC`. 228 fromPC int 229 230 // toPC is the index *after* the last instruction that was recorded. 231 // This means that right after recording, the program will not have 232 // any instruction at index `toPC`. 233 toPC int 234 } 235 236 // Record starts recording the instructions being added to the ProgramBuilder 237 // until the returned function is called. 238 // The returned function returns a ProgramFragment which represents the 239 // recorded instructions. It may be called repeatedly. 240 func (b *ProgramBuilder) Record() func() ProgramFragment { 241 currentPC := len(b.instructions) 242 return func() ProgramFragment { 243 return ProgramFragment{ 244 b: b, 245 fromPC: currentPC, 246 toPC: len(b.instructions), 247 } 248 } 249 } 250 251 // String returns a string version of the fragment. 252 func (f ProgramFragment) String() string { 253 return fmt.Sprintf("fromPC=%d toPC=%d", f.fromPC, f.toPC) 254 } 255 256 // FragmentOutcomes represents the set of outcomes that a ProgramFragment 257 // execution may result into. 258 type FragmentOutcomes struct { 259 // MayFallThrough is true if executing the fragment may cause it to start 260 // executing the program instruction that comes right after the last 261 // instruction in this fragment (i.e. at `Fragment.toPC`). 262 MayFallThrough bool 263 264 // MayJumpToKnownOffsetBeyondFragment is true if executing the fragment may 265 // jump to a fixed offset (or resolved label) that is not within the range 266 // of the fragment itself, nor does it point to the instruction that would 267 // come right after this fragment. 268 // If the fragment jumps to an unresolved label, this will instead be 269 // indicated in `MayJumpToUnresolvedLabels`. 270 MayJumpToKnownOffsetBeyondFragment bool 271 272 // MayJumpToUnresolvedLabels is the set of named labels that have not yet 273 // been added to the program (the labels are not resolvable) but that the 274 // fragment may jump to. 275 MayJumpToUnresolvedLabels map[string]struct{} 276 277 // MayReturnImmediate contains the set of possible immediate return values 278 // that the fragment may return. 279 MayReturnImmediate map[linux.BPFAction]struct{} 280 281 // MayReturnRegisterA is true if the fragment may return the value of 282 // register A. 283 MayReturnRegisterA bool 284 } 285 286 // String returns a list of possible human-readable outcomes. 287 func (o FragmentOutcomes) String() string { 288 var s []string 289 if o.MayJumpToKnownOffsetBeyondFragment { 290 s = append(s, "may jump to known offset beyond fragment") 291 } 292 sortedLabels := make([]string, 0, len(o.MayJumpToUnresolvedLabels)) 293 for lbl := range o.MayJumpToUnresolvedLabels { 294 sortedLabels = append(sortedLabels, lbl) 295 } 296 sort.Strings(sortedLabels) 297 for _, lbl := range sortedLabels { 298 s = append(s, fmt.Sprintf("may jump to unresolved label %q", lbl)) 299 } 300 if o.MayFallThrough { 301 s = append(s, "may fall through") 302 } 303 sortedReturnValues := make([]uint32, 0, len(o.MayReturnImmediate)) 304 for v := range o.MayReturnImmediate { 305 sortedReturnValues = append(sortedReturnValues, uint32(v)) 306 } 307 sort.Slice(sortedReturnValues, func(i, j int) bool { 308 return sortedReturnValues[i] < sortedReturnValues[j] 309 }) 310 for _, v := range sortedReturnValues { 311 s = append(s, fmt.Sprintf("may return '0x%x'", v)) 312 } 313 if o.MayReturnRegisterA { 314 s = append(s, "may return register A") 315 } 316 if len(s) == 0 { 317 return "no outcomes (this should never happen)" 318 } 319 return strings.Join(s, ", ") 320 } 321 322 // MayReturn returns whether the fragment may return for any reason. 323 func (o FragmentOutcomes) MayReturn() bool { 324 return len(o.MayReturnImmediate) > 0 || o.MayReturnRegisterA 325 } 326 327 // Outcomes returns the set of possible outcomes that executing this fragment 328 // may result into. 329 func (f ProgramFragment) Outcomes() FragmentOutcomes { 330 if f.fromPC == f.toPC { 331 // No instructions, this just falls through. 332 return FragmentOutcomes{ 333 MayFallThrough: true, 334 } 335 } 336 outcomes := FragmentOutcomes{ 337 MayJumpToUnresolvedLabels: make(map[string]struct{}), 338 MayReturnImmediate: make(map[linux.BPFAction]struct{}), 339 } 340 for pc := f.fromPC; pc < f.toPC; pc++ { 341 ins := f.b.instructions[pc] 342 isLastInstruction := pc == f.toPC-1 343 switch ins.OpCode & instructionClassMask { 344 case Ret: 345 switch ins.OpCode { 346 case Ret | K: 347 outcomes.MayReturnImmediate[linux.BPFAction(ins.K)] = struct{}{} 348 case Ret | A: 349 outcomes.MayReturnRegisterA = true 350 } 351 case Jmp: 352 for _, offset := range ins.JumpOffsets() { 353 var foundLabel *label 354 foundLabelName, found := f.b.jumpSourceToLabel[source{line: pc, jt: offset.Type}] 355 if found { 356 foundLabel = f.b.labels[foundLabelName] 357 if foundLabel.target == -1 { 358 outcomes.MayJumpToUnresolvedLabels[foundLabelName] = struct{}{} 359 continue 360 } 361 } 362 var target int 363 if foundLabel != nil { 364 target = foundLabel.target 365 } else { 366 target = pc + int(offset.Offset) + 1 367 } 368 if target == f.toPC { 369 outcomes.MayFallThrough = true 370 } else if target > f.toPC { 371 outcomes.MayJumpToKnownOffsetBeyondFragment = true 372 } 373 } 374 default: 375 if isLastInstruction { 376 outcomes.MayFallThrough = true 377 } 378 } 379 } 380 return outcomes 381 } 382 383 // MayModifyRegisterA returns whether this fragment may modify register A. 384 // A value of "true" does not necessarily mean that A *will* be modified, 385 // as the control flow of this fragment may skip over instructions that 386 // modify the A register. 387 func (f ProgramFragment) MayModifyRegisterA() bool { 388 for pc := f.fromPC; pc < f.toPC; pc++ { 389 if f.b.instructions[pc].ModifiesRegisterA() { 390 return true 391 } 392 } 393 return false 394 }