use super::super::*;
use super::*;
use goblin::elf::header as h;
use goblin::elf::section_header as sh;
use goblin::elf::sym as syms;
#[test]
fn patch_kfunc_calls_happy_path_rewrites_call_site() {
let kf_name = "bpf_task_acquire";
let (btf_blob, expected_func_id, _t_id) = build_kfunc_btf_blob(kf_name);
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let mut strtab: Vec<u8> = vec![0];
let kf_str_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
kf_str_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_kfunc_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_kfunc_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
assert_eq!(text_concat[0].code, 0x85);
assert_eq!(text_concat[0].src_reg(), BPF_PSEUDO_CALL);
assert_eq!(text_concat[0].imm, -1);
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(text_concat[0].code, 0x85);
assert_eq!(
text_concat[0].src_reg(),
BPF_PSEUDO_KFUNC_CALL,
"src_reg now BPF_PSEUDO_KFUNC_CALL"
);
assert_eq!(
text_concat[0].imm, expected_func_id as i32,
"imm patched to BTF Func id"
);
assert_eq!(text_concat[1].code, 0x95);
}
#[test]
fn patch_kfunc_calls_skips_non_extern_symbol() {
let kf_name = "static_helper";
let (btf_blob, _func_id, _) = build_kfunc_btf_blob(kf_name);
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_LOCAL, syms::STT_NOTYPE),
0,
0,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_kfunc_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_kfunc_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(text_concat[0].src_reg(), BPF_PSEUDO_CALL);
assert_eq!(text_concat[0].imm, -1);
}
#[test]
fn patch_kfunc_calls_skips_symbol_not_in_btf() {
let (btf_blob, _func_id, _) = build_kfunc_btf_blob("bpf_task_acquire");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let unknown = "unknown_kfunc";
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(unknown.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_kfunc_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_kfunc_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(text_concat[0].src_reg(), BPF_PSEUDO_CALL);
assert_eq!(text_concat[0].imm, -1);
}
#[test]
fn patch_kfunc_calls_ignores_non_text_relocations() {
let kf_name = "bpf_task_acquire";
let (btf_blob, _func_id, _) = build_kfunc_btf_blob(kf_name);
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_kfunc_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".maps", sh::SHT_PROGBITS).data(vec![0u8; 8]),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(3)
.entsize(24),
SecSpec::new(".rel.maps", sh::SHT_REL)
.data(rel_data)
.link(4)
.info(2)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_kfunc_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(text_concat[0].src_reg(), BPF_PSEUDO_CALL);
assert_eq!(text_concat[0].imm, -1);
}
#[test]
fn patch_kfunc_calls_rejects_out_of_bounds_offset() {
let kf_name = "bpf_task_acquire";
let (btf_blob, _func_id, _) = build_kfunc_btf_blob(kf_name);
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_kfunc_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(100, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_kfunc_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(text_concat[0].src_reg(), BPF_PSEUDO_CALL);
assert_eq!(text_concat[0].imm, -1);
}
#[test]
fn patch_kfunc_calls_rejects_non_call_instruction() {
let kf_name = "bpf_task_acquire";
let (btf_blob, _func_id, _) = build_kfunc_btf_blob(kf_name);
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let ld_imm64_first_slot: [u8; 8] = [0x18, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let ld_imm64_second_slot: [u8; 8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&ld_imm64_first_slot);
text.extend_from_slice(&ld_imm64_second_slot);
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 1).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(ld_imm64_first_slot),
BpfInsn::from_le_bytes(ld_imm64_second_slot),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
let pre = text_concat.clone();
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(text_concat, pre);
}
#[test]
fn patch_kfunc_calls_rejects_non_minus_one_imm() {
let kf_name = "bpf_task_acquire";
let (btf_blob, _func_id, _) = build_kfunc_btf_blob(kf_name);
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let subprog_call: [u8; 8] = [0x85, 0x10, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00];
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&subprog_call);
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(subprog_call),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(text_concat[0].src_reg(), BPF_PSEUDO_CALL);
assert_eq!(text_concat[0].imm, 42);
}
#[test]
fn find_extern_func_btf_id_filters_to_func_kind() {
let mut strings: Vec<u8> = vec![0];
let n_u64 = strings.len() as u32;
strings.extend_from_slice(b"u64");
strings.push(0);
let n_foo = strings.len() as u32;
strings.extend_from_slice(b"foo");
strings.push(0);
let mut types: Vec<u8> = Vec::new();
types.extend_from_slice(&kfunc_btf_type_header(n_u64, 1, 0, 8));
types.extend_from_slice(&64u32.to_le_bytes());
types.extend_from_slice(&kfunc_btf_type_header(n_foo, 14, 0, 1));
types.extend_from_slice(&1u32.to_le_bytes());
let mut blob: Vec<u8> = Vec::new();
blob.extend_from_slice(&0xEB9F_u16.to_le_bytes());
blob.push(1);
blob.push(0);
blob.extend_from_slice(&24u32.to_le_bytes());
blob.extend_from_slice(&0u32.to_le_bytes());
blob.extend_from_slice(&(types.len() as u32).to_le_bytes());
blob.extend_from_slice(&(types.len() as u32).to_le_bytes());
blob.extend_from_slice(&(strings.len() as u32).to_le_bytes());
blob.extend_from_slice(&types);
blob.extend_from_slice(&strings);
let btf = Btf::from_bytes(&blob).expect("parse btf");
assert_eq!(find_extern_func_btf_id(&btf, "foo"), None);
assert_eq!(find_extern_func_btf_id(&btf, "absent"), None);
}
#[test]
fn patch_subprog_calls_happy_path_rewrites_imm() {
let callee_name = "my_subprog";
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(callee_name.as_bytes());
strtab.push(0);
let callee_st_value: u64 = 16;
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_FUNC),
1, callee_st_value,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_subprog_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
text.extend_from_slice(&subprog_nop_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_subprog_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
BpfInsn::from_le_bytes(subprog_nop_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
assert_eq!(text_concat[0].imm, -1);
patch_subprog_calls(&mut text_concat, &elf, §ion_bases);
assert_eq!(
text_concat[0].imm, 1,
"imm patched to callee_pc - call_pc - 1"
);
assert_eq!(
text_concat[0].src_reg(),
BPF_PSEUDO_CALL,
"src_reg untouched (subprog calls keep BPF_PSEUDO_CALL)"
);
assert_eq!(text_concat[0].code, 0x85, "opcode untouched");
}
#[test]
fn patch_subprog_calls_skips_non_minus_one_imm() {
let callee_name = "static_subprog";
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(callee_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_LOCAL, syms::STT_FUNC),
1,
0,
0,
));
let mut call = pre_reloc_subprog_call_bytes();
call[4..8].copy_from_slice(&5i32.to_le_bytes());
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&call);
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(call),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
assert_eq!(text_concat[0].imm, 5);
patch_subprog_calls(&mut text_concat, &elf, §ion_bases);
assert_eq!(text_concat[0].imm, 5, "non-(-1) imm must stay untouched");
}
#[test]
fn patch_subprog_calls_skips_stt_notype_symbol() {
let kf_name = "bpf_some_kfunc";
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_subprog_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_subprog_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
patch_subprog_calls(&mut text_concat, &elf, §ion_bases);
assert_eq!(
text_concat[0].imm, -1,
"STT_NOTYPE / SHN_UNDEF kfunc shape must not be touched"
);
}
#[test]
fn patch_subprog_calls_skips_callee_section_outside_section_bases() {
let callee_name = "subprog_in_other_section";
let mut strtab: Vec<u8> = vec![0];
let name_off = strtab.len() as u32;
strtab.extend_from_slice(callee_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
name_off,
st_info(syms::STB_GLOBAL, syms::STT_FUNC),
5,
0,
0,
));
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&pre_reloc_subprog_call_bytes());
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".other", sh::SHT_PROGBITS).data(vec![0u8; 8]),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(pre_reloc_subprog_call_bytes()),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
patch_subprog_calls(&mut text_concat, &elf, §ion_bases);
assert_eq!(
text_concat[0].imm, -1,
"callee section outside section_bases must skip patching"
);
}
#[test]
fn build_subprog_returns_happy_path_emits_one() {
let (blob, text_concat, section_bases) = build_subprog_test_scaffold(
"scx_alloc_internal",
st_info(syms::STB_GLOBAL, syms::STT_FUNC),
1, pseudo_call_bytes(123),
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let out = build_subprog_returns(&text_concat, &elf, §ion_bases);
assert_eq!(out.len(), 1, "happy path: expected 1 entry, got {out:?}");
assert_eq!(
out[0].insn_offset, 0,
"SubprogReturn must point at the call PC"
);
}
#[test]
fn build_subprog_returns_skips_pseudo_kfunc_call() {
let (blob, text_concat, section_bases) = build_subprog_test_scaffold(
"scx_alloc_internal",
st_info(syms::STB_GLOBAL, syms::STT_FUNC),
1,
pseudo_kfunc_call_bytes(0),
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let out = build_subprog_returns(&text_concat, &elf, §ion_bases);
assert!(
out.is_empty(),
"BPF_PSEUDO_KFUNC_CALL must not seed a SubprogReturn: {out:?}"
);
}
#[test]
fn build_subprog_returns_skips_stt_object() {
let (blob, text_concat, section_bases) = build_subprog_test_scaffold(
"scx_alloc_internal",
st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
1,
pseudo_call_bytes(0),
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let out = build_subprog_returns(&text_concat, &elf, §ion_bases);
assert!(
out.is_empty(),
"STT_OBJECT symbol must not seed a SubprogReturn: {out:?}"
);
}
#[test]
fn build_subprog_returns_skips_non_allowlist_name() {
let (blob, text_concat, section_bases) = build_subprog_test_scaffold(
"ktstr_some_unrelated_helper",
st_info(syms::STB_GLOBAL, syms::STT_FUNC),
1,
pseudo_call_bytes(0),
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let out = build_subprog_returns(&text_concat, &elf, §ion_bases);
assert!(
out.is_empty(),
"non-allowlist subprog name must not seed a SubprogReturn: {out:?}"
);
}
#[test]
fn build_datasec_pointers_rejects_non_r_bpf_64_64() {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".bss",
10, 0,
0,
1, st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
0,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert!(out.is_empty(), "non-R_BPF_64_64 reloc must be skipped");
}
#[test]
fn build_datasec_pointers_rejects_non_multiple_of_8_offset() {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".bss",
1,
4, 0,
1,
st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
0,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert!(
out.is_empty(),
"r_offset=4 (not multiple of 8) must be rejected"
);
}
#[test]
fn build_datasec_pointers_rejects_offset_past_section_size() {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".bss",
1,
100, 0,
1,
st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
0,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert!(
out.is_empty(),
"r_offset past section size must be rejected"
);
}
#[test]
fn build_datasec_pointers_rejects_non_ld_imm64_opcode() {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".bss",
1,
16, 0,
1,
st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
0,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert!(
out.is_empty(),
"reloc on non-LD_IMM64 opcode must be rejected"
);
}
#[test]
fn build_datasec_pointers_rejects_special_section_index_symbols() {
for shndx in [0u16, 0xFFF1, 0xFFF2] {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".bss",
1,
0,
0,
shndx,
st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
0,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert!(
out.is_empty(),
"symbol with st_shndx={shndx:#x} must be rejected"
);
}
}
#[test]
fn build_datasec_pointers_rejects_section_not_in_btf() {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".rodata", 1,
0,
0,
1,
st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
0,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert!(
out.is_empty(),
"section name not in BTF as DATASEC must be rejected"
);
}
#[test]
fn build_datasec_pointers_rejects_st_value_past_u32_max() {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".bss",
1,
0,
(u32::MAX as u64) + 1, 1,
st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
0,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert!(out.is_empty(), "sym.st_value > u32::MAX must be rejected");
}
#[test]
fn build_datasec_pointers_happy_path_emits_pointer() {
let (blob, btf_blob, text_concat, section_bases) = build_datasec_test_scaffold(
".bss",
".bss",
1, 0, 0, 1, st_info(syms::STB_GLOBAL, syms::STT_OBJECT),
16, );
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let out = build_datasec_pointers(&text_concat, &btf, &elf, §ion_bases);
assert_eq!(out.len(), 1, "all gates pass → exactly one entry");
assert_eq!(out[0].insn_offset, 0, "PC = base + r_offset/8 = 0");
assert_eq!(
out[0].datasec_type_id, 2,
"datasec id is 2 (per build_datasec_btf_blob)"
);
assert_eq!(
out[0].base_offset, 16,
"base_offset = imm (16) + st_value (0) = 16"
);
}
#[test]
fn find_datasec_btf_id_filters_to_datasec_kind() {
let mut strings: Vec<u8> = vec![0];
let n_bss = strings.len() as u32;
strings.extend_from_slice(b".bss");
strings.push(0);
let mut types: Vec<u8> = Vec::new();
types.extend_from_slice(&kfunc_btf_type_header(n_bss, 1, 0, 4));
let int_data: u32 = 32;
types.extend_from_slice(&int_data.to_le_bytes());
types.extend_from_slice(&kfunc_btf_type_header(n_bss, 14, 0, 1));
let var_linkage: u32 = 1; types.extend_from_slice(&var_linkage.to_le_bytes());
append_btf_datasec(&mut types, n_bss, 8, &[]);
let mut blob: Vec<u8> = Vec::new();
blob.extend_from_slice(&0xEB9F_u16.to_le_bytes());
blob.push(1);
blob.push(0);
blob.extend_from_slice(&24u32.to_le_bytes());
blob.extend_from_slice(&0u32.to_le_bytes());
blob.extend_from_slice(&(types.len() as u32).to_le_bytes());
blob.extend_from_slice(&(types.len() as u32).to_le_bytes());
blob.extend_from_slice(&(strings.len() as u32).to_le_bytes());
blob.extend_from_slice(&types);
blob.extend_from_slice(&strings);
let btf = Btf::from_bytes(&blob).expect("parse btf");
assert_eq!(
find_datasec_btf_id(&btf, ".bss"),
Some(3),
"kind filter must skip past Int/Var to the Datasec",
);
assert_eq!(find_datasec_btf_id(&btf, ".rodata"), None);
}
#[test]
fn patch_kfunc_calls_skips_already_relocated_src_reg() {
let kf_name = "bpf_task_acquire";
let (btf_blob, _expected_func_id, _t_id) = build_kfunc_btf_blob(kf_name);
let btf = Btf::from_bytes(&btf_blob).expect("parse btf");
let mut strtab: Vec<u8> = vec![0];
let kf_str_off = strtab.len() as u32;
strtab.extend_from_slice(kf_name.as_bytes());
strtab.push(0);
let mut symtab: Vec<u8> = Vec::new();
symtab.extend_from_slice(&elf64_sym(0, 0, 0, 0, 0));
symtab.extend_from_slice(&elf64_sym(
kf_str_off,
st_info(syms::STB_GLOBAL, syms::STT_NOTYPE),
0,
0,
0,
));
let already_relocated_call: [u8; 8] = [0x85, 0x20, 0x00, 0x00, 42, 0x00, 0x00, 0x00];
let mut text: Vec<u8> = Vec::new();
text.extend_from_slice(&already_relocated_call);
text.extend_from_slice(&kfunc_exit_bytes());
let rel_data: Vec<u8> = elf64_rel(0, 1, 10).to_vec();
let blob = build_elf64(
vec![
SecSpec::new(".text", sh::SHT_PROGBITS)
.flags(sh::SHF_EXECINSTR.into())
.data(text),
SecSpec::new(".strtab", sh::SHT_STRTAB).data(strtab),
SecSpec::new(".symtab", sh::SHT_SYMTAB)
.data(symtab)
.link(2)
.entsize(24),
SecSpec::new(".rel.text", sh::SHT_REL)
.data(rel_data)
.link(3)
.info(1)
.entsize(16),
SecSpec::new(".BTF", sh::SHT_PROGBITS).data(btf_blob),
],
h::EM_BPF,
h::ET_REL,
);
let elf = goblin::elf::Elf::parse(&blob).expect("parse elf");
let mut text_concat: Vec<BpfInsn> = vec![
BpfInsn::from_le_bytes(already_relocated_call),
BpfInsn::from_le_bytes(kfunc_exit_bytes()),
];
let mut section_bases: HashMap<u32, usize> = HashMap::new();
section_bases.insert(1, 0);
assert_eq!(text_concat[0].code, 0x85);
assert_eq!(text_concat[0].src_reg(), BPF_PSEUDO_KFUNC_CALL);
assert_eq!(text_concat[0].imm, 42);
patch_kfunc_calls(&mut text_concat, &btf, &elf, §ion_bases);
assert_eq!(
text_concat[0].src_reg(),
BPF_PSEUDO_KFUNC_CALL,
"src_reg must survive unmodified",
);
assert_eq!(
text_concat[0].imm, 42,
"imm must survive unmodified — kernel BTF id preserved",
);
}
#[test]
fn build_fwd_index_indexes_single_btf_structs() {
let mut strings = vec![0u8];
let n_int = push_btf_name(&mut strings, "u64");
let n_foo = push_btf_name(&mut strings, "foo");
let n_bar = push_btf_name(&mut strings, "bar");
let n_x = push_btf_name(&mut strings, "x");
let types = vec![
SynKind::Int {
name_off: n_int,
size: 8,
encoding: 0,
offset: 0,
bits: 64,
},
SynKind::Struct {
name_off: n_foo,
size: 8,
members: vec![SynMember {
name_off: n_x,
type_id: 1,
byte_offset: 0,
}],
},
SynKind::Struct {
name_off: n_bar,
size: 8,
members: vec![SynMember {
name_off: n_x,
type_id: 1,
byte_offset: 0,
}],
},
];
let blob = build_btf_full(&types, &strings);
let btf = Arc::new(Btf::from_bytes(&blob).expect("parse btf"));
let btfs = vec![btf];
let index = build_fwd_index(&btfs);
assert_eq!(
index.get("foo"),
Some(&FwdIndexEntry {
btfs_idx: 0,
type_id: 2,
})
);
assert_eq!(
index.get("bar"),
Some(&FwdIndexEntry {
btfs_idx: 0,
type_id: 3,
})
);
assert!(!index.contains_key("u64"), "Int names must not be indexed");
}