-
Notifications
You must be signed in to change notification settings - Fork 768
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Timeline feature work has started. * Self-update fixed for Debian. * UpdateClient flow refactored to remove AFF4 traces. * Self-update tests added. * Chipsec dependency correctly listed. * Protobuf dependency updated. * Bumped version to 3.4.0.1
- Loading branch information
Showing
60 changed files
with
2,041 additions
and
488 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
#!/usr/bin/env python | ||
"""A module with a client action for timeline collection.""" | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
|
||
from __future__ import unicode_literals | ||
|
||
import hashlib | ||
import os | ||
import stat as stat_mode | ||
|
||
from typing import Iterator | ||
|
||
from grr_response_client import actions | ||
from grr_response_core.lib import rdfvalue | ||
from grr_response_core.lib.rdfvalues import protodict as rdf_protodict | ||
from grr_response_core.lib.rdfvalues import timeline as rdf_timeline | ||
|
||
|
||
class Timeline(actions.ActionPlugin): | ||
"""A client action for timeline collection.""" | ||
|
||
in_rdfvalue = rdf_timeline.TimelineArgs | ||
out_rdfvalues = [rdf_timeline.TimelineResult] | ||
|
||
_TRANSFER_STORE_ID = rdfvalue.SessionID(flow_name="TransferStore") | ||
|
||
def Run(self, args): | ||
"""Executes the client action.""" | ||
result = rdf_timeline.TimelineResult() | ||
|
||
entries = Walk(args.root) | ||
for entry_batch in rdf_timeline.TimelineEntry.SerializeStream(entries): | ||
entry_batch_blob = rdf_protodict.DataBlob(data=entry_batch) | ||
self.SendReply(entry_batch_blob, session_id=self._TRANSFER_STORE_ID) | ||
|
||
entry_batch_blob_id = hashlib.sha256(entry_batch).digest() | ||
result.entry_batch_blob_ids.append(entry_batch_blob_id) | ||
|
||
self.Progress() | ||
|
||
self.SendReply(result) | ||
|
||
|
||
def Walk(root): | ||
"""Walks the filesystem collecting stat information. | ||
This method will recursively descend to all sub-folders and sub-sub-folders | ||
and so on. It will stop the recursion at device boundaries and will not follow | ||
any symlinks (to avoid cycles and virtual filesystems that may be potentially | ||
infinite). | ||
Args: | ||
root: A path to the root folder at which the recursion should start. | ||
Returns: | ||
An iterator over timeline entries with stat information about each file. | ||
""" | ||
try: | ||
dev = os.lstat(root).st_dev | ||
except OSError: | ||
return iter([]) | ||
|
||
def Recurse(path): | ||
"""Performs the recursive walk over the file hierarchy.""" | ||
try: | ||
stat = os.lstat(path) | ||
except OSError: | ||
return | ||
|
||
yield rdf_timeline.TimelineEntry.FromStat(path, stat) | ||
|
||
# We want to recurse only to folders on the same device. | ||
if not stat_mode.S_ISDIR(stat.st_mode) or stat.st_dev != dev: | ||
return | ||
|
||
try: | ||
childnames = os.listdir(path) | ||
except OSError: | ||
childnames = [] | ||
|
||
# TODO(hanuszczak): Implement more efficient auto-batcher instead of having | ||
# multi-level iterators. | ||
for childname in childnames: | ||
for entry in Recurse(os.path.join(path, childname)): | ||
yield entry | ||
|
||
return Recurse(root) |
156 changes: 156 additions & 0 deletions
156
grr/client/grr_response_client/client_actions/timeline_test.py
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
#!/usr/bin/env python | ||
from __future__ import absolute_import | ||
from __future__ import division | ||
|
||
from __future__ import unicode_literals | ||
|
||
import hashlib | ||
import io | ||
import os | ||
import platform | ||
import random | ||
import stat as stat_mode | ||
|
||
from absl.testing import absltest | ||
from typing import Text | ||
|
||
from grr_response_client.client_actions import timeline | ||
from grr_response_core.lib.rdfvalues import timeline as rdf_timeline | ||
from grr_response_core.lib.util import temp | ||
from grr.test_lib import client_test_lib | ||
from grr.test_lib import skip | ||
from grr.test_lib import testing_startup | ||
|
||
|
||
# TODO(hanuszczak): `GRRBaseTest` is terrible, try to avoid it in any new code. | ||
class TimelineTest(client_test_lib.EmptyActionTest): | ||
|
||
@classmethod | ||
def setUpClass(cls): | ||
super(TimelineTest, cls).setUpClass() | ||
testing_startup.TestInit() | ||
|
||
def testRun(self): | ||
with temp.AutoTempDirPath(remove_non_empty=True) as temp_dirpath: | ||
for idx in range(64): | ||
temp_filepath = os.path.join(temp_dirpath, "foo{}".format(idx)) | ||
_Touch(temp_filepath, content=os.urandom(random.randint(0, 1024))) | ||
|
||
args = rdf_timeline.TimelineArgs() | ||
args.root = temp_dirpath.encode("utf-8") | ||
|
||
results = self.RunAction(timeline.Timeline, args) | ||
|
||
self.assertNotEmpty(results) | ||
self.assertNotEmpty(results[-1].entry_batch_blob_ids) | ||
|
||
blob_ids = results[-1].entry_batch_blob_ids | ||
for blob in results[:-1]: | ||
self.assertIn(hashlib.sha256(blob.data).digest(), blob_ids) | ||
|
||
|
||
class WalkTest(absltest.TestCase): | ||
|
||
def testSingleFile(self): | ||
with temp.AutoTempDirPath(remove_non_empty=True) as dirpath: | ||
filepath = os.path.join(dirpath, "foo") | ||
_Touch(filepath, content=b"foobar") | ||
|
||
entries = list(timeline.Walk(dirpath.encode("utf-8"))) | ||
self.assertLen(entries, 2) | ||
|
||
self.assertTrue(stat_mode.S_ISDIR(entries[0].mode)) | ||
self.assertEqual(entries[0].path, dirpath.encode("utf-8")) | ||
|
||
self.assertTrue(stat_mode.S_ISREG(entries[1].mode)) | ||
self.assertEqual(entries[1].path, filepath.encode("utf-8")) | ||
self.assertEqual(entries[1].size, 6) | ||
|
||
def testMultipleFiles(self): | ||
with temp.AutoTempDirPath(remove_non_empty=True) as dirpath: | ||
foo_filepath = os.path.join(dirpath, "foo") | ||
bar_filepath = os.path.join(dirpath, "bar") | ||
baz_filepath = os.path.join(dirpath, "baz") | ||
|
||
_Touch(foo_filepath) | ||
_Touch(bar_filepath) | ||
_Touch(baz_filepath) | ||
|
||
entries = list(timeline.Walk(dirpath.encode("utf-8"))) | ||
self.assertLen(entries, 4) | ||
|
||
paths = [_.path for _ in entries[1:]] | ||
self.assertIn(foo_filepath.encode("utf-8"), paths) | ||
self.assertIn(bar_filepath.encode("utf-8"), paths) | ||
self.assertIn(baz_filepath.encode("utf-8"), paths) | ||
|
||
def testNestedDirectories(self): | ||
with temp.AutoTempDirPath(remove_non_empty=True) as root_dirpath: | ||
foobar_dirpath = os.path.join(root_dirpath, "foo", "bar") | ||
os.makedirs(foobar_dirpath) | ||
|
||
foobaz_dirpath = os.path.join(root_dirpath, "foo", "baz") | ||
os.makedirs(foobaz_dirpath) | ||
|
||
quuxnorfthud_dirpath = os.path.join(root_dirpath, "quux", "norf", "thud") | ||
os.makedirs(quuxnorfthud_dirpath) | ||
|
||
entries = list(timeline.Walk(root_dirpath.encode("utf-8"))) | ||
self.assertLen(entries, 7) | ||
|
||
paths = [_.path.decode("utf-8") for _ in entries] | ||
self.assertCountEqual(paths, [ | ||
os.path.join(root_dirpath), | ||
os.path.join(root_dirpath, "foo"), | ||
os.path.join(root_dirpath, "foo", "bar"), | ||
os.path.join(root_dirpath, "foo", "baz"), | ||
os.path.join(root_dirpath, "quux"), | ||
os.path.join(root_dirpath, "quux", "norf"), | ||
os.path.join(root_dirpath, "quux", "norf", "thud"), | ||
]) | ||
|
||
for entry in entries: | ||
self.assertTrue(stat_mode.S_ISDIR(entry.mode)) | ||
|
||
@skip.If( | ||
platform.system() == "Windows", | ||
reason="Symlinks are not supported on Windows.") | ||
def testSymlinks(self): | ||
with temp.AutoTempDirPath(remove_non_empty=True) as root_dirpath: | ||
sub_dirpath = os.path.join(root_dirpath, "foo", "bar", "baz") | ||
link_path = os.path.join(sub_dirpath, "quux") | ||
|
||
# This creates a cycle, walker should be able to cope with that. | ||
os.makedirs(sub_dirpath) | ||
os.symlink(root_dirpath, os.path.join(sub_dirpath, link_path)) | ||
|
||
entries = list(timeline.Walk(root_dirpath.encode("utf-8"))) | ||
self.assertLen(entries, 5) | ||
|
||
paths = [_.path.decode("utf-8") for _ in entries] | ||
self.assertEqual(paths, [ | ||
os.path.join(root_dirpath), | ||
os.path.join(root_dirpath, "foo"), | ||
os.path.join(root_dirpath, "foo", "bar"), | ||
os.path.join(root_dirpath, "foo", "bar", "baz"), | ||
os.path.join(root_dirpath, "foo", "bar", "baz", "quux") | ||
]) | ||
|
||
for entry in entries[:-1]: | ||
self.assertTrue(stat_mode.S_ISDIR(entry.mode)) | ||
self.assertTrue(stat_mode.S_ISLNK(entries[-1].mode)) | ||
|
||
def testIncorrectPath(self): | ||
not_existing_path = os.path.join("some", "not", "existing", "path") | ||
|
||
entries = list(timeline.Walk(not_existing_path.encode("utf-8"))) | ||
self.assertEmpty(entries) | ||
|
||
|
||
def _Touch(filepath, content = b""): | ||
with io.open(filepath, mode="wb") as filedesc: | ||
filedesc.write(content) | ||
|
||
|
||
if __name__ == "__main__": | ||
absltest.main() |
This file contains 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
This file contains 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
Oops, something went wrong.