Rust: Add unsafe deserialization query (CWE-502) - #22324
lcmangalagiri wants to merge 3 commits into
Conversation
Detects user-controlled data flowing into deserialization functions (serde_json, bincode, rmp_serde, ciborium, serde_yaml, toml). - Extension library with sources, sinks, and barriers - Models-as-data sink definitions - Query help (.qhelp) with examples - Test cases with inline expectations Query ID: rust/unsafe-deserialization
|
Hi, just a quick note that I do intend to review this soon, but I've got a bit delayed with other things (including the other new query pull request). |
Hi @geoffw0, no rush — just a friendly reminder since it's been a few days. Feel free to take a look at this one whenever you get a chance. Thanks! |
Yep, I'm looking forward to getting round to this - I think it will be a good query. I am away next week though. Sorry for the delays. |
|
QHelp previews: rust/ql/src/queries/security/CWE-502/UnsafeDeserialization.qhelpDeserialization of user-controlled dataDeserializing untrusted data without validation can allow an attacker to cause denial of service, consume excessive resources, or in some cases execute arbitrary code. In Rust, while memory safety mitigates some risks, deserializing untrusted data with libraries like RecommendationAvoid deserializing untrusted data with formats that allow unbounded allocation or polymorphic dispatch. Prefer formats with schema validation (like Protocol Buffers) when processing untrusted input. If using
ExampleIn the following example, data from an HTTP request is directly deserialized without any validation. An attacker could send a crafted payload that causes excessive memory allocation or other unintended behavior. use serde::Deserialize;
#[derive(Deserialize)]
struct UserData {
name: String,
items: Vec<String>,
}
fn handle_request(body: &[u8]) -> UserData {
// BAD: deserializing user-controlled data without size validation
serde_json::from_slice(body).unwrap()
}A safer approach validates the input size and uses strict deserialization settings: use serde::Deserialize;
const MAX_BODY_SIZE: usize = 1024 * 1024; // 1 MB limit
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct UserData {
name: String,
#[serde(deserialize_with = "bounded_vec")]
items: Vec<String>,
}
fn bounded_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let v = Vec::<String>::deserialize(deserializer)?;
if v.len() > 100 {
return Err(serde::de::Error::custom("too many items"));
}
Ok(v)
}
fn handle_request(body: &[u8]) -> Result<UserData, String> {
// GOOD: validate input size before deserialization, use bounded containers
if body.len() > MAX_BODY_SIZE {
return Err("payload too large".to_string());
}
serde_json::from_slice(body).map_err(|e| e.to_string())
}References
|
| from UnsafeDeserializationFlow::PathNode sourceNode, UnsafeDeserializationFlow::PathNode sinkNode | ||
| where UnsafeDeserializationFlow::flowPath(sourceNode, sinkNode) | ||
| select sinkNode.getNode(), sourceNode, sinkNode, | ||
| "This deserialization operation processes $@ without validation.", sourceNode.getNode(), | ||
| "user-provided data" |
There was a problem hiding this comment.
Its right, though to be honest I slightly prefer your alert message.
geoffw0
left a comment
There was a problem hiding this comment.
This looks pretty good and we'll be glad to have an unsafe deserialization query in the Rust query suite! ✨
In the long run we may want to add barriers to this query so that we don't report spurious results when there are checks on the untrusted data before it gets deserialized. I don't see a lot of noisy results in the wild so I think it will be OK to merge without that feature.
| <references> | ||
|
|
||
| <li>OWASP: <a href="https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/16-Testing_for_HTTP_Incoming_Requests">Deserialization of untrusted data</a>.</li> | ||
| <li>CWE-502: <a href="https://cwe.mitre.org/data/definitions/502.html">Deserialization of Untrusted Data</a>.</li> |
There was a problem hiding this comment.
| <li>CWE-502: <a href="https://cwe.mitre.org/data/definitions/502.html">Deserialization of Untrusted Data</a>.</li> |
We don't need to include a reference to the CWE-502 page, this will be done automatically based on theexternal/cwe/cwe-502 tag in the query (and we'll end up with a duplicate if we do it manually as well).
| </example> | ||
| <references> | ||
|
|
||
| <li>OWASP: <a href="https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/16-Testing_for_HTTP_Incoming_Requests">Deserialization of untrusted data</a>.</li> |
There was a problem hiding this comment.
I get a 404 error for this link. Is this the page you want to reference?
| <li>OWASP: <a href="https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/16-Testing_for_HTTP_Incoming_Requests">Deserialization of untrusted data</a>.</li> | |
| <li>OWASP: <a href="https://community.owasp.org/vulnerabilities/Deserialization_of_untrusted_data">Deserialization of untrusted data</a>.</li> |
| fn handle_request(body: &[u8]) -> UserData { | ||
| // BAD: deserializing user-controlled data without size validation | ||
| serde_json::from_slice(body).unwrap() | ||
| } |
There was a problem hiding this comment.
We should include the bad and good example code in the test.
(it's OK if the query doesn't give exactly the results we'd like on them, at this stage)
| // Safe: size check before deserialization (still flagged as the barrier | ||
| // is not modeled as a data flow barrier, but demonstrates the pattern) | ||
| if remote_string.len() < 1024 { | ||
| let _data: UserData = serde_json::from_str(&remote_string).unwrap(); // $ Alert[rust/unsafe-deserialization]=remote4 |
There was a problem hiding this comment.
Use the SPURIOUS tag to make it clear to readers that this result is unwanted.
| let _data: UserData = serde_json::from_str(&remote_string).unwrap(); // $ Alert[rust/unsafe-deserialization]=remote4 | |
| let _data: UserData = serde_json::from_str(&remote_string).unwrap(); // $ SPURIOUS: Alert[rust/unsafe-deserialization]=remote4 |
|
Hmm, I tried out the query locally and it looks like we aren't finding the results in the test (despite the tags suggesting that we are). I'm looking into what's gone wrong... |
Detects user-controlled data flowing into deserialization functions (serde_json, bincode, rmp_serde, ciborium, serde_yaml, toml).
Query include:
Query ID: rust/unsafe-deserialization