-
Notifications
You must be signed in to change notification settings - Fork 669
Linux: Optimize recoverfs, unify banner scanning and fix memory leaks #1976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
6436cfc
7304bf4
595f7ca
15a676e
a239339
febbe7e
0cb240d
f6b7222
09843ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| from typing import Iterator, Optional, Tuple | ||
| from volatility3.framework.layers import scanners | ||
|
|
||
| VALID_BANNER_CHARSET = ( | ||
| b" #()+,;/-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~\n" | ||
| ) | ||
| BANNER_READ_SIZE = 0xFFF | ||
|
|
||
|
|
||
| class BannerScanner(scanners.RegExScanner): | ||
| """Scanner for Linux and macOS kernel version strings.""" | ||
|
|
||
| BANNER_PATTERN = ( | ||
| rb"(Linux version|Darwin Kernel Version) [0-9]+\.[0-9]+\.[0-9]+[^\x00]+" | ||
| ) | ||
|
|
||
| _version = (1, 0, 0) | ||
|
|
||
| _required_framework_version = (2, 0, 0) | ||
|
|
||
| def __init__(self) -> None: | ||
| super().__init__(pattern=self.BANNER_PATTERN) | ||
|
|
||
| def _get_valid_banner(self, offset: int) -> Optional[bytes]: | ||
| """Gets the banner at a layer offset and validates it.""" | ||
| layer = self.context.layers[self.layer_name] | ||
| data = layer.read(offset, BANNER_READ_SIZE, pad=True) | ||
| # See symbol_cache's _normalize_identifier | ||
| data_index = data.find(b"\x00") | ||
| if data_index <= 0: | ||
| return None | ||
|
|
||
| data = data[:data_index].strip() | ||
| failed = any(char not in VALID_BANNER_CHARSET for char in data) | ||
| if not failed: | ||
| return data | ||
|
|
||
| return None | ||
|
|
||
| def __call__(self, data: bytes, data_offset: int) -> Iterator[Tuple[int, bytes]]: | ||
| for off in super().__call__(data, data_offset): | ||
| banner = self._get_valid_banner(off) | ||
| if banner is not None: | ||
| yield off, banner | ||
|
|
||
|
|
||
| class LinuxBannerScanner(BannerScanner): | ||
| BANNER_PATTERN = rb"Linux version [0-9]+\.[0-9]+\.[0-9]+[^\x00]+" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reasonable to put them in here, but only because they're the only thing differentiating the class from the base class, and making the user go hunt in the constants file would be a lot. Still concerned that the pattern isn't accurate enough, but since we're only using it for the banners plugin, I'm ok with that. |
||
|
|
||
|
|
||
| class MacBannerScanner(BannerScanner): | ||
| BANNER_PATTERN = rb"Darwin Kernel Version [0-9]+\.[0-9]+\.[0-9]+[^\x00]+" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,7 +79,7 @@ def __getitem__(self, key): | |
| raise KeyError(f"No {key} present in ObjectInformation") | ||
|
|
||
| def __contains__(self, key): | ||
| return key in [field.name for field in dataclasses.fields(self)] | ||
| return any(field.name == key for field in dataclasses.fields(self)) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change also looks like it's unrelated. Should be fine, but why are we making it? Does it provide a signficant optimization? What's the value in this change specifically? Given it's in pretty fundamental code, I'd want there to at least some small speed improvement if we're bothering to change it... |
||
|
|
||
|
|
||
| class ObjectInterface(metaclass=abc.ABCMeta): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -61,14 +61,14 @@ def __call__(self, data: bytes, data_offset: int) -> Generator[int, None, None]: | |
| class MultiStringScanner(layers.ScannerInterface): | ||
| thread_safe = True | ||
|
|
||
| _version = (1, 0, 0) | ||
| _version = (1, 0, 1) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be 1.1.0, rather than 1.0.1, since it's an addition to the way the plugin is usable. The third digit isn't used in version checks, so one that needed the extra param couldn't specify that it required it. |
||
| _required_framework_version = (2, 0, 0) | ||
|
|
||
| def __init__(self, patterns: List[bytes]) -> None: | ||
| def __init__(self, patterns: List[bytes], max_depth: int = None) -> None: | ||
| super().__init__() | ||
| self._pattern_trie: Optional[Dict[int, Optional[Dict]]] = {} | ||
| for pattern in patterns: | ||
| self._process_pattern(pattern) | ||
| self._process_pattern(pattern[: max_depth or len(pattern)]) | ||
| self._regex = self._process_trie(self._pattern_trie) | ||
|
|
||
| def _process_pattern(self, value: bytes) -> None: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1615,7 +1615,9 @@ def to_list( | |
| vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) | ||
|
|
||
| current = self.first | ||
| while current and current.is_readable(): | ||
| seen = set() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sneaking other changes into a PR isn't really good practice. It'd be much better to put this in as a small targetted PR, with its own description to describe what the change is and what it does, rather than tacking it into a large banner rework. Fine this time, but please avoid it in the future... |
||
| while current and current.is_readable() and current.vol.offset not in seen: | ||
| seen.add(current.vol.offset) | ||
| yield linux.LinuxUtilities.container_of( | ||
| current, symbol_type, member, vmlinux | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels like it should be in constants.linux, rather than in this file? I might also consider using
string.printable - ["\t\n\ra\x0b\x0c"]or similar...