Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Rust: Add tests for web frameworks as taint sources #19466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 13, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,8 @@
| test.rs:369:25:369:43 | ...::open | Flow source 'FileSource' of type file (DEFAULT). |
| test.rs:377:22:377:35 | ...::stdin | Flow source 'StdInSource' of type stdin (DEFAULT). |
| test.rs:386:16:386:29 | ...::args | Flow source 'CommandLineArgs' of type commandargs (DEFAULT). |
| web_frameworks.rs:12:31:12:31 | a | Flow source 'RemoteSource' of type remote (DEFAULT). |
| web_frameworks.rs:21:31:21:36 | TuplePat | Flow source 'RemoteSource' of type remote (DEFAULT). |
| web_frameworks.rs:43:31:43:45 | MyStruct {...} | Flow source 'RemoteSource' of type remote (DEFAULT). |
| web_frameworks.rs:51:31:51:32 | ms | Flow source 'RemoteSource' of type remote (DEFAULT). |
| web_frameworks.rs:60:15:60:15 | a | Flow source 'RemoteSource' of type remote (DEFAULT). |
5 changes: 5 additions & 0 deletions 5 rust/ql/test/library-tests/dataflow/sources/options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ qltest_dependencies:
- http = { version = "1.2.0" }
- tokio = { version = "1.43.0", features = ["full"] }
- futures = { version = "0.3" }
- poem = { version = "3.1.10" }
- serde = { version = "1.0.219" }
- actix-web = { version = "4.10.2" }
- axum = { version = "0.8.4" }
- serde_json = { version = "1.0.140" }
198 changes: 198 additions & 0 deletions 198 rust/ql/test/library-tests/dataflow/sources/web_frameworks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@

fn sink<T>(_: T) { }

// --- tests ---

mod poem_test {
use poem::{get, handler, web::Path, web::Query, Route, Server, listener::TcpListener};
use serde::Deserialize;
use super::sink;

#[handler]
fn my_poem_handler_1(Path(a): Path<String>) -> String { // $ Alert[rust/summary/taint-sources]
sink(a.as_str()); // $ MISSING: hasTaintFlow
sink(a.as_bytes()); // $ MISSING: hasTaintFlow
sink(a); // $ MISSING: hasTaintFlow

"".to_string()
}

#[handler]
fn my_poem_handler_2(Path((a, b)): Path<(String, String)>) -> String { // $ Alert[rust/summary/taint-sources]
sink(a); // $ MISSING: hasTaintFlow
sink(b); // $ MISSING: hasTaintFlow

"".to_string()
}

#[handler]
fn my_poem_handler_3(path: Path<(String, String)>) -> String { // $ MISSING: Alert[rust/summary/taint-sources]
sink(&path.0); // $ MISSING: hasTaintFlow
sink(&path.1); // $ MISSING: hasTaintFlow

"".to_string()
}

#[derive(Deserialize)]
struct MyStruct {
a: String,
b: String,
}

#[handler]
fn my_poem_handler_4(Path(MyStruct {a, b}): Path<MyStruct>) -> String { // $ Alert[rust/summary/taint-sources]
sink(a); // $ MISSING: hasTaintFlow
sink(b); // $ MISSING: hasTaintFlow

"".to_string()
}

#[handler]
fn my_poem_handler_5(Path(ms): Path<MyStruct>) -> String { // $ Alert[rust/summary/taint-sources]
sink(ms.a); // $ MISSING: hasTaintFlow
sink(ms.b); // $ MISSING: hasTaintFlow

"".to_string()
}

#[handler]
fn my_poem_handler_6(
Query(a): Query<String>, // $ Alert[rust/summary/taint-sources]
) -> String {
sink(a); // $ MISSING: hasTaintFlow

"".to_string()
}

async fn test_poem() {
let app = Route::new()
.at("/1/:a", get(my_poem_handler_1))
.at("/2/:a/:b", get(my_poem_handler_2))
.at("/3/:a/:b", get(my_poem_handler_3))
.at("/4/:a/:b", get(my_poem_handler_4))
.at("/5/:a/:b", get(my_poem_handler_5))
.at("/6/:a/", get(my_poem_handler_6));

Server::new(TcpListener::bind("0.0.0.0:3000")).run(app).await.unwrap();

// ...
}
}

