Repository navigation
Conversation
forensic-rs now defines what a memory backend provides and what analyzers, parsers and tools consume, so code written against it works on any backend. - src/memory/: Va/Pa, PageSize, Validity/Backing/ReadReport, PhysicalMemory, AddressSpace (+ AddressSpaceExt typed reads), Translation, typed records (MemOsInfo, MemProcess, MemThread, MemModule, MemRegion, MemHandle) with MemValue fields and MemRecordMeta provenance, MemoryImage (+ Ext), MemorySpec. - Integration: Mounted::Memory/MountKind::Memory, Requirement::Memory, TriageSourcesBuilder::memory, CommonArtifact::Memory(MemoryArtifact), Locus::Memory/Physical, LocatorSegment::AddressSpace, AnomalyFlags::INFERRED_LAYOUT, memory EntityKinds and EntityId constructors, process.*/dll.*/memory.* dictionary fields. - Test doubles: InMemoryPhysicalMemory, TestingAddressSpace, TestingMemoryImage, TestingFormatFactory::memory. - tests/memory_contract.rs: a backend-independent analyzer and parser factory.
read_bytes allocated vec![0; len] for any len, and lengths in memory images are attacker-controlled: a hostile size was an allocation DoS for every consumer. It now refuses lengths above MAX_READ (16 MiB) with a too_big error; larger spans go through AddressSpace::read into a caller-owned buffer. read_cstr, read_utf16 and read_utf16_counted returned a plain Known value when the bound cut the string. They now return Bounded<T>, whose truncated flag says so.
A MemoryImage returns an error for a record kind it does not provide, so that 'no handles' and 'handles not supported' never look alike. That error was ForensicError::other, which callers could not tell from a failed read. DataAccessError::Unsupported, with ForensicError::unsupported and is_unsupported (looking through context wrappers), names it. memory::unsupported(kind) uses it.
Records' provenance ids were minted in the backend's store, which a consumer of &dyn MemoryImage could not reach, so the ids were opaque. MemoryImage::provenance() now returns that store. MemRecordMeta::new defaulted validity to Resident, so a backend that forgot to compute it claimed a full read. The validity is now a required argument. MemOsInfo.live duplicated PhysicalMemory::is_live with a default of false; it is removed, and is_live is the one place liveness is stated. TestingMemoryImage holds a store (with_provenance) and the contract test resolves each record's provenance through the image.
MemRecordKind::Module stood for both kernel_modules() and modules(process), so a backend providing one claimed both and the other looked like an empty list. It is split into KernelModule and ProcessModule. os() was required although OsInfo is listed as an optional kind. It now defaults to an unsupported error like every other kind.
A page whose data lives in a mapped file (on Windows, a prototype PTE
pointing at a subsection) had no translation of its own: backends could
only report it as an unresolved prototype, losing which file and where.
Translation::FileBacked { file, offset } carries an opaque file key and
the byte offset; unresolved, it reads as Paged { MappedFile, data: false }.
A Translation::PageFile page could only be reported as Paged { data:
false }: nothing let an investigator supply pagefile.sys or swapfile.sys.
PagingFile is one such file read at the offsets translations carry, and
PagingFiles holds them by the OS's paging-file number. Reads past the end
of a truncated copy, or of a number nobody attached, are zero-filled and
reported without data. InMemoryPagingFile is the test double.
Both decoders panicked on attacker-controlled data, which memory images, hibernation files and prefetch all are. lz77 indexed past the input on a cut literal, flags word, match or length, underflowed on a back-reference before the output, and had no output bound, so the padding after a memory-compression page ran it off the end. It is rewritten with checked reads, back-references limited to this call's output, and decompress_bounded(in, out, max_out). xpress_huff read the bit stream header without checking it (inputs of 256 to 259 bytes), ran out of tree nodes on an over-subscribed table, underflowed on a bad back-reference or an overshooting match. These are errors now, and symbol decoding is bounded by the 15-bit code length. Tests cover each case and fail on the old code; a decompress fuzz target covers LZ77, Xpress Huffman and LZNT1.
Code walking lists over any AddressSpace stops at a link that is not a canonical address, and could only assume 48 bits for 8-byte pointers. A 5-level paging (LA57) space implements 57 bits, so kernel pointers above bit 47 looked like corrupt links. AddressSpace::va_bits says how many bits a space implements; it defaults to 48 for 8-byte pointers and 32 otherwise, so existing backends keep their behaviour. Va::is_canonical and Va::is_upper_half check an address against that width.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.