1use aya::{
2 maps::MapType,
3 programs::ProgramType,
4 sys::{is_helper_supported, is_map_supported, BpfHelper},
5};
6use std::{fmt, sync::OnceLock};
7use tracing::{error, info, warn};
8
9static KERNEL_CAPS: KernelCapabilityCache = KernelCapabilityCache::new();
11
12#[derive(Debug)]
13struct KernelCapabilityCache {
14 full: OnceLock<KernelCapabilities>,
15}
16
17impl KernelCapabilityCache {
18 const fn new() -> Self {
19 Self {
20 full: OnceLock::new(),
21 }
22 }
23
24 fn get_or_detect<F>(&self, detect: F) -> Result<KernelCapabilities, KernelCapabilityError>
25 where
26 F: FnOnce() -> Result<KernelCapabilityDetection, KernelCapabilityError>,
27 {
28 if let Some(capabilities) = self.full.get() {
29 return Ok(*capabilities);
30 }
31
32 let detection = detect()?;
33 if detection.cacheable {
34 let _ = self.full.set(detection.capabilities);
35 if let Some(capabilities) = self.full.get() {
36 return Ok(*capabilities);
37 }
38 } else {
39 warn!("Kernel capability probe used fallback values; not caching this result");
40 }
41
42 Ok(detection.capabilities)
43 }
44}
45
46#[derive(Debug, Clone)]
47pub struct KernelCapabilityError {
48 message: String,
49}
50
51impl KernelCapabilityError {
52 fn new(message: impl Into<String>) -> Self {
53 Self {
54 message: message.into(),
55 }
56 }
57}
58
59impl fmt::Display for KernelCapabilityError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 write!(f, "{}", self.message)
62 }
63}
64
65impl std::error::Error for KernelCapabilityError {}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub struct KernelCapabilities {
70 pub supports_ringbuf: bool,
72 pub supports_perf_event_array: bool,
74 pub supports_ns_current_pid_tgid_helper: bool,
76}
77
78impl KernelCapabilities {
79 pub fn detect_for_startup(force_perf_event_array: bool) -> Result<Self, KernelCapabilityError> {
82 detect_for_startup_with_detectors(force_perf_event_array, Self::get, Self::get_perf_only)
83 }
84
85 pub fn get() -> Result<Self, KernelCapabilityError> {
88 KERNEL_CAPS.get_or_detect(detect_full_capabilities)
89 }
90
91 pub fn get_perf_only() -> Result<Self, KernelCapabilityError> {
95 detect_perf_only_capabilities()
96 }
97
98 pub fn ringbuf_supported() -> bool {
100 Self::get()
101 .map(|caps| caps.supports_ringbuf)
102 .unwrap_or(false)
103 }
104
105 pub fn perf_event_array_supported() -> bool {
107 Self::get()
108 .map(|caps| caps.supports_perf_event_array)
109 .unwrap_or(false)
110 }
111
112 pub fn ns_current_pid_tgid_helper_supported() -> bool {
114 Self::get()
115 .map(|caps| caps.supports_ns_current_pid_tgid_helper)
116 .unwrap_or(false)
117 }
118}
119
120fn detect_for_startup_with_detectors<F, P>(
121 force_perf_event_array: bool,
122 detect_full: F,
123 detect_perf_only: P,
124) -> Result<KernelCapabilities, KernelCapabilityError>
125where
126 F: FnOnce() -> Result<KernelCapabilities, KernelCapabilityError>,
127 P: FnOnce() -> Result<KernelCapabilities, KernelCapabilityError>,
128{
129 let capabilities = if force_perf_event_array {
130 warn!("⚠️ TESTING MODE: force_perf_event_array=true - will use PerfEventArray");
131 detect_perf_only().map_err(|err| {
132 KernelCapabilityError::new(format!(
133 "{err}\nGhostScope requires Linux kernel >= 4.3 with PerfEventArray enabled."
134 ))
135 })?
136 } else {
137 detect_full().map_err(|err| {
138 KernelCapabilityError::new(format!(
139 "{err}\nHint: ensure CONFIG_BPF, CONFIG_BPF_SYSCALL and CONFIG_UPROBE_EVENTS are enabled in your kernel."
140 ))
141 })?
142 };
143
144 info!(
145 "Kernel eBPF startup summary: ringbuf_supported={} perf_event_array_supported={} helper_ns_current_pid_tgid={}",
146 capabilities.supports_ringbuf,
147 capabilities.supports_perf_event_array,
148 capabilities.supports_ns_current_pid_tgid_helper
149 );
150
151 Ok(capabilities)
152}
153
154#[derive(Debug, Clone, Copy)]
155struct KernelCapabilityDetection {
156 capabilities: KernelCapabilities,
157 cacheable: bool,
158}
159
160#[derive(Debug, Clone, Copy)]
161struct CapabilityProbe {
162 supported: bool,
163 cacheable: bool,
164}
165
166impl CapabilityProbe {
167 fn cacheable(supported: bool) -> Self {
168 Self {
169 supported,
170 cacheable: true,
171 }
172 }
173
174 fn uncacheable_unsupported() -> Self {
175 Self {
176 supported: false,
177 cacheable: false,
178 }
179 }
180}
181
182fn detect_full_capabilities() -> Result<KernelCapabilityDetection, KernelCapabilityError> {
183 let supports_ringbuf = detect_ringbuf_support();
184 let supports_perf_event_array = if !supports_ringbuf.supported {
185 detect_perf_event_array_support()
186 } else {
187 CapabilityProbe::cacheable(true)
188 };
189
190 if supports_ringbuf.supported {
191 info!("✓ Kernel supports RingBuf (>= 5.8)");
192 } else if supports_perf_event_array.supported {
193 warn!("⚠️ Kernel does not support RingBuf (< 5.8)");
194 warn!("⚠️ Will use PerfEventArray as fallback");
195 info!("✓ Kernel supports PerfEventArray (>= 4.3)");
196 } else {
197 if !supports_ringbuf.cacheable || !supports_perf_event_array.cacheable {
198 error!("❌ Unable to verify kernel eBPF event output support");
199 return Err(KernelCapabilityError::new(
200 "Unable to verify RingBuf or PerfEventArray support because one or more \
201 eBPF capability probes failed. Check privileges and kernel BPF settings.",
202 ));
203 }
204
205 error!("❌ Kernel supports neither RingBuf nor PerfEventArray");
206 error!("❌ GhostScope requires kernel >= 4.3 for eBPF event output");
207 error!("❌ Current kernel appears to be older or eBPF is disabled");
208 return Err(KernelCapabilityError::new(
209 "Kernel lacks both RingBuf (>=5.8) and PerfEventArray (>=4.3) support. \
210 Please upgrade the kernel or enable eBPF features.",
211 ));
212 }
213
214 let supports_ns_current_pid_tgid_helper = detect_ns_current_pid_tgid_helper_support();
215 if supports_ns_current_pid_tgid_helper.supported {
216 info!("✓ Kernel supports helper bpf_get_ns_current_pid_tgid (id=120)");
217 } else {
218 warn!("⚠️ Kernel does not support helper bpf_get_ns_current_pid_tgid (id=120)");
219 }
220
221 Ok(KernelCapabilityDetection {
222 capabilities: KernelCapabilities {
223 supports_ringbuf: supports_ringbuf.supported,
224 supports_perf_event_array: supports_perf_event_array.supported,
225 supports_ns_current_pid_tgid_helper: supports_ns_current_pid_tgid_helper.supported,
226 },
227 cacheable: supports_ringbuf.cacheable
228 && supports_perf_event_array.cacheable
229 && supports_ns_current_pid_tgid_helper.cacheable,
230 })
231}
232
233fn detect_perf_only_capabilities() -> Result<KernelCapabilities, KernelCapabilityError> {
234 info!("Testing mode: Only detecting PerfEventArray support");
235 let supports_perf_event_array = detect_perf_event_array_support();
236
237 if !supports_perf_event_array.supported {
238 if !supports_perf_event_array.cacheable {
239 error!("❌ Unable to verify PerfEventArray support");
240 return Err(KernelCapabilityError::new(
241 "Unable to verify PerfEventArray support because the eBPF capability probe \
242 failed. Check privileges and kernel BPF settings.",
243 ));
244 }
245
246 error!("❌ Kernel does not support PerfEventArray");
247 error!("❌ GhostScope requires kernel >= 4.3 for eBPF event output");
248 return Err(KernelCapabilityError::new(
249 "Kernel lacks PerfEventArray support (>=4.3 required). \
250 Please upgrade the kernel or enable eBPF features.",
251 ));
252 }
253
254 info!("✓ Kernel supports PerfEventArray (>= 4.3)");
255
256 let supports_ns_current_pid_tgid_helper = detect_ns_current_pid_tgid_helper_support();
257 if supports_ns_current_pid_tgid_helper.supported {
258 info!("✓ Kernel supports helper bpf_get_ns_current_pid_tgid (id=120)");
259 } else {
260 warn!("⚠️ Kernel does not support helper bpf_get_ns_current_pid_tgid (id=120)");
261 }
262
263 Ok(KernelCapabilities {
264 supports_ringbuf: false,
265 supports_perf_event_array: supports_perf_event_array.supported,
266 supports_ns_current_pid_tgid_helper: supports_ns_current_pid_tgid_helper.supported,
267 })
268}
269
270fn detect_ringbuf_support() -> CapabilityProbe {
272 detect_map_support(
273 MapType::RingBuf,
274 "RingBuf",
275 "this is normal on kernels < 5.8",
276 )
277}
278
279fn detect_perf_event_array_support() -> CapabilityProbe {
281 detect_map_support(
282 MapType::PerfEventArray,
283 "PerfEventArray",
284 "kernel may be older than 4.3",
285 )
286}
287
288fn detect_map_support(
289 map_type: MapType,
290 label: &str,
291 unsupported_context: &str,
292) -> CapabilityProbe {
293 info!("Probing kernel {label} support via aya::sys::is_map_supported...");
294
295 match is_map_supported(map_type) {
296 Ok(true) => {
297 info!("{label} map support probe succeeded - {label} is supported");
298 CapabilityProbe::cacheable(true)
299 }
300 Ok(false) => {
301 info!("{label} map support probe reported unsupported ({unsupported_context})");
302 CapabilityProbe::cacheable(false)
303 }
304 Err(err) => {
305 warn!("{label} map support probe failed unexpectedly: {err}");
306 CapabilityProbe::uncacheable_unsupported()
307 }
308 }
309}
310
311fn detect_ns_current_pid_tgid_helper_support() -> CapabilityProbe {
312 info!(
313 "Probing kernel bpf_get_ns_current_pid_tgid helper support via aya::sys::is_helper_supported..."
314 );
315
316 match is_helper_supported(
317 ProgramType::KProbe,
318 BpfHelper::BPF_FUNC_get_ns_current_pid_tgid,
319 ) {
320 Ok(true) => {
321 info!(
322 "bpf_get_ns_current_pid_tgid helper support probe succeeded - helper is supported"
323 );
324 CapabilityProbe::cacheable(true)
325 }
326 Ok(false) => {
327 info!("bpf_get_ns_current_pid_tgid helper support probe reported unsupported");
328 CapabilityProbe::cacheable(false)
329 }
330 Err(err) => {
331 warn!("bpf_get_ns_current_pid_tgid helper support probe failed unexpectedly: {err}");
332 CapabilityProbe::uncacheable_unsupported()
333 }
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 fn caps(
342 supports_ringbuf: bool,
343 supports_perf_event_array: bool,
344 supports_ns_current_pid_tgid_helper: bool,
345 ) -> KernelCapabilities {
346 KernelCapabilities {
347 supports_ringbuf,
348 supports_perf_event_array,
349 supports_ns_current_pid_tgid_helper,
350 }
351 }
352
353 fn detection(capabilities: KernelCapabilities, cacheable: bool) -> KernelCapabilityDetection {
354 KernelCapabilityDetection {
355 capabilities,
356 cacheable,
357 }
358 }
359
360 #[test]
361 fn forced_perf_startup_does_not_populate_full_capabilities_cache() {
362 let cache = KernelCapabilityCache::new();
363 let perf_only_caps = caps(false, true, true);
364 let full_caps = caps(true, true, true);
365
366 let forced = detect_for_startup_with_detectors(
367 true,
368 || -> Result<KernelCapabilities, KernelCapabilityError> {
369 panic!("full detector should not run for forced perf startup")
370 },
371 || Ok(perf_only_caps),
372 )
373 .expect("forced perf startup detection");
374
375 assert_eq!(forced, perf_only_caps);
376
377 let normal = detect_for_startup_with_detectors(
378 false,
379 || cache.get_or_detect(|| Ok(detection(full_caps, true))),
380 || -> Result<KernelCapabilities, KernelCapabilityError> {
381 panic!("perf-only detector should not run for normal startup")
382 },
383 )
384 .expect("normal startup detection");
385
386 assert_eq!(normal, full_caps);
387 assert_eq!(
388 cache
389 .get_or_detect(|| {
390 panic!("full detector should not rerun after cacheable detection")
391 })
392 .expect("cached full capabilities"),
393 full_caps
394 );
395 }
396
397 #[test]
398 fn uncacheable_full_probe_result_is_not_cached() {
399 let cache = KernelCapabilityCache::new();
400 let uncacheable_caps = caps(false, true, false);
401 let cacheable_caps = caps(true, true, true);
402
403 let first = cache
404 .get_or_detect(|| Ok(detection(uncacheable_caps, false)))
405 .expect("uncacheable startup result");
406 assert_eq!(first, uncacheable_caps);
407
408 let second = cache
409 .get_or_detect(|| Ok(detection(cacheable_caps, true)))
410 .expect("cacheable startup result");
411 assert_eq!(second, cacheable_caps);
412
413 assert_eq!(
414 cache
415 .get_or_detect(|| {
416 panic!("full detector should not rerun after cacheable detection")
417 })
418 .expect("cached full capabilities"),
419 cacheable_caps
420 );
421 }
422}