mod actix_test {
use actix_web::{get, web, App};
use super::sink;

async fn my_actix_handler_1(path: web::Path<String>) -> String { // $ MISSING: Alert[rust/summary/taint-sources]
let a = path.into_inner();
sink(a.as_str()); // $ MISSING: hasTaintFlow
sink(a.as_bytes()); // $ MISSING: hasTaintFlow
sink(a); // $ MISSING: hasTaintFlow

"".to_string()
}

async fn my_actix_handler_2(path: web::Path<(String, String)>) -> String { // $ MISSING: Alert[rust/summary/taint-sources]
let (a, b) = path.into_inner();

sink(a); // $ MISSING: hasTaintFlow
sink(b); // $ MISSING: hasTaintFlow

"".to_string()
}

async fn my_actix_handler_3(web::Query(a): web::Query<String>) -> String { // $ MISSING: Alert[rust/summary/taint-sources]
sink(a); // $ MISSING: hasTaintFlow

"".to_string()
}

#[get("/4/{a}")]
async fn my_actix_handler_4(path: web::Path<String>) -> String { // $ MISSING: Alert[rust/summary/taint-sources]
let a = path.into_inner();
sink(a); // $ MISSING: hasTaintFlow

"".to_string()
}

async fn test_actix() {
let app = App::new()
.route("/1/{a}", web::get().to(my_actix_handler_1))
.route("/2/{a}/{b}", web::get().to(my_actix_handler_2))
.route("/3/{a}", web::get().to(my_actix_handler_3))
.service(my_actix_handler_4);

// ...
}
}

mod axum_test {
use axum::Router;
use axum::routing::get;
use axum::extract::{Path, Query, Request, Json};
use std::collections::HashMap;
use super::sink;

async fn my_axum_handler_1(Path(a): Path<String>) -> &'static str { // $ MISSING: Alert[rust/summary/taint-sources]
sink(a.as_str()); // $ MISSING: hasTaintFlow
sink(a.as_bytes()); // $ MISSING: hasTaintFlow
sink(a); // $ MISSING: hasTaintFlow

""
}

async fn my_axum_handler_2(Path((a, b)): Path<(String, String)>) -> &'static str { // $ MISSING: Alert[rust/summary/taint-sources]
sink(a); // $ MISSING: hasTaintFlow
sink(b); // $ MISSING: hasTaintFlow

""
}

async fn my_axum_handler_3(Query(params): Query<HashMap<String, String>>) -> &'static str { // $ MISSING: Alert[rust/summary/taint-sources]
for (key, value) in params {
sink(key); // $ MISSING: hasTaintFlow
sink(value); // $ MISSING: hasTaintFlow
}

""
}

async fn my_axum_handler_4(request: Request) -> &'static str { // $ MISSING: Alert[rust/summary/taint-sources]
sink(request.body()); // $ MISSING: hasTaintFlow
request.headers().get("header").unwrap(); // $ MISSING: hasTaintFlow
sink(request.into_body()); // $ MISSING: hasTaintFlow

""
}

async fn my_axum_handler_5(Json(payload): Json<serde_json::Value>) -> &'static str { // $ MISSING: Alert[rust/summary/taint-sources]
sink(payload.as_str()); // $ MISSING: hasTaintFlow
sink(payload); // $ MISSING: hasTaintFlow

""
}

async fn my_axum_handler_6(body: String) -> &'static str { // $ MISSING: Alert[rust/summary/taint-sources]
sink(body); // $ MISSING: hasTaintFlow

""
}

async fn my_axum_handler_7(body: String) -> &'static str { // $ MISSING: Alert[rust/summary/taint-sources]
Copy link
Contributor

Choose a reason for hiding this comment

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

For these Axum handlers that are just functions without any attributes I guess we'll have to find the get call where they are used? Could it make sense to have the path start at the get call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we have the choice to match either the #[] attributes, the function arguments / types, or the way they're used in router setup (the get call). We also have the choice of making three library specific models or one heuristic model (I'd prefer the latter, as there are probably more than three web libraries for Rust, but lets wait and see what works best). In any case the goal here is just for the tests to be realistic enough to serve whichever approach to modelling we settle on.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks. Though in this particular case it seems that we only have the get call to go by. Nothing else about the function stands out as a handler. Anyway, I was just curious, agree that we'll see later and that the test is good as-is :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, I think you might be right it'll be the trickiest one to model for that reason. Even if we don't succeed, the test serves to document that gap and potential for future improvement.

sink(body); // $ MISSING: hasTaintFlow

""
}

async fn test_axum() {
let app = Router::<()>::new()
.route("/1/{a}", get(my_axum_handler_1))
.route("/2/{a}/{b}", get(my_axum_handler_2))
.route("/3/:a", get(my_axum_handler_3))
.route("/4/:a", get(my_axum_handler_4))
.route("/5/:a", get(my_axum_handler_5))
.route("/67/:a", get(my_axum_handler_6).get(my_axum_handler_7));

// ...
}
}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.