about summary refs log tree commit diff
path: root/tvix/castore
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2024-01-31T17·03+0200
committerclbot <clbot@tvl.fyi>2024-02-02T16·26+0000
commit9504015031a7299f22a9827ff0eded74a95c66f8 (patch)
tree279d5e62003b31731c531336b076be8cdeeab229 /tvix/castore
parent5ad5a0da00622beb713fb85520821212416b02f9 (diff)
feat(tvix/castore/blobsvc): validate StatBlobResponse r/7470
All chunks must have valid blake3 digests. It is allowed to send an
empty list, if no more granular chunking is available.

Change-Id: I7ecb53579cdf40fd938bb68a85685751b4d3626f
Reviewed-on: https://cl.tvl.fyi/c/depot/+/10726
Tested-by: BuildkiteCI
Reviewed-by: Connor Brewster <cbrewster@hey.com>
Autosubmit: flokli <flokli@flokli.de>
Diffstat (limited to 'tvix/castore')
-rw-r--r--tvix/castore/src/blobservice/grpc.rs4
-rw-r--r--tvix/castore/src/proto/mod.rs25
2 files changed, 29 insertions, 0 deletions
diff --git a/tvix/castore/src/blobservice/grpc.rs b/tvix/castore/src/blobservice/grpc.rs
index acc0125c82ed..d98a9b517724 100644
--- a/tvix/castore/src/blobservice/grpc.rs
+++ b/tvix/castore/src/blobservice/grpc.rs
@@ -129,6 +129,10 @@ impl BlobService for GRPCBlobService {
             Err(e) => Err(io::Error::new(io::ErrorKind::Other, e)),
             Ok(resp) => {
                 let resp = resp.into_inner();
+
+                resp.validate()
+                    .map_err(|e| std::io::Error::new(io::ErrorKind::InvalidData, e))?;
+
                 if resp.chunks.is_empty() {
                     warn!("chunk list is empty");
                 }
diff --git a/tvix/castore/src/proto/mod.rs b/tvix/castore/src/proto/mod.rs
index edf042e3dfa6..59f5c1fdf3f6 100644
--- a/tvix/castore/src/proto/mod.rs
+++ b/tvix/castore/src/proto/mod.rs
@@ -56,6 +56,14 @@ pub enum ValidateNodeError {
     InvalidSymlinkTarget(Vec<u8>),
 }
 
+/// Errors that occur during StatBlobResponse validation
+#[derive(Debug, PartialEq, Eq, thiserror::Error)]
+pub enum ValidateStatBlobResponseError {
+    /// Invalid digest length encountered
+    #[error("Invalid digest length {0} for chunk #{1}")]
+    InvalidDigestLen(usize, usize),
+}
+
 /// Checks a Node name for validity as an intermediate node.
 /// We disallow slashes, null bytes, '.', '..' and the empty string.
 fn validate_node_name(name: &[u8]) -> Result<(), ValidateNodeError> {
@@ -299,6 +307,23 @@ impl Directory {
     }
 }
 
+impl StatBlobResponse {
+    /// Validates a StatBlobResponse. All chunks must have valid blake3 digests.
+    /// It is allowed to send an empty list, if no more granular chunking is
+    /// available.
+    pub fn validate(&self) -> Result<(), ValidateStatBlobResponseError> {
+        for (i, chunk) in self.chunks.iter().enumerate() {
+            if chunk.digest.len() != blake3::KEY_LEN {
+                return Err(ValidateStatBlobResponseError::InvalidDigestLen(
+                    chunk.digest.len(),
+                    i,
+                ));
+            }
+        }
+        Ok(())
+    }
+}
+
 /// Struct to hold the state of an iterator over all nodes of a Directory.
 ///
 /// Internally, this keeps peekable Iterators over all three lists of a