mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-29 09:38:59 +00:00
fix(filemeta): reject positive size without parts (#5354)
* fix(filemeta): reject positive size without parts * test(ecstore): keep optimized read fixture valid * test(ecstore): keep listing fixtures valid
This commit is contained in:
@@ -8500,6 +8500,12 @@ mod test {
|
||||
file_info.data_dir = data_dir;
|
||||
file_info.data = data;
|
||||
file_info.size = size;
|
||||
file_info.parts = vec![ObjectPartInfo {
|
||||
number: 1,
|
||||
size: usize::try_from(size).expect("test object size should fit usize"),
|
||||
actual_size: size,
|
||||
..Default::default()
|
||||
}];
|
||||
file_info.mod_time = Some(OffsetDateTime::now_utc());
|
||||
file_info
|
||||
}
|
||||
|
||||
@@ -2019,6 +2019,33 @@ mod metadata_cache_tests {
|
||||
fi
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_object_with_fileinfo_rejects_positive_size_without_parts() {
|
||||
let mut output = Vec::new();
|
||||
let err = SetDisks::get_object_with_fileinfo(
|
||||
"bucket",
|
||||
"object",
|
||||
0,
|
||||
1,
|
||||
&mut output,
|
||||
valid_test_fileinfo("object"),
|
||||
Vec::new(),
|
||||
&[],
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
GET_OBJECT_PATH_SET_DISK,
|
||||
"plain",
|
||||
"small",
|
||||
)
|
||||
.await
|
||||
.expect_err("positive-size metadata without parts must fail without panicking");
|
||||
|
||||
assert_eq!(err, Error::FileCorrupt);
|
||||
assert!(output.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_object_with_fileinfo_rejects_invalid_ranges_before_reader_setup() {
|
||||
let bucket = "bucket";
|
||||
@@ -2092,6 +2119,12 @@ mod metadata_cache_tests {
|
||||
|
||||
let mut invalid_erasure = valid_test_fileinfo(object);
|
||||
invalid_erasure.erasure.block_size = 0;
|
||||
invalid_erasure.parts.push(ObjectPartInfo {
|
||||
number: 1,
|
||||
size: 1,
|
||||
actual_size: 1,
|
||||
..Default::default()
|
||||
});
|
||||
let err = SetDisks::get_object_with_fileinfo(
|
||||
bucket,
|
||||
object,
|
||||
@@ -2120,6 +2153,37 @@ mod metadata_cache_tests {
|
||||
assert!(output.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_object_with_fileinfo_accepts_zero_size_without_parts() {
|
||||
let bucket = "bucket";
|
||||
let object = "empty";
|
||||
let mut fi = valid_test_fileinfo(object);
|
||||
fi.size = 0;
|
||||
|
||||
let mut output = Vec::new();
|
||||
SetDisks::get_object_with_fileinfo(
|
||||
bucket,
|
||||
object,
|
||||
0,
|
||||
0,
|
||||
&mut output,
|
||||
fi,
|
||||
Vec::new(),
|
||||
&[],
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
GET_OBJECT_PATH_SET_DISK,
|
||||
"plain",
|
||||
"empty",
|
||||
)
|
||||
.await
|
||||
.expect("zero-byte object without parts must remain readable");
|
||||
|
||||
assert!(output.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_object_with_fileinfo_fails_closed_without_read_quorum() {
|
||||
let bucket = "bucket";
|
||||
@@ -2166,6 +2230,7 @@ mod metadata_cache_tests {
|
||||
let object = "object";
|
||||
let (_dir, disk) = new_read_version_test_disk(bucket).await;
|
||||
let mut fi = valid_test_fileinfo(object);
|
||||
fi.size = 0;
|
||||
fi.mod_time = Some(OffsetDateTime::now_utc());
|
||||
disk.write_metadata(bucket, bucket, object, fi.clone())
|
||||
.await
|
||||
|
||||
@@ -6696,7 +6696,7 @@ mod test {
|
||||
use crate::object_api::ObjectInfo;
|
||||
use rustfs_filemeta::{
|
||||
FileInfo, FileMeta, FileMetaVersion, MetaCacheEntries, MetaCacheEntriesSorted, MetaCacheEntry, MetaDeleteMarker,
|
||||
VersionType,
|
||||
ObjectPartInfo, VersionType,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
@@ -6899,6 +6899,12 @@ mod test {
|
||||
fi.volume = "bucket".to_owned();
|
||||
fi.name = "object".to_owned();
|
||||
fi.size = 1;
|
||||
fi.parts = vec![ObjectPartInfo {
|
||||
number: 1,
|
||||
size: 1,
|
||||
actual_size: 1,
|
||||
..Default::default()
|
||||
}];
|
||||
fi.fresh = true;
|
||||
fi.erasure.index = 1;
|
||||
fi.mod_time = Some(time::OffsetDateTime::from_unix_timestamp(1_705_312_300).expect("valid timestamp"));
|
||||
@@ -6954,6 +6960,12 @@ mod test {
|
||||
fi.version_id = Some(Uuid::from_u128(version_idx));
|
||||
fi.versioned = true;
|
||||
fi.size = 1;
|
||||
fi.parts = vec![ObjectPartInfo {
|
||||
number: 1,
|
||||
size: 1,
|
||||
actual_size: 1,
|
||||
..Default::default()
|
||||
}];
|
||||
fi.mod_time = Some(*mod_time);
|
||||
fi.metadata = metadata;
|
||||
|
||||
|
||||
@@ -415,6 +415,10 @@ impl FileInfo {
|
||||
}
|
||||
|
||||
fn validate_collection_contents(&self, layout: Option<&ValidatedErasureLayout>) -> Result<()> {
|
||||
if layout.is_some() && self.size > 0 && self.parts.is_empty() {
|
||||
return Err(Error::FileCorrupt);
|
||||
}
|
||||
|
||||
let mut part_numbers = [0u64; FILEINFO_PART_BITMAP_WORDS];
|
||||
for part in &self.parts {
|
||||
if let Some(layout) = layout {
|
||||
@@ -692,6 +696,10 @@ impl FileInfo {
|
||||
|
||||
// to_part_offset gets the part index where offset is located, returns part index and offset
|
||||
pub fn to_part_offset(&self, offset: usize) -> Result<(usize, usize)> {
|
||||
if self.size > 0 && self.parts.is_empty() {
|
||||
return Err(Error::FileCorrupt);
|
||||
}
|
||||
|
||||
if offset == 0 {
|
||||
return Ok((0, 0));
|
||||
}
|
||||
@@ -1185,6 +1193,21 @@ mod tests {
|
||||
assert_eq!(layout.expect("strict layout must be present").data_blocks, 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_require_erasure_rejects_positive_size_without_parts() {
|
||||
let mut fi = FileInfo::new("bucket/object", 4, 2);
|
||||
fi.erasure.index = 1;
|
||||
fi.size = 1;
|
||||
|
||||
assert_eq!(fi.validate_for_metadata_read(), Err(Error::FileCorrupt));
|
||||
assert_eq!(fi.to_part_offset(0), Err(Error::FileCorrupt));
|
||||
|
||||
fi.size = 0;
|
||||
fi.validate_for_metadata_read()
|
||||
.expect("zero-byte object metadata may omit parts");
|
||||
assert_eq!(fi.to_part_offset(0), Ok((0, 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_for_erasure_write_only_relaxes_pending_shard_index() {
|
||||
let mut fi = validation_test_fileinfo();
|
||||
|
||||
Reference in New Issue
Block a user