Skip to content

Rust: Add unsafe deserialization query (CWE-502) - #22324

Open
lcmangalagiri wants to merge 3 commits into
github:mainfrom
lcmangalagiri:rust-cwe-502-unsafe-deserialization
Open

lcmangalagiri wants to merge 3 commits into
github:mainfrom
lcmangalagiri:rust-cwe-502-unsafe-deserialization

Conversation

@lcmangalagiri

Copy link
Copy Markdown
Contributor

Detects user-controlled data flowing into deserialization functions (serde_json, bincode, rmp_serde, ciborium, serde_yaml, toml).

Query include:

  1. Extension library with sources, sinks, and barriers
  2. Models-as-data sink definitions
  3. Query help (.qhelp) with examples
  4. Test cases with inline expectations

Query ID: rust/unsafe-deserialization

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
@geoffw0

geoffw0 commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

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).

@lcmangalagiri

Copy link
Copy Markdown
Contributor Author

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!

@geoffw0

geoffw0 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

just a friendly reminder since it's been a few days

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.

@github-actions

Copy link
Copy Markdown
Contributor

QHelp previews:

rust/ql/src/queries/security/CWE-502/UnsafeDeserialization.qhelp

Deserialization of user-controlled data

Deserializing 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 serde, bincode, or rmp-serde can still lead to panics, excessive memory allocation, or logic bugs when trait objects or polymorphic types are involved.

Recommendation

Avoid 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 serde, consider:

  • Validating input size before deserialization.
  • Using #[serde(deny_unknown_fields)] to reject unexpected data.
  • Avoiding #[typetag] or trait object deserialization with untrusted input.
  • Using bounded containers (e.g., limiting Vec length via custom deserializers).

Example

In 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

Comment on lines +38 to +42
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its right, though to be honest I slightly prefer your alert message.

@geoffw0 geoffw0 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get a 404 error for this link. Is this the page you want to reference?

Suggested change
<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()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the SPURIOUS tag to make it clear to readers that this result is unwanted.

Suggested change
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

@geoffw0

geoffw0 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants