github.com/kubeshark/ebpf@v0.9.2/features/prog.go (about) 1 package features 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "sync" 9 10 "github.com/kubeshark/ebpf" 11 "github.com/kubeshark/ebpf/asm" 12 "github.com/kubeshark/ebpf/internal" 13 "github.com/kubeshark/ebpf/internal/sys" 14 "github.com/kubeshark/ebpf/internal/unix" 15 ) 16 17 func init() { 18 pc.types = make(map[ebpf.ProgramType]error) 19 pc.helpers = make(map[ebpf.ProgramType]map[asm.BuiltinFunc]error) 20 allocHelperCache() 21 } 22 23 func allocHelperCache() { 24 for pt := ebpf.UnspecifiedProgram + 1; pt <= pt.Max(); pt++ { 25 pc.helpers[pt] = make(map[asm.BuiltinFunc]error) 26 } 27 } 28 29 var ( 30 pc progCache 31 ) 32 33 type progCache struct { 34 typeMu sync.Mutex 35 types map[ebpf.ProgramType]error 36 37 helperMu sync.Mutex 38 helpers map[ebpf.ProgramType]map[asm.BuiltinFunc]error 39 } 40 41 func createProgLoadAttr(pt ebpf.ProgramType, helper asm.BuiltinFunc) (*sys.ProgLoadAttr, error) { 42 var expectedAttachType ebpf.AttachType 43 var progFlags uint32 44 45 insns := asm.Instructions{ 46 asm.LoadImm(asm.R0, 0, asm.DWord), 47 asm.Return(), 48 } 49 50 if helper != asm.FnUnspec { 51 insns = append(asm.Instructions{helper.Call()}, insns...) 52 } 53 54 buf := bytes.NewBuffer(make([]byte, 0, insns.Size())) 55 if err := insns.Marshal(buf, internal.NativeEndian); err != nil { 56 return nil, err 57 } 58 59 bytecode := buf.Bytes() 60 instructions := sys.NewSlicePointer(bytecode) 61 62 // Some programs have expected attach types which are checked during the 63 // BPD_PROG_LOAD syscall. 64 switch pt { 65 case ebpf.CGroupSockAddr: 66 expectedAttachType = ebpf.AttachCGroupInet4Connect 67 case ebpf.CGroupSockopt: 68 expectedAttachType = ebpf.AttachCGroupGetsockopt 69 case ebpf.SkLookup: 70 expectedAttachType = ebpf.AttachSkLookup 71 case ebpf.Syscall: 72 progFlags = unix.BPF_F_SLEEPABLE 73 default: 74 expectedAttachType = ebpf.AttachNone 75 } 76 77 // Kernels before 5.0 (6c4fc209fcf9 "bpf: remove useless version check for prog load") 78 // require the version field to be set to the value of the KERNEL_VERSION 79 // macro for kprobe-type programs. 80 v, err := internal.KernelVersion() 81 if err != nil { 82 return nil, fmt.Errorf("detecting kernel version: %w", err) 83 } 84 85 return &sys.ProgLoadAttr{ 86 ProgType: sys.ProgType(pt), 87 Insns: instructions, 88 InsnCnt: uint32(len(bytecode) / asm.InstructionSize), 89 ProgFlags: progFlags, 90 ExpectedAttachType: sys.AttachType(expectedAttachType), 91 License: sys.NewStringPointer("GPL"), 92 KernVersion: v.Kernel(), 93 }, nil 94 } 95 96 // HaveProgType probes the running kernel for the availability of the specified program type. 97 // 98 // Deprecated: use HaveProgramType() instead. 99 var HaveProgType = HaveProgramType 100 101 // HaveProgramType probes the running kernel for the availability of the specified program type. 102 // 103 // See the package documentation for the meaning of the error return value. 104 func HaveProgramType(pt ebpf.ProgramType) error { 105 if err := validateProgramType(pt); err != nil { 106 return err 107 } 108 109 return haveProgramType(pt) 110 111 } 112 113 func validateProgramType(pt ebpf.ProgramType) error { 114 if pt > pt.Max() { 115 return os.ErrInvalid 116 } 117 118 if progLoadProbeNotImplemented(pt) { 119 // A probe for a these prog types has BTF requirements we currently cannot meet 120 // Once we figure out how to add a working probe in this package, we can remove 121 // this check 122 return fmt.Errorf("a probe for ProgType %s isn't implemented", pt.String()) 123 } 124 125 return nil 126 } 127 128 func haveProgramType(pt ebpf.ProgramType) error { 129 pc.typeMu.Lock() 130 defer pc.typeMu.Unlock() 131 if err, ok := pc.types[pt]; ok { 132 return err 133 } 134 135 attr, err := createProgLoadAttr(pt, asm.FnUnspec) 136 if err != nil { 137 return fmt.Errorf("couldn't create the program load attribute: %w", err) 138 } 139 140 fd, err := sys.ProgLoad(attr) 141 142 switch { 143 // EINVAL occurs when attempting to create a program with an unknown type. 144 // E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end 145 // of the struct known by the running kernel, meaning the kernel is too old 146 // to support the given prog type. 147 case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG): 148 err = ebpf.ErrNotSupported 149 150 // EPERM is kept as-is and is not converted or wrapped. 151 case errors.Is(err, unix.EPERM): 152 break 153 154 // Wrap unexpected errors. 155 case err != nil: 156 err = fmt.Errorf("unexpected error during feature probe: %w", err) 157 158 default: 159 fd.Close() 160 } 161 162 pc.types[pt] = err 163 164 return err 165 } 166 167 // HaveProgramHelper probes the running kernel for the availability of the specified helper 168 // function to a specified program type. 169 // Return values have the following semantics: 170 // 171 // err == nil: The feature is available. 172 // errors.Is(err, ebpf.ErrNotSupported): The feature is not available. 173 // err != nil: Any errors encountered during probe execution, wrapped. 174 // 175 // Note that the latter case may include false negatives, and that program creation may 176 // succeed despite an error being returned. 177 // Only `nil` and `ebpf.ErrNotSupported` are conclusive. 178 // 179 // Probe results are cached and persist throughout any process capability changes. 180 func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error { 181 if err := validateProgramType(pt); err != nil { 182 return err 183 } 184 185 if err := validateProgramHelper(helper); err != nil { 186 return err 187 } 188 189 return haveProgramHelper(pt, helper) 190 } 191 192 func validateProgramHelper(helper asm.BuiltinFunc) error { 193 if helper > helper.Max() { 194 return os.ErrInvalid 195 } 196 197 return nil 198 } 199 200 func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error { 201 pc.helperMu.Lock() 202 defer pc.helperMu.Unlock() 203 if err, ok := pc.helpers[pt][helper]; ok { 204 return err 205 } 206 207 attr, err := createProgLoadAttr(pt, helper) 208 if err != nil { 209 return fmt.Errorf("couldn't create the program load attribute: %w", err) 210 } 211 212 fd, err := sys.ProgLoad(attr) 213 214 switch { 215 // If there is no error we need to close the FD of the prog. 216 case err == nil: 217 fd.Close() 218 219 // EACCES occurs when attempting to create a program probe with a helper 220 // while the register args when calling this helper aren't set up properly. 221 // We interpret this as the helper being available, because the verifier 222 // returns EINVAL if the helper is not supported by the running kernel. 223 case errors.Is(err, unix.EACCES): 224 // TODO: possibly we need to check verifier output here to be sure 225 err = nil 226 227 // EINVAL occurs when attempting to create a program with an unknown helper. 228 // E2BIG occurs when BPFProgLoadAttr contains non-zero bytes past the end 229 // of the struct known by the running kernel, meaning the kernel is too old 230 // to support the given prog type. 231 case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG): 232 // TODO: possibly we need to check verifier output here to be sure 233 err = ebpf.ErrNotSupported 234 235 // EPERM is kept as-is and is not converted or wrapped. 236 case errors.Is(err, unix.EPERM): 237 break 238 239 // Wrap unexpected errors. 240 case err != nil: 241 err = fmt.Errorf("unexpected error during feature probe: %w", err) 242 } 243 244 pc.helpers[pt][helper] = err 245 246 return err 247 } 248 249 func progLoadProbeNotImplemented(pt ebpf.ProgramType) bool { 250 switch pt { 251 case ebpf.Tracing, ebpf.StructOps, ebpf.Extension, ebpf.LSM: 252 return true 253 } 254 return false 255 }