Skip to content

Commit 2489186

Browse files
authored
Implement the get_file_sha256 action.
1 parent 2d6c462 commit 2489186

File tree

8 files changed

+304
-81
lines changed

8 files changed

+304
-81
lines changed

crates/rrg-proto/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const PROTOS: &'static [&'static str] = &[
1616
"../../proto/rrg/winreg.proto",
1717
"../../proto/rrg/action/execute_signed_command.proto",
1818
"../../proto/rrg/action/get_file_contents.proto",
19-
"../../proto/rrg/action/get_file_hash.proto",
19+
"../../proto/rrg/action/get_file_sha256.proto",
2020
"../../proto/rrg/action/get_file_metadata.proto",
2121
"../../proto/rrg/action/get_filesystem_timeline.proto",
2222
"../../proto/rrg/action/get_filesystem_timeline_tsk.proto",

crates/rrg/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ default = [
1313
"action-get_file_metadata-sha1",
1414
"action-get_file_metadata-sha256",
1515
"action-get_file_contents",
16+
"action-get_file_sha256",
1617
"action-grep_file_contents",
1718
"action-get_filesystem_timeline",
1819
"action-get_tcp_response",
@@ -34,6 +35,7 @@ action-get_file_metadata-md5 = ["action-get_file_metadata", "dep:md-5"]
3435
action-get_file_metadata-sha1 = ["action-get_file_metadata", "dep:sha1"]
3536
action-get_file_metadata-sha256 = ["action-get_file_metadata", "dep:sha2"]
3637
action-get_file_contents = ["dep:sha2"]
38+
action-get_file_sha256 = ["dep:sha2"]
3739
action-grep_file_contents = []
3840
action-get_filesystem_timeline = ["dep:flate2", "dep:sha2"]
3941
action-get_filesystem_timeline_tsk = ["action-get_filesystem_timeline", "dep:tsk"]

crates/rrg/src/action.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ pub mod get_file_metadata;
2424
#[cfg(feature = "action-get_file_contents")]
2525
pub mod get_file_contents;
2626

27+
#[cfg(feature = "action-get_file_sha256")]
28+
pub mod get_file_sha256;
29+
2730
#[cfg(feature = "action-grep_file_contents")]
2831
pub mod grep_file_contents;
2932

@@ -104,6 +107,10 @@ where
104107
GetFileContents => {
105108
handle(session, request, self::get_file_contents::handle)
106109
}
110+
#[cfg(feature = "action-get_file_sha256")]
111+
GetFileSha256 => {
112+
handle(session, request, self::get_file_sha256::handle)
113+
}
107114
#[cfg(feature = "action-grep_file_contents")]
108115
GrepFileContents => {
109116
handle(session, request, self::grep_file_contents::handle)
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Use of this source code is governed by an MIT-style license that can be found
4+
// in the LICENSE file or at https://opensource.org/licenses/MIT.
5+
use std::path::PathBuf;
6+
7+
/// Arguments of the `get_file_sha256` action.
8+
pub struct Args {
9+
/// Absolute path to the file to get the SHA-256 hash of.
10+
path: PathBuf,
11+
/// Byte offset from which the content should be hashed.
12+
offset: u64,
13+
/// Number of bytes to hash (from the start offset).
14+
len: Option<std::num::NonZero<u64>>,
15+
}
16+
17+
/// Result of the `get_file_sha256` action.
18+
struct Item {
19+
/// Absolute path of the file this result corresponds to.
20+
path: PathBuf,
21+
/// Byte offset from which the file content was hashed.
22+
offset: u64,
23+
/// Number of bytes of the file used to produce the hash.
24+
len: u64,
25+
/// SHA-256 hash digest of the file content.
26+
sha256: [u8; 32],
27+
}
28+
29+
/// Handle invocations of the `get_file_sha256` action.
30+
pub fn handle<S>(session: &mut S, args: Args) -> crate::session::Result<()>
31+
where
32+
S: crate::session::Session,
33+
{
34+
use std::io::{BufRead as _, Read as _, Seek as _};
35+
36+
let file = std::fs::File::open(&args.path)
37+
.map_err(crate::session::Error::action)?;
38+
let mut file = std::io::BufReader::new(file);
39+
40+
file.seek(std::io::SeekFrom::Start(args.offset))
41+
.map_err(crate::session::Error::action)?;
42+
43+
let mut file = file.take(match args.len {
44+
Some(len) => u64::from(len),
45+
None => u64::MAX,
46+
});
47+
48+
use sha2::Digest as _;
49+
let mut sha256 = sha2::Sha256::new();
50+
loop {
51+
let buf = match file.fill_buf() {
52+
Ok(buf) if buf.is_empty() => break,
53+
Ok(buf) => buf,
54+
Err(error) => return Err(crate::session::Error::action(error)),
55+
};
56+
sha256.update(&buf[..]);
57+
58+
let buf_len = buf.len();
59+
file.consume(buf_len);
60+
}
61+
let sha256 = <[u8; 32]>::from(sha256.finalize());
62+
63+
let len = file.stream_position()
64+
.map_err(crate::session::Error::action)?;
65+
66+
session.reply(Item {
67+
path: args.path,
68+
offset: args.offset,
69+
len,
70+
sha256,
71+
})?;
72+
73+
Ok(())
74+
}
75+
76+
impl crate::request::Args for Args {
77+
78+
type Proto = rrg_proto::get_file_sha256::Args;
79+
80+
fn from_proto(mut proto: Self::Proto) -> Result<Args, crate::request::ParseArgsError> {
81+
use crate::request::ParseArgsError;
82+
83+
let path = PathBuf::try_from(proto.take_path())
84+
.map_err(|error| ParseArgsError::invalid_field("path", error))?;
85+
86+
Ok(Args {
87+
path,
88+
offset: proto.offset(),
89+
len: std::num::NonZero::new(proto.length()),
90+
})
91+
}
92+
}
93+
94+
impl crate::response::Item for Item {
95+
96+
type Proto = rrg_proto::get_file_sha256::Result;
97+
98+
fn into_proto(self) -> Self::Proto {
99+
let mut proto = rrg_proto::get_file_sha256::Result::new();
100+
proto.set_path(self.path.into());
101+
proto.set_offset(self.offset);
102+
proto.set_length(self.len);
103+
proto.set_sha256(self.sha256.to_vec());
104+
105+
proto
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
mod tests {
111+
112+
use super::*;
113+
114+
#[test]
115+
fn handle_default() {
116+
let mut tempfile = tempfile::NamedTempFile::new()
117+
.unwrap();
118+
119+
use std::io::Write as _;
120+
tempfile.as_file_mut().write_all(b"hello\n")
121+
.unwrap();
122+
123+
let args = Args {
124+
path: tempfile.path().to_path_buf(),
125+
offset: 0,
126+
len: None,
127+
};
128+
129+
let mut session = crate::session::FakeSession::new();
130+
assert!(handle(&mut session, args).is_ok());
131+
132+
assert_eq!(session.reply_count(), 1);
133+
134+
let item = session.reply::<Item>(0);
135+
assert_eq!(item.path, tempfile.path());
136+
assert_eq!(item.offset, 0);
137+
assert_eq!(item.len, u64::try_from(b"hello\n".len()).unwrap());
138+
assert_eq!(item.sha256, [
139+
// Pre-computed by the `sha256sum` tool.
140+
0x58, 0x91, 0xb5, 0xb5, 0x22, 0xd5, 0xdf, 0x08,
141+
0x6d, 0x0f, 0xf0, 0xb1, 0x10, 0xfb, 0xd9, 0xd2,
142+
0x1b, 0xb4, 0xfc, 0x71, 0x63, 0xaf, 0x34, 0xd0,
143+
0x82, 0x86, 0xa2, 0xe8, 0x46, 0xf6, 0xbe, 0x03,
144+
]);
145+
}
146+
147+
#[test]
148+
fn handle_offset() {
149+
let mut tempfile = tempfile::NamedTempFile::new()
150+
.unwrap();
151+
152+
use std::io::Write as _;
153+
tempfile.as_file_mut().write_all(b"<ignore me>hello\n")
154+
.unwrap();
155+
156+
let args = Args {
157+
path: tempfile.path().to_path_buf(),
158+
offset: u64::try_from("<ignore me>".len()).unwrap(),
159+
len: None,
160+
};
161+
162+
let mut session = crate::session::FakeSession::new();
163+
assert!(handle(&mut session, args).is_ok());
164+
165+
assert_eq!(session.reply_count(), 1);
166+
167+
let item = session.reply::<Item>(0);
168+
assert_eq!(item.path, tempfile.path());
169+
assert_eq!(item.offset, u64::try_from(b"<ignore me>".len()).unwrap());
170+
assert_eq!(item.len, u64::try_from(b"hello\n".len()).unwrap());
171+
assert_eq!(item.sha256, [
172+
// Pre-computed by the `sha256sum` tool.
173+
0x58, 0x91, 0xb5, 0xb5, 0x22, 0xd5, 0xdf, 0x08,
174+
0x6d, 0x0f, 0xf0, 0xb1, 0x10, 0xfb, 0xd9, 0xd2,
175+
0x1b, 0xb4, 0xfc, 0x71, 0x63, 0xaf, 0x34, 0xd0,
176+
0x82, 0x86, 0xa2, 0xe8, 0x46, 0xf6, 0xbe, 0x03,
177+
]);
178+
}
179+
180+
#[test]
181+
fn handle_len() {
182+
let mut tempfile = tempfile::NamedTempFile::new()
183+
.unwrap();
184+
185+
use std::io::Write as _;
186+
tempfile.as_file_mut().write_all(b"hello\n<ignore me>")
187+
.unwrap();
188+
189+
let args = Args {
190+
path: tempfile.path().to_path_buf(),
191+
offset: 0,
192+
len: std::num::NonZero::new(b"hello\n".len().try_into().unwrap()),
193+
};
194+
195+
let mut session = crate::session::FakeSession::new();
196+
assert!(handle(&mut session, args).is_ok());
197+
198+
assert_eq!(session.reply_count(), 1);
199+
200+
let item = session.reply::<Item>(0);
201+
assert_eq!(item.path, tempfile.path());
202+
assert_eq!(item.offset, 0);
203+
assert_eq!(item.len, u64::try_from(b"hello\n".len()).unwrap());
204+
assert_eq!(item.sha256, [
205+
// Pre-computed by the `sha256sum` tool.
206+
0x58, 0x91, 0xb5, 0xb5, 0x22, 0xd5, 0xdf, 0x08,
207+
0x6d, 0x0f, 0xf0, 0xb1, 0x10, 0xfb, 0xd9, 0xd2,
208+
0x1b, 0xb4, 0xfc, 0x71, 0x63, 0xaf, 0x34, 0xd0,
209+
0x82, 0x86, 0xa2, 0xe8, 0x46, 0xf6, 0xbe, 0x03,
210+
]);
211+
}
212+
213+
#[test]
214+
fn handle_large() {
215+
let mut tempfile = tempfile::NamedTempFile::new()
216+
.unwrap();
217+
218+
use std::io::Read as _;
219+
std::io::copy(&mut std::io::repeat(0).take(13371337), &mut tempfile)
220+
.unwrap();
221+
222+
let args = Args {
223+
path: tempfile.path().to_path_buf(),
224+
offset: 0,
225+
len: None,
226+
};
227+
228+
let mut session = crate::session::FakeSession::new();
229+
assert!(handle(&mut session, args).is_ok());
230+
231+
assert_eq!(session.reply_count(), 1);
232+
233+
let item = session.reply::<Item>(0);
234+
assert_eq!(item.path, tempfile.path());
235+
assert_eq!(item.offset, 0);
236+
assert_eq!(item.len, 13371337);
237+
assert_eq!(item.sha256, [
238+
// Pre-computed by `head --bytes=13371337 < /dev/zero | sha256sum`.
239+
0xda, 0xa6, 0x04, 0x11, 0x35, 0x03, 0xdb, 0x38,
240+
0xe3, 0x62, 0xfe, 0xff, 0x8f, 0x73, 0xc1, 0xf9,
241+
0xb2, 0x6f, 0x02, 0x85, 0x3d, 0x2f, 0x47, 0x8d,
242+
0x52, 0x16, 0xc5, 0x70, 0x32, 0x54, 0x1c, 0xf8,
243+
]);
244+
}
245+
}

crates/rrg/src/request.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ pub enum Action {
1919
GetFileMetadata,
2020
/// Get contents of the specified file.
2121
GetFileContents,
22-
/// Get hash of the specified file.
23-
GetFileHash,
22+
/// Get SHA-256 hash of the specified file.
23+
GetFileSha256,
2424
/// Grep the specified file for a pattern.
2525
GrepFileContents,
2626
/// List contents of a directory.
@@ -64,7 +64,7 @@ impl std::fmt::Display for Action {
6464
Action::GetSystemMetadata => write!(fmt, "get_system_metadata"),
6565
Action::GetFileMetadata => write!(fmt, "get_file_metadata"),
6666
Action::GetFileContents => write!(fmt, "get_file_contents"),
67-
Action::GetFileHash => write!(fmt, "get_file_hash"),
67+
Action::GetFileSha256 => write!(fmt, "get_file_sha256"),
6868
Action::GrepFileContents => write!(fmt, "grep_file_contents"),
6969
Action::ListDirectory => write!(fmt, "list_directory"),
7070
Action::ListProcesses => write!(fmt, "list_processes"),
@@ -117,7 +117,7 @@ impl TryFrom<rrg_proto::rrg::Action> for Action {
117117
GET_SYSTEM_METADATA => Ok(Action::GetSystemMetadata),
118118
GET_FILE_METADATA => Ok(Action::GetFileMetadata),
119119
GET_FILE_CONTENTS => Ok(Action::GetFileContents),
120-
GET_FILE_HASH => Ok(Action::GetFileHash),
120+
GET_FILE_SHA256 => Ok(Action::GetFileSha256),
121121
GREP_FILE_CONTENTS => Ok(Action::GrepFileContents),
122122
LIST_DIRECTORY => Ok(Action::ListDirectory),
123123
LIST_PROCESSES => Ok(Action::ListProcesses),

proto/rrg.proto

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ enum Action {
2020
GET_FILE_METADATA = 2;
2121
// Get contents of the specified file.
2222
GET_FILE_CONTENTS = 3;
23-
// Get hash of the specified file.
24-
GET_FILE_HASH = 4;
23+
// Get SHA-256 hash of the specified file.
24+
GET_FILE_SHA256 = 4;
2525
// List contents of a directory.
2626
LIST_DIRECTORY = 5;
2727
// List processes available on the system.

0 commit comments

Comments
 (0)