Repository: aya-rs/aya (language: rust)

Issue:
panic: enum relocation index out of bounds
I got the following panic on enum relocation:

```console
panicked at /home/user/.cargo/git/checkouts/aya-c55fbc69175ac116/80736c5/aya-obj/src/btf/relocation.rs:1050:47:
index out of bounds: the len is 0 but the index is 0
```

the related code is: https://github.com/aya-rs/aya/blob/80736c5db26cd64aa821bb21fcdcfbbe9471c7cd/aya-obj/src/btf/relocation.rs#L1050

I was testing `bpf_core_enum_value` and in particular in the case when a specific enum value is not present on the kernel that is loading the program.

I'm building a C program against `libbpf` and then I'm loading it with `aya`.

To test it I modified the struct `cgroup_subsys_id` of the `vmlinux.h` dumped from my actual kernel, adding a field before `CGROUP_SUBSYS_COUNT` in this way:

```c
enum cgroup_subsys_id {
	cpuset_cgrp_id = 0,
	cpu_cgrp_id = 1,
	cpuacct_cgrp_id = 2,
	io_cgrp_id = 3,
	memory_cgrp_id = 4,
	devices_cgrp_id = 5,
	freezer_cgrp_id = 6,
	net_cls_cgrp_id = 7,
	perf_event_cgrp_id = 8,
	net_prio_cgrp_id = 9,
	hugetlb_cgrp_id = 10,
	pids_cgrp_id = 11,
	rdma_cgrp_id = 12,
	misc_cgrp_id = 13,
	cristo_cgrp_id = 14, // <----- Added this, the original line was `CGROUP_SUBSYS_COUNT = 14` and it was the last enum value
	CGROUP_SUBSYS_COUNT = 15,	
};
```

and the I created a BPF program using it in a way like this:

```c
int cristo_id;
if (bpf_core_enum_value_exists(enum cgroup_subsys_id, cristo_cgrp_id))
  cristo_id = bpf_core_enum_value(enum cgroup_subsys_id, cristo_cgrp_id);
else
  cristo_id = 666;
```

Trying to load the program in the kernel, it results in the error above.



Discussion / hints:

Fix patch:
diff --git a/aya-obj/src/btf/relocation.rs b/aya-obj/src/btf/relocation.rs
index c23bee759..c9412c79b 100644
--- a/aya-obj/src/btf/relocation.rs
+++ b/aya-obj/src/btf/relocation.rs
@@ -450,9 +450,9 @@ fn match_candidate<'target>(
                 candidate.btf,
                 candidate.type_id,
             )? {
-                return Ok(Some(target_spec));
+                Ok(Some(target_spec))
             } else {
-                return Ok(None);
+                Ok(None)
             }
         }
         RelocationKind::EnumVariantExists | RelocationKind::EnumVariantValue => {
@@ -460,8 +460,15 @@ fn match_candidate<'target>(
             let target_ty = candidate.btf.type_by_id(target_id)?;
             // the first accessor is guaranteed to have a name by construction
             let local_variant_name = local_spec.accessors[0].name.as_ref().unwrap();
-            let match_enum =
-                |name_offset, index, target_spec: &mut AccessSpec| -> Result<_, BtfError> {
+
+            fn match_enum<'a>(
+                iterator: impl Iterator<Item = (usize, u32)>,
+                candidate: &Candidate,
+                local_variant_name: &str,
+                target_id: u32,
+                mut target_spec: AccessSpec<'a>,
+            ) -> Result<Option<AccessSpec<'a>>, RelocationError> {
+                for (index, name_offset) in iterator {
                     let target_variant_name = candidate.btf.string_at(name_offset)?;
                     if flavorless_name(local_variant_name) == flavorless_name(&target_variant_name)
                     {
@@ -471,29 +478,34 @@ fn match_candidate<'target>(
                             type_id: target_id,
                             name: None,
                         });
-                        Ok(Some(()))
-                    } else {
-                        Ok(None)
-                    }
-                };
-            match target_ty {
-                BtfType::Enum(en) => {
-                    for (index, member) in en.variants.iter().enumerate() {
-                        if let Ok(Some(_)) = match_enum(member.name_offset, index, &mut target_spec)
-                        {
-                            return Ok(Some(target_spec));
-                        }
-                    }
-                }
-                BtfType::Enum64(en) => {
-                    for (index, member) in en.variants.iter().enumerate() {
-                        if let Ok(Some(_)) = match_enum(member.name_offset, index, &mut target_spec)
-                        {
-                            return Ok(Some(target_spec));
-                        }
+                        return Ok(Some(target_spec));
                     }
                 }
-                _ => return Ok(None),
+                Ok(None)
+            }
+
+            match target_ty {
+                BtfType::Enum(en) => match_enum(
+                    en.variants
+                        .iter()
+                        .map(|member| member.name_offset)
+                        .enumerate(),
+                    candidate,
+                    local_variant_name,
+                    target_id,
+                    target_spec,
+                ),
+                BtfType::Enum64(en) => match_enum(
+                    en.variants
+                        .iter()
+                        .map(|member| member.name_offset)
+                        .enumerate(),
+                    candidate,
+                    local_variant_name,
+                    target_id,
+                    target_spec,
+                ),
+                _ => Ok(None),
             }
         }
         RelocationKind::FieldByteOffset
