atlas_core/
safetensors.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! The one place that turns a safetensors `data_offsets` pair into a byte span.
4//!
5//! A checkpoint is third-party data: Atlas loads it by URL, so every number in
6//! the header is attacker-controlled until it has been checked. The header
7//! declares each tensor as `"data_offsets": [start, end]` relative to the data
8//! section, and the naive `end - start` is a `u64` subtraction that WRAPS on a
9//! crafted or truncated file — a reversed pair yields a length near `u64::MAX`,
10//! which downstream becomes an allocation size, a `pread` window, or an RDMA
11//! `len` published to a peer.
12//!
13//! Two loaders parse the same header format for different transports
14//! (`spark_runtime::fast_weights` reads it with O_DIRECT, and
15//! `spark_storage::weight_peer` republishes it as an RDMA manifest). They live
16//! in crates that cannot depend on each other, so the *rule* lives here and
17//! both call it, rather than each keeping its own copy of the arithmetic.
18
19use anyhow::{Context, Result, bail};
20use serde_json::Value;
21
22/// A validated tensor byte span: an absolute file offset and a length that is
23/// known to fit inside the file it came from.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct TensorSpan {
26    /// Absolute byte offset in the shard file where the tensor starts.
27    pub abs_offset: u64,
28    /// Tensor byte length.
29    pub len: u64,
30}
31
32/// Validate one tensor's `data_offsets` against the file that declared it.
33///
34/// `offsets` is the raw `data_offsets` JSON value, `data_start` is
35/// `8 + header_size` (where the data section begins), and `file_len` is the
36/// shard's size on disk.
37///
38/// Rejects, in this order: a pair that isn't two integers, a reversed pair
39/// (`end < start`, the underflow), and a span that runs past end-of-file.
40/// Callers get a named error instead of a wrapped length.
41pub fn tensor_span(
42    tensor: &str,
43    offsets: &Value,
44    data_start: u64,
45    file_len: u64,
46) -> Result<TensorSpan> {
47    let arr = offsets
48        .as_array()
49        .with_context(|| format!("tensor {tensor}: data_offsets is not an array"))?;
50    if arr.len() != 2 {
51        bail!(
52            "tensor {tensor}: data_offsets has {} entries, expected 2",
53            arr.len()
54        );
55    }
56    let rel_start = arr[0]
57        .as_u64()
58        .with_context(|| format!("tensor {tensor}: bad data_offsets[0]"))?;
59    let rel_end = arr[1]
60        .as_u64()
61        .with_context(|| format!("tensor {tensor}: bad data_offsets[1]"))?;
62    // The underflow. `rel_end - rel_start` wraps to ~u64::MAX on a reversed
63    // pair; in release builds that is silent.
64    if rel_end < rel_start {
65        bail!("tensor {tensor}: data_offsets [{rel_start}, {rel_end}] end precedes start");
66    }
67    let len = rel_end - rel_start;
68    // `data_start` is bounded by the 64 MiB header cap the callers enforce, but
69    // `rel_start` is not, so the sum still needs checking.
70    let abs_offset = data_start
71        .checked_add(rel_start)
72        .with_context(|| format!("tensor {tensor}: data_offsets[0] {rel_start} overflows"))?;
73    let abs_end = abs_offset
74        .checked_add(len)
75        .with_context(|| format!("tensor {tensor}: span {abs_offset}+{len} overflows"))?;
76    if abs_end > file_len {
77        bail!(
78            "tensor {tensor}: spans bytes {abs_offset}..{abs_end} but the shard is only \
79             {file_len} bytes (truncated or corrupt checkpoint)"
80        );
81    }
82    Ok(TensorSpan { abs_offset, len })
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use serde_json::json;
89
90    /// A well-formed pair keeps its exact span, converted to absolute.
91    #[test]
92    fn accepts_well_formed_span() {
93        let s = tensor_span("w", &json!([0, 128]), 72, 4096).unwrap();
94        assert_eq!(
95            s,
96            TensorSpan {
97                abs_offset: 72,
98                len: 128
99            }
100        );
101        let s = tensor_span("w", &json!([128, 256]), 72, 4096).unwrap();
102        assert_eq!(
103            s,
104            TensorSpan {
105                abs_offset: 200,
106                len: 128
107            }
108        );
109    }
110
111    /// A zero-length tensor is legal safetensors and must not be rejected.
112    #[test]
113    fn accepts_empty_tensor() {
114        let s = tensor_span("w", &json!([64, 64]), 8, 4096).unwrap();
115        assert_eq!(s.len, 0);
116    }
117
118    /// A tensor ending exactly at EOF is the last tensor of every real shard.
119    #[test]
120    fn accepts_span_ending_exactly_at_eof() {
121        let s = tensor_span("w", &json!([0, 4024]), 72, 4096).unwrap();
122        assert_eq!(s.len, 4024);
123    }
124
125    /// THE BUG: `rel_end - rel_start` on a reversed pair wraps to ~u64::MAX in
126    /// release builds. Reject it instead.
127    #[test]
128    fn rejects_reversed_offsets_instead_of_underflowing() {
129        let err = tensor_span("evil", &json!([4096, 0]), 8, 65536).unwrap_err();
130        let msg = err.to_string();
131        assert!(msg.contains("evil"), "{msg}");
132        assert!(msg.contains("end precedes start"), "{msg}");
133        // Sanity-check that the unchecked form really does wrap, so this test
134        // is guarding a live hazard and not a hypothetical one.
135        assert_eq!(0u64.wrapping_sub(4096), u64::MAX - 4095);
136    }
137
138    /// A truncated shard: the header still advertises the full tensor.
139    #[test]
140    fn rejects_span_past_end_of_file() {
141        let err = tensor_span("w", &json!([0, 1_000_000]), 72, 4096).unwrap_err();
142        assert!(err.to_string().contains("truncated or corrupt"), "{err}");
143    }
144
145    /// A huge `rel_start` must not wrap when added to `data_start`.
146    #[test]
147    fn rejects_offset_that_overflows_u64() {
148        let err = tensor_span("w", &json!([u64::MAX, u64::MAX]), 72, 4096).unwrap_err();
149        assert!(
150            err.to_string().contains("data_offsets[0]") && err.to_string().contains("overflows"),
151            "{err}"
152        );
153    }
154
155    /// A valid absolute start can still overflow when its nonzero length is added.
156    #[test]
157    fn rejects_span_end_that_overflows_u64() {
158        let err = tensor_span("w", &json!([u64::MAX - 1, u64::MAX]), 1, u64::MAX).unwrap_err();
159        assert!(
160            err.to_string().contains("span") && err.to_string().contains("overflows"),
161            "{err}"
162        );
163    }
164
165    /// Malformed `data_offsets` shapes are named, not indexed into blindly.
166    #[test]
167    fn rejects_malformed_offsets() {
168        let cases = [
169            (json!("nope"), "not an array"),
170            (json!([0]), "1 entries"),
171            (json!([0, 1, 2]), "3 entries"),
172            (json!([-1, 16]), "data_offsets[0]"),
173            (json!([0.5, 16]), "data_offsets[0]"),
174            (json!([0, -1]), "data_offsets[1]"),
175            (json!([0, 16.5]), "data_offsets[1]"),
176            (json!([0, "16"]), "data_offsets[1]"),
177        ];
178        for (offsets, cause) in cases {
179            let err = tensor_span("w", &offsets, 8, 4096).unwrap_err();
180            let msg = err.to_string();
181            assert!(msg.contains("tensor w"), "{msg}");
182            assert!(msg.contains(cause), "expected {cause} in: {msg}");
183        }
184    }
185}