use crate::structures::PclntabVersion;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Confidence {
None,
Low,
Medium,
High,
}
impl Confidence {
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Low => "low",
Self::Medium => "medium",
Self::High => "high",
}
}
}
impl std::fmt::Display for Confidence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct ConfidenceReport {
pub tier: Confidence,
pub signals: Vec<ConfidenceSignal>,
}
impl ConfidenceReport {
pub fn empty() -> Self {
Self {
tier: Confidence::None,
signals: Vec::new(),
}
}
pub fn push(&mut self, signal: ConfidenceSignal) {
self.signals.push(signal);
}
pub fn raise_to(&mut self, tier: Confidence) {
if tier > self.tier {
self.tier = tier;
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConfidenceSignal {
GopclntabSectionPresent,
BuildinfoSectionPresent,
BuildidNotePresent,
TypeSectionPresent {
section: &'static str,
},
BuildIdMarkerFound,
BuildinfoParsed,
BuildinfoMissing {
reason: &'static str,
},
PclntabParsed {
version: PclntabVersion,
nfunc: usize,
},
PclntabMissing {
reason: &'static str,
},
GoVersionString {
version: String,
source: VersionSource,
},
HeuristicStringsMatched {
hits: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionSource {
BuildInfoBlob,
StringScan,
}
#[derive(Debug, Clone)]
pub enum ParseError {
NotAGoBinary {
report: ConfidenceReport,
},
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotAGoBinary { report } => {
write!(
f,
"not a Go binary: no Go indicators found ({} signals checked)",
report.signals.len()
)
}
}
}
}
impl std::error::Error for ParseError {}
const HEURISTIC_PATTERNS: &[&[u8]] = &[
b"runtime.main",
b"runtime.goexit",
b"runtime.mstart",
b"runtime.rt0_go",
b"fatal error: all goroutines are asleep",
b"runtime.gopanic",
b"runtime.newproc",
];
const HEURISTIC_THRESHOLD: usize = 3;
pub fn heuristic_check(data: &[u8]) -> bool {
heuristic_hits(data) >= HEURISTIC_THRESHOLD
}
pub fn heuristic_hits(data: &[u8]) -> usize {
HEURISTIC_PATTERNS
.iter()
.filter(|p| find_bytes(data, p).is_some())
.count()
}
pub(crate) fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
let (first, rest) = needle.split_first()?;
let last_start = haystack.len().checked_sub(needle.len())?;
let mut from = 0usize;
while from <= last_start {
let window = haystack.get(from..)?;
let hit = find_byte(window, *first)?;
let start = from.checked_add(hit)?;
if start > last_start {
return None;
}
let tail_start = start.checked_add(1)?;
let tail_end = tail_start.checked_add(rest.len())?;
if haystack.get(tail_start..tail_end) == Some(rest) {
return Some(start);
}
from = tail_start;
}
None
}
fn find_byte(haystack: &[u8], byte: u8) -> Option<usize> {
const LANES: usize = 8;
const LOW: u64 = 0x0101_0101_0101_0101;
const HIGH: u64 = 0x8080_8080_8080_8080;
let broadcast = u64::from_ne_bytes([byte; LANES]);
let (words, tail) = haystack.as_chunks::<LANES>();
for (i, chunk) in words.iter().enumerate() {
let x = u64::from_ne_bytes(*chunk) ^ broadcast;
if x.wrapping_sub(LOW) & !x & HIGH == 0 {
continue;
}
let base = i.checked_mul(LANES)?;
let hit = chunk.iter().position(|&b| b == byte)?;
return base.checked_add(hit);
}
let consumed = haystack.len().checked_sub(tail.len())?;
tail.iter()
.position(|&b| b == byte)
.and_then(|hit| consumed.checked_add(hit))
}