From 155f524b5e3b8d1a0d8362170ccaa689424db4c9 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 16 Mar 2026 16:05:23 -0700 Subject: [PATCH] Reject mismatched vif bytes offsets --- seaweed-volume/src/storage/volume.rs | 62 ++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/seaweed-volume/src/storage/volume.rs b/seaweed-volume/src/storage/volume.rs index af3eba202..66be54b93 100644 --- a/seaweed-volume/src/storage/volume.rs +++ b/seaweed-volume/src/storage/volume.rs @@ -548,7 +548,7 @@ impl Volume { self.load_index()?; } - self.load_vif(); + self.load_vif()?; Ok(()) } @@ -1444,11 +1444,11 @@ impl Volume { /// Load volume info from .vif file. /// Supports both the protobuf-JSON format (Go-compatible) and legacy JSON. - fn load_vif(&mut self) { + fn load_vif(&mut self) -> Result<(), VolumeError> { let path = self.vif_path(); if let Ok(content) = fs::read_to_string(&path) { if content.trim().is_empty() { - return; + return Ok(()); } // Try protobuf-JSON (Go-compatible VolumeInfo via VifVolumeInfo) if let Ok(vif_info) = serde_json::from_str::(&content) { @@ -1464,15 +1464,28 @@ impl Volume { if !self.has_remote_file && self.volume_info.bytes_offset == 0 { self.volume_info.bytes_offset = OFFSET_SIZE as u32; } - return; + if self.volume_info.bytes_offset != 0 + && self.volume_info.bytes_offset != OFFSET_SIZE as u32 + { + return Err(VolumeError::Io(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "bytes_offset mismatch in {}: found {}, expected {}", + path, self.volume_info.bytes_offset, OFFSET_SIZE + ), + ))); + } + return Ok(()); } // Fall back to legacy format if let Ok(info) = serde_json::from_str::(&content) { if info.read_only { self.no_write_or_delete = true; } + return Ok(()); } } + Ok(()) } /// Save volume info to .vif file in protobuf-JSON format (Go-compatible). @@ -2992,6 +3005,47 @@ mod tests { assert_eq!(v.version(), VERSION_2); } + #[test] + fn test_load_vif_rejects_bytes_offset_mismatch() { + let tmp = TempDir::new().unwrap(); + let dir = tmp.path().to_str().unwrap(); + + { + let _v = make_test_volume(dir); + let vif = VifVolumeInfo { + version: Version::current().0 as u32, + bytes_offset: (OFFSET_SIZE as u32) + 1, + ..VifVolumeInfo::default() + }; + std::fs::write( + format!("{}/1.vif", dir), + serde_json::to_string_pretty(&vif).unwrap(), + ) + .unwrap(); + } + + let result = Volume::new( + dir, + dir, + "", + VolumeId(1), + NeedleMapKind::InMemory, + None, + None, + 0, + Version::current(), + ); + + match result { + Ok(_) => panic!("expected bytes_offset mismatch to fail"), + Err(VolumeError::Io(io_err)) => { + assert_eq!(io_err.kind(), io::ErrorKind::InvalidData); + assert!(io_err.to_string().contains("bytes_offset mismatch")); + } + Err(other) => panic!("unexpected error: {other:?}"), + } + } + /// Volume destroy removes .vif alongside the primary data files. #[test] fn test_destroy_removes_vif() {