@@ -560,10 +572,9 @@ fn match_candidate<'target>(
                         accessor.index * candidate.btf.type_size(target_id)? * 8;
                 }
             }
+            Ok(Some(target_spec))
         }
-    };
-
-    Ok(Some(target_spec))
+    }
 }
 
 fn match_member<'target>(


Test patch:
diff --git a/test/integration-test/bpf/reloc.bpf.c b/test/integration-test/bpf/reloc.bpf.c
index 609e504b0..f8104bbb9 100644
--- a/test/integration-test/bpf/reloc.bpf.c
+++ b/test/integration-test/bpf/reloc.bpf.c
@@ -90,6 +90,38 @@ SEC("uprobe") int enum_unsigned_32(void *ctx) {
   return enum_unsigned_32_global();
 }
 
+enum relocated_enum_unsigned_32_checked_variants {
+#ifndef TARGET
+  U32_VAL_A = 0xAAAAAAAA,
+#endif
+  U32_VAL_B = 0xBBBBBBBB,
+#ifdef TARGET
+  U32_VAL_C = 0xCCCCCCCC
+#endif
+};
+
+__noinline int enum_unsigned_32_checked_variants_global() {
+#ifndef TARGET
+  if (bpf_core_enum_value_exists(
+          enum relocated_enum_unsigned_32_checked_variants, U32_VAL_A)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_unsigned_32_checked_variants, U32_VAL_A));
+#else
+  if (bpf_core_enum_value_exists(
+          enum relocated_enum_unsigned_32_checked_variants, U32_VAL_C)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_unsigned_32_checked_variants, U32_VAL_C));
+#endif
+  } else {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_unsigned_32_checked_variants, U32_VAL_B));
+  }
+}
+
+SEC("uprobe") int enum_unsigned_32_checked_variants(void *ctx) {
+  return enum_unsigned_32_checked_variants_global();
+}
+
 enum relocated_enum_signed_32 {
   S32_VAL =
 #ifndef TARGET
@@ -106,6 +138,38 @@ __noinline int enum_signed_32_global() {
 
 SEC("uprobe") int enum_signed_32(void *ctx) { return enum_signed_32_global(); }
 
+enum relocated_enum_signed_32_checked_variants {
+#ifndef TARGET
+  S32_VAL_A = -0x7AAAAAAA,
+#endif
+  S32_VAL_B = -0x7BBBBBBB,
+#ifdef TARGET
+  S32_VAL_C = -0x7CCCCCCC
+#endif
+};
+
+__noinline int enum_signed_32_checked_variants_global() {
+#ifndef TARGET
+  if (bpf_core_enum_value_exists(enum relocated_enum_signed_32_checked_variants,
+                                 S32_VAL_A)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_signed_32_checked_variants, S32_VAL_A));
+#else
+  if (bpf_core_enum_value_exists(enum relocated_enum_signed_32_checked_variants,
+                                 S32_VAL_C)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_signed_32_checked_variants, S32_VAL_C));
+#endif
+  } else {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_signed_32_checked_variants, S32_VAL_B));
+  }
+}
+
+SEC("uprobe") int enum_signed_32_checked_variants(void *ctx) {
+  return enum_signed_32_checked_variants_global();
+}
+
 enum relocated_enum_unsigned_64 {
   U64_VAL =
 #ifndef TARGET
@@ -124,6 +188,38 @@ SEC("uprobe") int enum_unsigned_64(void *ctx) {
   return enum_unsigned_64_global();
 }
 
+enum relocated_enum_unsigned_64_checked_variants {
+#ifndef TARGET
+  U64_VAL_A = 0xAAAAAAAABBBBBBBB,
+#endif
+  U64_VAL_B = 0xCCCCCCCCDDDDDDDD,
+#ifdef TARGET
+  U64_VAL_C = 0xEEEEEEEEFFFFFFFF
+#endif
+};
+
+__noinline int enum_unsigned_64_checked_variants_global() {
+#ifndef TARGET
+  if (bpf_core_enum_value_exists(
+          enum relocated_enum_unsigned_64_checked_variants, U64_VAL_A)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_unsigned_64_checked_variants, U64_VAL_A));
+#else
+  if (bpf_core_enum_value_exists(
+          enum relocated_enum_unsigned_64_checked_variants, U64_VAL_C)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_unsigned_64_checked_variants, U64_VAL_C));
+#endif
+  } else {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_unsigned_64_checked_variants, U64_VAL_B));
+  }
+}
+
+SEC("uprobe") int enum_unsigned_64_checked_variants(void *ctx) {
+  return enum_unsigned_64_checked_variants_global();
+}
+
 enum relocated_enum_signed_64 {
   S64_VAL =
 #ifndef TARGET
@@ -139,3 +235,35 @@ __noinline int enum_signed_64_global() {
 }
 
 SEC("uprobe") int enum_signed_64(void *ctx) { return enum_signed_64_global(); }
+
+enum relocated_enum_signed_64_checked_variants {
+#ifndef TARGET
+  S64_VAL_A = -0xAAAAAAABBBBBBB,
+#endif
+  S64_VAL_B = -0xCCCCCCCDDDDDDD,
+#ifdef TARGET
+  S64_VAL_C = -0xEEEEEEEFFFFFFF
+#endif
+};
+
+__noinline int enum_signed_64_checked_variants_global() {
+#ifndef TARGET
+  if (bpf_core_enum_value_exists(enum relocated_enum_signed_64_checked_variants,
+                                 S64_VAL_A)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_signed_64_checked_variants, S64_VAL_A));
+#else
+  if (bpf_core_enum_value_exists(enum relocated_enum_signed_64_checked_variants,
+                                 S64_VAL_C)) {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_signed_64_checked_variants, S64_VAL_C));
+#endif
+  } else {
+    return set_output(bpf_core_enum_value(
+        enum relocated_enum_signed_64_checked_variants, S64_VAL_B));
+  }
+}
+
+SEC("uprobe") int enum_signed_64_checked_variants(void *ctx) {
+  return enum_signed_64_checked_variants_global();
+}
diff --git a/test/integration-test/src/tests/btf_relocations.rs b/test/integration-test/src/tests/btf_relocations.rs
index c149d43e1..2e6c2ee40 100644
--- a/test/integration-test/src/tests/btf_relocations.rs
+++ b/test/integration-test/src/tests/btf_relocations.rs
@@ -3,12 +3,20 @@ use test_case::test_case;
 
 #[test_case("enum_signed_32", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7AAAAAAAi32 as u64)]
 #[test_case("enum_signed_32", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7BBBBBBBi32 as u64)]
