Skip to content

Commit 9a14131

Browse files
committed
fill gaps on write
1 parent 0f0301d commit 9a14131

1 file changed

Lines changed: 65 additions & 10 deletions

File tree

src/crypto/file.rs

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ impl<T: FileExt> WriteAt for T {
2222
}
2323
}
2424

25+
/// Trait for querying file length.
26+
/// Needed to detect writes past EOF that would create sparse holes.
27+
pub trait FileLen {
28+
fn file_len(&self) -> io::Result<u64>;
29+
}
30+
31+
impl FileLen for std::fs::File {
32+
fn file_len(&self) -> io::Result<u64> {
33+
self.metadata().map(|m| m.len())
34+
}
35+
}
36+
2537
/// Decodes encrypted files.
2638
///
2739
/// Handles block-by-block decryption and MAC verification.
@@ -217,7 +229,7 @@ impl<'a, F: ReadAt> FileDecoder<'a, F> {
217229
}
218230

219231
/// Encodes encrypted files (writes).
220-
pub struct FileEncoder<'a, F: ReadAt + WriteAt> {
232+
pub struct FileEncoder<'a, F: ReadAt + WriteAt + FileLen> {
221233
cipher: &'a SslCipher,
222234
file: &'a F,
223235
file_iv: u64,
@@ -226,7 +238,7 @@ pub struct FileEncoder<'a, F: ReadAt + WriteAt> {
226238
block_mac_bytes: u64,
227239
}
228240

229-
impl<'a, F: ReadAt + WriteAt> FileEncoder<'a, F> {
241+
impl<'a, F: ReadAt + WriteAt + FileLen> FileEncoder<'a, F> {
230242
pub fn new(
231243
cipher: &'a SslCipher,
232244
file: &'a F,
@@ -273,14 +285,6 @@ impl<'a, F: ReadAt + WriteAt> FileEncoder<'a, F> {
273285
}
274286

275287
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
276-
let size = buf.len() as u64;
277-
let mut bytes_remaining = size;
278-
let mut total_written = 0;
279-
let mut current_offset = offset;
280-
281-
// Usage in `FileDecoder::read_at`: `self.block_size - self.block_mac_bytes`.
282-
// I will follow `FileDecoder` convention here to avoid breaking things, assuming `self.block_size` = ON DISK SIZE.
283-
284288
if self.block_size <= self.block_mac_bytes {
285289
return Err(io::Error::other(
286290
"Invalid config: block_size must be > block_mac_bytes",
@@ -299,6 +303,57 @@ impl<'a, F: ReadAt + WriteAt> FileEncoder<'a, F> {
299303
));
300304
}
301305

306+
// Detect and fill gaps to prevent sparse files.
307+
// Sparse files cause MAC verification failures because reading a block
308+
// that partially spans a hole returns zeros that weren't properly encrypted.
309+
let physical_size = self.file.file_len()?;
310+
let current_logical_size = FileDecoder::<F>::calculate_logical_size(
311+
physical_size,
312+
self.header_size,
313+
self.block_size,
314+
self.block_mac_bytes,
315+
);
316+
317+
if offset > current_logical_size {
318+
// Fill the gap with encrypted zeros
319+
let gap_size = offset - current_logical_size;
320+
let zeros = vec![0u8; gap_size as usize];
321+
// Recursively call write_at to fill the gap with properly encrypted zeros
322+
self.write_at_internal(
323+
&zeros,
324+
current_logical_size,
325+
data_block_size,
326+
physical_block_size,
327+
physical_block_size_usize,
328+
mac_len_usize,
329+
)?;
330+
}
331+
332+
// Now write the actual data
333+
self.write_at_internal(
334+
buf,
335+
offset,
336+
data_block_size,
337+
physical_block_size,
338+
physical_block_size_usize,
339+
mac_len_usize,
340+
)
341+
}
342+
343+
fn write_at_internal(
344+
&self,
345+
buf: &[u8],
346+
offset: u64,
347+
data_block_size: u64,
348+
physical_block_size: u64,
349+
physical_block_size_usize: usize,
350+
mac_len_usize: usize,
351+
) -> io::Result<usize> {
352+
let size = buf.len() as u64;
353+
let mut bytes_remaining = size;
354+
let mut total_written = 0;
355+
let mut current_offset = offset;
356+
302357
while bytes_remaining > 0 {
303358
let block_num = current_offset / data_block_size;
304359
let block_offset = current_offset % data_block_size;

0 commit comments

Comments
 (0)