Skip to content

Commit c084ce2

Browse files
committed
Release v0.12.0: gate OOM hook behind opt-in feature, bump version
Makes masstin compile cleanly on stable Rust while keeping the full OOM recovery hook in the published release binaries. ## Problem The EVTX carving path installs `std::alloc::set_alloc_error_hook` so that allocation failures triggered by the `evtx` crate when it parses corrupt BinXML from unallocated space become catchable panics instead of process aborts. That function is still nightly-only, so the crate attribute `#![feature(alloc_error_hook)]` on `src/lib.rs` forced the whole crate to require nightly — including a plain `cargo install masstin` from crates.io, which is not acceptable for a tool aimed at responders. ## Fix - New Cargo feature `nightly-oom-hook`, disabled by default. - `lib.rs` gates the nightly attribute with `#![cfg_attr(feature = "nightly-oom-hook", feature(alloc_error_hook))]` so stable sees no unstable-feature attribute at all. - `parse_carve.rs` has two `install_oom_hook()` definitions: - `#[cfg(feature = "nightly-oom-hook")]` — the real hook - `#[cfg(not(...))]` — a no-op stub - `.github/workflows/release.yml` switched from stable to nightly and now builds with `--features nightly-oom-hook` on all three targets, so the binaries published under GitHub Releases carry full OOM protection out of the box. - README: short build-note paragraph explaining the policy. End users downloading released binaries see a normal executable and never interact with Rust or nightly. Contributors compiling from source on stable get a fully functional masstin; only the OOM recovery in the carving path becomes a no-op, which only affects the 1% of images with pathological BinXML corruption. Version bumped from 0.11.0 to 0.12.0 — significant feature release covering parse-custom, EVTX carving hardening, and this build policy. Verified locally that both `cargo build --release` and `cargo build --release --features nightly-oom-hook` compile cleanly on the nightly toolchain currently installed.
1 parent 74d6a88 commit c084ce2

6 files changed

Lines changed: 54 additions & 11 deletions

File tree

.github/workflows/release.yml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,20 @@ jobs:
2828
steps:
2929
- uses: actions/checkout@v4
3030

31-
- name: Install Rust
32-
uses: dtolnay/rust-toolchain@stable
31+
- name: Install Rust (nightly — required by the opt-in nightly-oom-hook feature)
32+
uses: dtolnay/rust-toolchain@nightly
3333
with:
3434
targets: ${{ matrix.target }}
3535

36-
- name: Build
37-
run: cargo build --release --target ${{ matrix.target }}
36+
- name: Build with nightly-oom-hook feature enabled
37+
# The released binaries enable `nightly-oom-hook` so EVTX carving
38+
# can catch BinXML-triggered OOM aborts from the evtx crate and
39+
# continue processing the rest of the image. End users run a
40+
# normal executable — they never interact with Rust or nightly.
41+
# Contributors building from source on stable can use plain
42+
# `cargo build` without the feature; everything still works,
43+
# only the carving OOM protection becomes a no-op.
44+
run: cargo build --release --target ${{ matrix.target }} --features nightly-oom-hook
3845

3946
- name: Rename binary (Windows)
4047
if: matrix.os == 'windows-latest'

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
[package]
22
name = "masstin"
3-
version = "0.11.0"
3+
version = "0.12.0"
44
edition = "2021"
55
description = "Lateral movement tracker for anything! A DFIR tool that parses forensic artifacts and visualizes lateral movement in graph databases. Written by Toño Díaz (@jupyterjones)"
66
authors = ["Tono Diaz (@jupyterj0nes)"]
77
repository = "https://github.com/jupyterjones/masstin"
88
license = "AGPL-3.0-only"
99

10+
# Optional features
11+
#
12+
# `nightly-oom-hook` installs std::alloc::set_alloc_error_hook during EVTX
13+
# carving so allocation failures from malformed BinXML can be caught and
14+
# the offending chunk rejected, instead of aborting the whole process.
15+
# The hook is nightly-only today, so the feature is opt-in. The official
16+
# release binaries are built with this feature enabled on nightly Rust.
17+
# Plain `cargo build` on stable compiles cleanly with a no-op stub.
18+
[features]
19+
default = []
20+
nightly-oom-hook = []
21+
1022
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1123