+#[test_case("enum_signed_32_checked_variants", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7AAAAAAAi32 as u64)]
+#[test_case("enum_signed_32_checked_variants", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0x7BBBBBBBi32 as u64)]
 #[test_case("enum_signed_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xAAAAAAABBBBBBBBi64 as u64)]
 #[test_case("enum_signed_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xCCCCCCCDDDDDDDDi64 as u64)]
+#[test_case("enum_signed_64_checked_variants", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xAAAAAAABBBBBBBi64 as u64)]
+#[test_case("enum_signed_64_checked_variants", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), -0xCCCCCCCDDDDDDDi64 as u64)]
 #[test_case("enum_unsigned_32", false, None, 0xAAAAAAAA)]
 #[test_case("enum_unsigned_32", true, None, 0xBBBBBBBB)]
+#[test_case("enum_unsigned_32_checked_variants", false, None, 0xAAAAAAAA)]
+#[test_case("enum_unsigned_32_checked_variants", true, None, 0xBBBBBBBB)]
 #[test_case("enum_unsigned_64", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xAAAAAAAABBBBBBBB)]
 #[test_case("enum_unsigned_64", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xCCCCCCCCDDDDDDDD)]
+#[test_case("enum_unsigned_64_checked_variants", false, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xAAAAAAAABBBBBBBB)]
+#[test_case("enum_unsigned_64_checked_variants", true, Some((KernelVersion::new(6, 0, 0), "https://github.com/torvalds/linux/commit/6089fb3")), 0xCCCCCCCCDDDDDDDD)]
 #[test_case("field", false, None, 2)]
 #[test_case("field", true, None, 1)]
 #[test_case("pointer", false, None, 42)]