diff --git a/rust/ql/lib/codeql/rust/frameworks/rusqlite.model.yml b/rust/ql/lib/codeql/rust/frameworks/rusqlite.model.yml new file mode 100644 index 000000000000..4b8adcc16e9c --- /dev/null +++ b/rust/ql/lib/codeql/rust/frameworks/rusqlite.model.yml @@ -0,0 +1,20 @@ +extensions: + - addsTo: + pack: codeql/rust-all + extensible: sinkModel + data: + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::execute", "Argument[0]", "sql-injection", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::execute_batch", "Argument[0]", "sql-injection", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::prepare", "Argument[0]", "sql-injection", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::prepare_with_flags", "Argument[0]", "sql-injection", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::query_row", "Argument[0]", "sql-injection", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::query_row_and_then", "Argument[0]", "sql-injection", "manual"] + + - addsTo: + pack: codeql/rust-all + extensible: sourceModel + data: + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::get", "ReturnValue.Variant[crate::result::Result::Ok(0)]", "database", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::get_unwrap", "ReturnValue", "database", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::get_ref", "ReturnValue.Variant[crate::result::Result::Ok(0)]", "database", "manual"] + - ["repo:https://github.com/rusqlite/rusqlite:rusqlite", "::get_ref_unwrap", "ReturnValue", "database", "manual"] diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.expected b/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.ql b/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.ql new file mode 100644 index 000000000000..aca6ded23757 --- /dev/null +++ b/rust/ql/test/library-tests/frameworks/rusqlite/Rusqlite.ql @@ -0,0 +1,28 @@ +import rust +import codeql.rust.security.SqlInjectionExtensions +import codeql.rust.Concepts +import utils.test.InlineExpectationsTest + +module RusqliteTest implements TestSig { + string getARelevantTag() { result = ["sql-sink", "database-read"] } + + predicate hasActualResult(Location location, string element, string tag, string value) { + exists(SqlInjection::Sink sink | + location = sink.getLocation() and + location.getFile().getBaseName() != "" and + element = sink.toString() and + tag = "sql-sink" and + value = "" + ) + or + exists(ModeledDatabaseSource sink | + location = sink.getLocation() and + location.getFile().getBaseName() != "" and + element = sink.toString() and + tag = "database-read" and + value = "" + ) + } +} + +import MakeTest diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/cargo.toml.manual b/rust/ql/test/library-tests/frameworks/rusqlite/cargo.toml.manual new file mode 100644 index 000000000000..c4c57f4b1bac --- /dev/null +++ b/rust/ql/test/library-tests/frameworks/rusqlite/cargo.toml.manual @@ -0,0 +1,13 @@ +[workspace] + +[package] +name = "rusqlite-test" +version = "0.1.0" +edition = "2021" + +[dependencies] +rusqlite = { version = "0.33", features = ["bundled"] } + +[[bin]] +name = "rusqlite" +path = "./main.rs" diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/main.rs b/rust/ql/test/library-tests/frameworks/rusqlite/main.rs new file mode 100644 index 000000000000..438e55920ec1 --- /dev/null +++ b/rust/ql/test/library-tests/frameworks/rusqlite/main.rs @@ -0,0 +1,50 @@ + +use rusqlite::Connection; + +#[derive(Debug)] +struct Person { + id: i32, + name: String, + age: i32, +} + +fn main() -> Result<(), Box> { + // Get input from CLI + let args: Vec = std::env::args().collect(); + let name = &args[1]; + let age = &args[2]; + + let connection = Connection::open_in_memory()?; + + connection.execute( // $ sql-sink + "CREATE TABLE person ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name VARCHAR NOT NULL, + age INT NOT NULL + )", + (), + )?; + + let query = format!("INSERT INTO person (name, age) VALUES ('{}', '{}')", name, age); + + connection.execute(&query, ())?; // $ sql-sink + + let person = connection.query_row(&query, (), |row| { // $ sql-sink + Ok(Person { + id: row.get(0)?, // $ database-read + name: row.get(1)?, // $ database-read + age: row.get(2)?, // $ database-read + }) + })?; + + let mut stmt = connection.prepare("SELECT id, name, age FROM person")?; // $ sql-sink + let people = stmt.query_map([], |row| { + Ok(Person { + id: row.get_unwrap(0), // $ database-read + name: row.get_unwrap(1), // $ database-read + age: row.get_unwrap(2), // $ database-read + }) + })?; + + Ok(()) +} \ No newline at end of file diff --git a/rust/ql/test/library-tests/frameworks/rusqlite/options.yml b/rust/ql/test/library-tests/frameworks/rusqlite/options.yml new file mode 100644 index 000000000000..ee37af2b00db --- /dev/null +++ b/rust/ql/test/library-tests/frameworks/rusqlite/options.yml @@ -0,0 +1,3 @@ +qltest_cargo_check: true +qltest_dependencies: + - rusqlite = { version = "0.33", features = ["bundled"] }