1224
[dependencies]

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ Named after the [Mastín Leonés](https://en.wikipedia.org/wiki/Spanish_Mastiff)
5151
| **EVTX carving** | `carve-image` scans raw disk data for EVTX chunks (`ElfChnk`) in unallocated space — recovers lateral movement events even after logs AND VSS are deleted. Implements **Tier 1** (full 64 KB chunks) and **Tier 2** (orphan record detection); **Tier 3** (template matching for partially overwritten records) is planned. Builds synthetic EVTX files grouped by provider and parses them through the full pipeline. Hardened against upstream `evtx` crate bugs (infinite loops, multi-GB OOMs) via thread isolation + `alloc_error_hook`. Corrupted chunks can be skipped with `--skip-offsets`. | [EVTX carving](https://weinvestigateanything.com/en/tools/evtx-carving-unallocated/) |
5252
| **Multi-artifact parsing** | 32+ Windows Event IDs from 11 EVTX sources + Scheduled Tasks XML + MountPoints2 registry + Linux logs + Winlogbeat JSON + Cortex XDR | [Artifacts](#supported-artifacts) |
5353
| **Custom parsers (YAML)** | `parse-custom` action parses arbitrary VPN / firewall / proxy logs via YAML rule files with 3 extractor types (csv, regex, keyvalue) and nested sub-extract. Ships with 8 researched rules and 31 sub-parsers out of the box: Palo Alto GlobalProtect (5), Palo Alto TRAFFIC with User-ID filter (2), Cisco AnyConnect (4), Cisco ASA (6), Fortinet SSL VPN (3), Fortinet FortiGate (4), OpenVPN (4), Squid proxy (3). Every rule is backed by vendor official documentation — see [`rules/README.md#references`](rules/README.md#references). Full schema spec in [`docs/custom-parsers.md`](docs/custom-parsers.md). | [Custom parsers](https://weinvestigateanything.com/en/tools/masstin-custom-parsers/) |
54+
55+
> **Build note.** Core masstin builds on **stable Rust** with a plain `cargo build --release` — the default configuration does not require nightly. The EVTX carving path ships with an optional OOM-recovery hook (`nightly-oom-hook` Cargo feature) that uses `std::alloc::set_alloc_error_hook`, which is currently nightly-only. The official **pre-built release binaries** on the [Releases page](https://github.com/jupyterj0nes/masstin/releases) are compiled on nightly with this feature enabled, so end users downloading those binaries get the full OOM protection automatically — no Rust toolchain required at runtime. Contributors building from source on stable get a fully functional masstin; the OOM hook becomes a no-op stub, which only affects the 1% of forensic images with pathological BinXML corruption in carved chunks.
5456
| **Event classification** | Every event classified as `SUCCESSFUL_LOGON`, `FAILED_LOGON`, `LOGOFF` or `CONNECT` with human-readable failure reasons | [CSV format](https://weinvestigateanything.com/en/tools/masstin-csv-format/) |
5557
| **Unified timeline** | All sources merged into a single chronological CSV with 14 standardized columns | [CSV format](https://weinvestigateanything.com/en/tools/masstin-csv-format/) |
5658
| **Cross-platform timeline** | Windows EVTX + Linux SSH + EDR data in one timeline — `parse-image` auto-merges across OS boundaries, or use `merge` for manual combination | |

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
#![feature(alloc_error_hook)]
1+
// Crate-level feature gate for the optional OOM hook used by EVTX carving.
2+
// Only activated when the `nightly-oom-hook` Cargo feature is on, and only
3+
// compiles on nightly Rust. On stable (or nightly without the feature),
4+
// the gate is not emitted and masstin builds cleanly.
5+
#![cfg_attr(feature = "nightly-oom-hook", feature(alloc_error_hook))]
26

37
use clap::{Parser, ValueEnum};
48
use std::fs;

src/parse_carve.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,31 @@ const EVTX_CHUNK_SIZE: usize = 65536; // 64KB
2525
const RECORD_MAGIC: &[u8; 4] = b"\x2a\x2a\x00\x00";
2626
const SCAN_BLOCK_SIZE: usize = 4 * 1024 * 1024; // 4MB read blocks
2727

28-
/// Main entry point for carve-image action
29-
pub fn carve_image(files: &[String], output: Option<&String>, unalloc_only: bool, skip_offsets: &[u64]) {
30-
// Convert OOM aborts into panics so the isolated-thread validator can recover
31-
// when the evtx crate tries to allocate multi-GB buffers on corrupt BinXML.
28+
/// Install a global allocation error hook that converts OOM aborts into panics,
29+
/// so the isolated-thread validator below can recover when the evtx crate tries
30+
/// to allocate multi-GB buffers on corrupt BinXML.
31+
///
32+
/// On nightly with `--features nightly-oom-hook`, this is the real hook. On
33+
/// stable (or nightly without the feature) it's a no-op stub: allocation
34+
/// failures will abort the process as they would without masstin's protection.
35+
/// The official release binaries are built on nightly with the feature enabled.
36+
#[cfg(feature = "nightly-oom-hook")]
37+
fn install_oom_hook() {
3238
std::alloc::set_alloc_error_hook(|layout| {
3339
panic!("allocation of {} bytes failed (caught by masstin hook)", layout.size());
3440
});
41+
}
42+
43+
#[cfg(not(feature = "nightly-oom-hook"))]
44+
fn install_oom_hook() {
45+
// No-op on stable. carve-image still works; the catch_unwind isolation
46+
// thread recovers from panics (including OOM re-raised via other paths),
47+
// but a bare `abort()` from the allocator propagates out as before.
48+
}
49+
50+
/// Main entry point for carve-image action
51+
pub fn carve_image(files: &[String], output: Option<&String>, unalloc_only: bool, skip_offsets: &[u64]) {
52+
install_oom_hook();
3553

3654
let start_time = std::time::Instant::now();
3755

0 commit comments

Comments
 (0)