use crate::{
detection::find_bytes,
formats::{BinaryContext, BinaryFormat},
structures::util::{align_up, slice_at},
};
const BUILD_ID_PREFIX: &[u8] = b"\xff Go build ID: \"";
const BUILD_ID_SUFFIX: &[u8] = b"\"\n \xff";
const ELF_GO_NOTE_NAME: &[u8] = b"Go\x00\x00";
const ELF_NOTE_GOBUILDID_TAG: u32 = 4;
const RAW_SEARCH_LIMIT: usize = 65536;
pub fn extract<'a>(ctx: &BinaryContext<'a>) -> Option<&'a str> {
match ctx.format() {
BinaryFormat::Elf => extract_elf_note(ctx).or_else(|| extract_raw_marker(ctx.data())),
_ => extract_raw_marker(ctx.data()),
}
}
fn extract_raw_marker(data: &[u8]) -> Option<&str> {
let search_end = data.len().min(RAW_SEARCH_LIMIT);
let head = data.get(..search_end)?;
let prefix_pos = find_bytes(head, BUILD_ID_PREFIX)?;
let id_start = prefix_pos.checked_add(BUILD_ID_PREFIX.len())?;
let window_end = id_start.checked_add(1024)?.min(data.len());
let search_window = data.get(id_start..window_end)?;
let suffix_pos = find_bytes(search_window, BUILD_ID_SUFFIX)?;
let id_end = id_start.checked_add(suffix_pos)?;
let bytes = data.get(id_start..id_end)?;
std::str::from_utf8(bytes).ok()
}
fn extract_elf_note<'a>(ctx: &BinaryContext<'a>) -> Option<&'a str> {
for note_data in ctx.elf_note_segments() {
if let Some(id) = parse_go_note(note_data) {
return Some(id);
}
}
None
}
fn parse_go_note(data: &[u8]) -> Option<&str> {
let mut pos: usize = 0;
loop {
let header_end = pos.checked_add(12)?;
if header_end > data.len() {
return None;
}
let namesz = u32::from_le_bytes(slice_at::<4>(data, pos)?) as usize;
let descsz_off = pos.checked_add(4)?;
let descsz = u32::from_le_bytes(slice_at::<4>(data, descsz_off)?) as usize;
let type_off = pos.checked_add(8)?;
let note_type = u32::from_le_bytes(slice_at::<4>(data, type_off)?);
let name_start = header_end;
let name_padded = align_up(namesz, 4)?;
let desc_start = name_start.checked_add(name_padded)?;
let desc_padded = align_up(descsz, 4)?;
let desc_end = desc_start.checked_add(descsz)?;
if desc_end > data.len() {
return None;
}
if namesz == 4
&& note_type == ELF_NOTE_GOBUILDID_TAG
&& data.get(name_start..name_start.checked_add(4)?) == Some(ELF_GO_NOTE_NAME)
{
let bytes = data.get(desc_start..desc_end)?;
return std::str::from_utf8(bytes).ok();
}
pos = desc_start.checked_add(desc_padded)?;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_raw_marker() {
let mut data = vec![0u8; 0x1000];
let marker = b"\xff Go build ID: \"abc123/def456\"\n \xff";
data[0..marker.len()].copy_from_slice(marker);
let id = extract_raw_marker(&data).unwrap();
assert_eq!(id, "abc123/def456");
}
#[test]
fn test_raw_marker_not_found() {
let data = vec![0u8; 0x1000];
assert!(extract_raw_marker(&data).is_none());
}
}