using System; using System.Collections.Generic; using System.Buffers; using System.IO; using System.Text; namespace StellaOps.Scanner.Analyzers.Lang.Go.Internal; internal static class GoBinaryScanner { private static readonly ReadOnlyMemory BuildInfoMagic = new byte[] { 0xFF, (byte)' ', (byte)'G', (byte)'o', (byte)' ', (byte)'b', (byte)'u', (byte)'i', (byte)'l', (byte)'d', (byte)'i', (byte)'n', (byte)'f', (byte)':' }; private static readonly ReadOnlyMemory BuildIdMarker = Encoding.ASCII.GetBytes("Go build ID:"); private static readonly ReadOnlyMemory GoPclnTabMarker = Encoding.ASCII.GetBytes(".gopclntab"); private static readonly ReadOnlyMemory GoVersionPrefix = Encoding.ASCII.GetBytes("go1."); public static IEnumerable EnumerateCandidateFiles(string rootPath) { var enumeration = new EnumerationOptions { RecurseSubdirectories = true, IgnoreInaccessible = true, AttributesToSkip = FileAttributes.Device | FileAttributes.ReparsePoint, MatchCasing = MatchCasing.CaseSensitive, }; foreach (var path in Directory.EnumerateFiles(rootPath, "*", enumeration)) { yield return path; } } public static bool TryReadBuildInfo(string filePath, out string? goVersion, out string? moduleData) { goVersion = null; moduleData = null; FileInfo info; try { info = new FileInfo(filePath); if (!info.Exists || info.Length < 64 || info.Length > 128 * 1024 * 1024) { return false; } } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } catch (System.Security.SecurityException) { return false; } var length = info.Length; if (length <= 0) { return false; } var inspectLength = (int)Math.Min(length, int.MaxValue); var buffer = ArrayPool.Shared.Rent(inspectLength); try { using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); var totalRead = 0; while (totalRead < inspectLength) { var read = stream.Read(buffer, totalRead, inspectLength - totalRead); if (read <= 0) { break; } totalRead += read; } if (totalRead < 64) { return false; } var span = new ReadOnlySpan(buffer, 0, totalRead); var offset = span.IndexOf(BuildInfoMagic.Span); if (offset < 0) { return false; } var view = span[offset..]; return GoBuildInfoDecoder.TryDecode(view, out goVersion, out moduleData); } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } finally { Array.Clear(buffer, 0, inspectLength); ArrayPool.Shared.Return(buffer); } } public static bool TryClassifyStrippedBinary(string filePath, out GoStrippedBinaryClassification classification) { classification = default; FileInfo fileInfo; try { fileInfo = new FileInfo(filePath); if (!fileInfo.Exists) { return false; } } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } catch (System.Security.SecurityException) { return false; } var length = fileInfo.Length; if (length < 128) { return false; } const int WindowSize = 128 * 1024; var readSize = (int)Math.Min(length, WindowSize); var buffer = ArrayPool.Shared.Rent(readSize); try { using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); var headRead = stream.Read(buffer, 0, readSize); if (headRead <= 0) { return false; } var headSpan = new ReadOnlySpan(buffer, 0, headRead); var hasBuildId = headSpan.IndexOf(BuildIdMarker.Span) >= 0; var hasPcln = headSpan.IndexOf(GoPclnTabMarker.Span) >= 0; var goVersion = ExtractGoVersion(headSpan); if (length > headRead) { var tailSize = Math.Min(readSize, (int)length); if (tailSize > 0) { stream.Seek(-tailSize, SeekOrigin.End); var tailRead = stream.Read(buffer, 0, tailSize); if (tailRead > 0) { var tailSpan = new ReadOnlySpan(buffer, 0, tailRead); hasBuildId |= tailSpan.IndexOf(BuildIdMarker.Span) >= 0; hasPcln |= tailSpan.IndexOf(GoPclnTabMarker.Span) >= 0; goVersion ??= ExtractGoVersion(tailSpan); } } } if (hasBuildId) { classification = new GoStrippedBinaryClassification( filePath, GoStrippedBinaryIndicator.BuildId, goVersion); return true; } if (hasPcln && !string.IsNullOrEmpty(goVersion)) { classification = new GoStrippedBinaryClassification( filePath, GoStrippedBinaryIndicator.GoRuntimeMarkers, goVersion); return true; } return false; } finally { Array.Clear(buffer, 0, readSize); ArrayPool.Shared.Return(buffer); } } private static string? ExtractGoVersion(ReadOnlySpan data) { var prefix = GoVersionPrefix.Span; var span = data; while (!span.IsEmpty) { var index = span.IndexOf(prefix); if (index < 0) { return null; } var absoluteIndex = data.Length - span.Length + index; if (absoluteIndex > 0) { var previous = (char)data[absoluteIndex - 1]; if (char.IsLetterOrDigit(previous)) { span = span[(index + 1)..]; continue; } } var start = absoluteIndex; var end = start + prefix.Length; while (end < data.Length && IsVersionCharacter((char)data[end])) { end++; } if (end - start <= prefix.Length) { span = span[(index + 1)..]; continue; } var candidate = data[start..end]; return Encoding.ASCII.GetString(candidate); } return null; } private static bool IsVersionCharacter(char value) => (value >= '0' && value <= '9') || (value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') || value is '.' or '-' or '+' or '_'; }