Skip to main content

sbpf_runtime/syscalls/
memory.rs

1use {
2    crate::config::ExecutionCost,
3    sbpf_vm::{
4        compute::ComputeMeter,
5        errors::{SbpfVmError, SbpfVmResult},
6        memory::Memory,
7    },
8};
9
10fn mem_op_consume(n: u64, compute: &ComputeMeter, costs: &ExecutionCost) -> SbpfVmResult<()> {
11    let cost = costs
12        .mem_op_base_cost
13        .max(n.checked_div(costs.cpi_bytes_per_unit).unwrap_or(u64::MAX));
14    compute.consume(cost)
15}
16
17fn is_nonoverlapping(src: u64, src_len: u64, dst: u64, dst_len: u64) -> bool {
18    if src > dst {
19        src.saturating_sub(dst) >= dst_len
20    } else {
21        dst.saturating_sub(src) >= src_len
22    }
23}
24
25pub fn sol_memcpy(
26    registers: [u64; 5],
27    memory: &mut Memory,
28    compute: &ComputeMeter,
29    costs: &ExecutionCost,
30) -> SbpfVmResult<u64> {
31    let dst = registers[0];
32    let src = registers[1];
33    let n = registers[2];
34
35    mem_op_consume(n, compute, costs)?;
36
37    if !is_nonoverlapping(src, n, dst, n) {
38        return Err(SbpfVmError::OverlappingMemoryRegions);
39    }
40
41    let data = memory.read_bytes(src, n as usize)?.to_vec();
42    memory.write_bytes(dst, &data)?;
43    Ok(0)
44}
45
46pub fn sol_memmove(
47    registers: [u64; 5],
48    memory: &mut Memory,
49    compute: &ComputeMeter,
50    costs: &ExecutionCost,
51) -> SbpfVmResult<u64> {
52    let dst = registers[0];
53    let src = registers[1];
54    let n = registers[2];
55
56    mem_op_consume(n, compute, costs)?;
57
58    let data = memory.read_bytes(src, n as usize)?.to_vec();
59    memory.write_bytes(dst, &data)?;
60    Ok(0)
61}
62
63pub fn sol_memset(
64    registers: [u64; 5],
65    memory: &mut Memory,
66    compute: &ComputeMeter,
67    costs: &ExecutionCost,
68) -> SbpfVmResult<u64> {
69    let dst = registers[0];
70    let c = registers[1] as u8;
71    let n = registers[2];
72
73    mem_op_consume(n, compute, costs)?;
74
75    let data = vec![c; n as usize];
76    memory.write_bytes(dst, &data)?;
77    Ok(0)
78}
79
80pub fn sol_memcmp(
81    registers: [u64; 5],
82    memory: &mut Memory,
83    compute: &ComputeMeter,
84    costs: &ExecutionCost,
85) -> SbpfVmResult<u64> {
86    let s1 = registers[0];
87    let s2 = registers[1];
88    let n = registers[2];
89    let result_ptr = registers[3];
90
91    mem_op_consume(n, compute, costs)?;
92
93    let s1_bytes = memory.read_bytes(s1, n as usize)?;
94    let s2_bytes = memory.read_bytes(s2, n as usize)?;
95
96    let mut result: i32 = 0;
97    for i in 0..n as usize {
98        if s1_bytes[i] != s2_bytes[i] {
99            result = (s1_bytes[i] as i32).saturating_sub(s2_bytes[i] as i32);
100            break;
101        }
102    }
103
104    memory.write_u32(result_ptr, result as u32)?;
105    Ok(0)
106}
107
108#[cfg(test)]
109mod tests {
110    use {
111        super::*,
112        crate::syscalls::tests::test_helpers::{costs, make_memory, meter},
113        sbpf_vm::{errors::SbpfVmError, memory::Memory},
114    };
115
116    #[test]
117    fn test_memcpy_basic() {
118        let mut memory = make_memory();
119        let src = Memory::HEAP_START;
120        let dst = Memory::HEAP_START + 64;
121        memory.write_bytes(src, &[1, 2, 3, 4, 5]).unwrap();
122
123        let registers = [dst, src, 5, 0, 0];
124        sol_memcpy(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
125
126        assert_eq!(memory.read_bytes(dst, 5).unwrap(), &[1, 2, 3, 4, 5]);
127    }
128
129    #[test]
130    fn test_memcpy_overlapping_returns_error() {
131        let mut memory = make_memory();
132        // src at HEAP_START, dst 4 bytes into src, n=8 → overlapping
133        let src = Memory::HEAP_START;
134        let dst = Memory::HEAP_START + 4;
135        memory.write_bytes(src, &[0u8; 16]).unwrap();
136
137        let registers = [dst, src, 8, 0, 0];
138        assert!(matches!(
139            sol_memcpy(registers, &mut memory, &meter(1_000_000), &costs()),
140            Err(SbpfVmError::OverlappingMemoryRegions)
141        ));
142    }
143
144    #[test]
145    fn test_memcpy_adjacent_not_overlapping() {
146        let mut memory = make_memory();
147        let src = Memory::HEAP_START;
148        let dst = Memory::HEAP_START + 5;
149        memory.write_bytes(src, &[10, 20, 30, 40, 50]).unwrap();
150
151        let registers = [dst, src, 5, 0, 0];
152        sol_memcpy(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
153        assert_eq!(memory.read_bytes(dst, 5).unwrap(), &[10, 20, 30, 40, 50]);
154    }
155
156    #[test]
157    fn test_memcpy_zero_length() {
158        let mut memory = make_memory();
159        let src = Memory::HEAP_START;
160        let dst = Memory::HEAP_START + 32;
161        memory.write_bytes(src, &[0xAA]).unwrap();
162
163        let registers = [dst, src, 0, 0, 0];
164        sol_memcpy(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
165        assert_eq!(memory.read_u8(dst).unwrap(), 0);
166    }
167
168    #[test]
169    fn test_memcpy_compute_exhausted() {
170        let mut memory = make_memory();
171        memory
172            .write_bytes(Memory::HEAP_START, &[1, 2, 3, 4, 5])
173            .unwrap();
174
175        let registers = [Memory::HEAP_START + 64, Memory::HEAP_START, 5, 0, 0];
176        // mem_op_base_cost = 10; budget of 9 is not enough
177        assert!(matches!(
178            sol_memcpy(registers, &mut memory, &meter(9), &costs()),
179            Err(SbpfVmError::ComputeBudgetExceeded { .. })
180        ));
181    }
182
183    #[test]
184    fn test_memcpy_oob_dst() {
185        let mut memory = make_memory();
186        let src = Memory::HEAP_START;
187        let heap_size = 64 * 1024u64;
188        let dst = Memory::HEAP_START + heap_size - 3;
189        memory.write_bytes(src, &[1, 2, 3, 4, 5]).unwrap();
190
191        let registers = [dst, src, 5, 0, 0];
192        let result = sol_memcpy(registers, &mut memory, &meter(1_000_000), &costs());
193        assert!(result.is_err());
194    }
195
196    #[test]
197    fn test_memcpy_oob_src() {
198        let mut memory = make_memory();
199        let heap_size = 64 * 1024u64;
200        let src = Memory::HEAP_START + heap_size - 2;
201        let dst = Memory::HEAP_START;
202
203        let registers = [dst, src, 5, 0, 0];
204        let result = sol_memcpy(registers, &mut memory, &meter(1_000_000), &costs());
205        assert!(result.is_err());
206    }
207
208    #[test]
209    fn test_memmove_basic() {
210        let mut memory = make_memory();
211        let src = Memory::HEAP_START;
212        let dst = Memory::HEAP_START + 64;
213        memory.write_bytes(src, &[5, 4, 3, 2, 1]).unwrap();
214
215        let registers = [dst, src, 5, 0, 0];
216        sol_memmove(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
217        assert_eq!(memory.read_bytes(dst, 5).unwrap(), &[5, 4, 3, 2, 1]);
218    }
219
220    #[test]
221    fn test_memmove_overlapping_allowed() {
222        let mut memory = make_memory();
223        let src = Memory::HEAP_START;
224        let dst = Memory::HEAP_START + 4;
225        memory.write_bytes(src, &[1, 2, 3, 4, 5, 6, 7, 8]).unwrap();
226
227        let registers = [dst, src, 8, 0, 0];
228        sol_memmove(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
229    }
230
231    #[test]
232    fn test_memmove_compute_exhausted() {
233        let mut memory = make_memory();
234        memory.write_bytes(Memory::HEAP_START, &[1]).unwrap();
235        let registers = [Memory::HEAP_START + 64, Memory::HEAP_START, 1, 0, 0];
236        assert!(matches!(
237            sol_memmove(registers, &mut memory, &meter(9), &costs()),
238            Err(SbpfVmError::ComputeBudgetExceeded { .. })
239        ));
240    }
241
242    #[test]
243    fn test_memset_basic() {
244        let mut memory = make_memory();
245        let dst = Memory::HEAP_START;
246
247        let registers = [dst, 0xAB, 8, 0, 0];
248        sol_memset(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
249
250        assert_eq!(memory.read_bytes(dst, 8).unwrap(), &[0xAB; 8]);
251    }
252
253    #[test]
254    fn test_memset_zero_fill() {
255        let mut memory = make_memory();
256        let dst = Memory::HEAP_START;
257        memory.write_bytes(dst, &[0xFF; 4]).unwrap();
258
259        let registers = [dst, 0x00, 4, 0, 0];
260        sol_memset(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
261        assert_eq!(memory.read_bytes(dst, 4).unwrap(), &[0u8; 4]);
262    }
263
264    #[test]
265    fn test_memset_compute_exhausted() {
266        let mut memory = make_memory();
267        let registers = [Memory::HEAP_START, 0xFF, 5, 0, 0];
268        assert!(matches!(
269            sol_memset(registers, &mut memory, &meter(9), &costs()),
270            Err(SbpfVmError::ComputeBudgetExceeded { .. })
271        ));
272    }
273
274    #[test]
275    fn test_memcmp_equal() {
276        let mut memory = make_memory();
277        let s1 = Memory::HEAP_START;
278        let s2 = Memory::HEAP_START + 16;
279        let result_ptr = Memory::HEAP_START + 32;
280        memory.write_bytes(s1, &[1, 2, 3, 4, 5]).unwrap();
281        memory.write_bytes(s2, &[1, 2, 3, 4, 5]).unwrap();
282
283        let registers = [s1, s2, 5, result_ptr, 0];
284        sol_memcmp(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
285
286        let cmp = memory.read_u32(result_ptr).unwrap() as i32;
287        assert_eq!(cmp, 0);
288    }
289
290    #[test]
291    fn test_memcmp_less_than() {
292        let mut memory = make_memory();
293        let s1 = Memory::HEAP_START;
294        let s2 = Memory::HEAP_START + 16;
295        let result_ptr = Memory::HEAP_START + 32;
296        // s1[2]=3, s2[2]=4 → 3 - 4 = -1
297        memory.write_bytes(s1, &[1, 2, 3]).unwrap();
298        memory.write_bytes(s2, &[1, 2, 4]).unwrap();
299
300        let registers = [s1, s2, 3, result_ptr, 0];
301        sol_memcmp(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
302
303        let cmp = memory.read_u32(result_ptr).unwrap() as i32;
304        assert!(cmp < 0);
305    }
306
307    #[test]
308    fn test_memcmp_greater_than() {
309        let mut memory = make_memory();
310        let s1 = Memory::HEAP_START;
311        let s2 = Memory::HEAP_START + 16;
312        let result_ptr = Memory::HEAP_START + 32;
313        memory.write_bytes(s1, &[1, 2, 5]).unwrap();
314        memory.write_bytes(s2, &[1, 2, 4]).unwrap();
315
316        let registers = [s1, s2, 3, result_ptr, 0];
317        sol_memcmp(registers, &mut memory, &meter(1_000_000), &costs()).unwrap();
318
319        let cmp = memory.read_u32(result_ptr).unwrap() as i32;
320        assert!(cmp > 0);
321    }
322
323    #[test]
324    fn test_memcmp_compute_exhausted() {
325        let mut memory = make_memory();
326        let s1 = Memory::HEAP_START;
327        let s2 = Memory::HEAP_START + 16;
328        let result_ptr = Memory::HEAP_START + 32;
329        let registers = [s1, s2, 5, result_ptr, 0];
330        assert!(matches!(
331            sol_memcmp(registers, &mut memory, &meter(9), &costs()),
332            Err(SbpfVmError::ComputeBudgetExceeded { .. })
333        ));
334    }
335}