-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Rust: TaintedPath query #18960
Rust: TaintedPath query #18960
Conversation
QHelp previews: rust/ql/src/queries/security/CWE-022/TaintedPath.qhelpUncontrolled data used in path expressionAccessing paths controlled by users can allow an attacker to access unexpected resources. This can result in sensitive information being revealed or deleted, or an attacker being able to influence behavior by modifying unexpected files. Paths that are naively constructed from data controlled by a user may be absolute paths, or may contain unexpected special characters such as "..". Such a path could point anywhere on the file system. RecommendationValidate user input before using it to construct a file path. Common validation methods include checking that the normalized path is relative and does not contain any ".." components, or checking that the path is contained within a safe folder. The method you should use depends on how the path is used in the application, and whether the path should be a single path component. If the path should be a single path component (such as a file name), you can check for the existence of any path separators ("/" or "\"), or ".." sequences in the input, and reject the input if any are found. Note that removing "../" sequences is not sufficient, since the input could still contain a path separator followed by "..". For example, the input ".../...//" would still result in the string "../" if only "../" sequences are removed. Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that the user input matches one of these patterns. ExampleIn this example, a user-provided file name is read from a HTTP request and then used to access a file and send it back to the user. However, a malicious user could enter a file name anywhere on the file system, such as "/etc/passwd" or "../../../etc/passwd". use poem::{error::InternalServerError, handler, web::Query, Result};
use std::{fs, path::PathBuf};
#[handler]
fn tainted_path_handler(Query(file_name): Query<String>) -> Result<String> {
let file_path = PathBuf::from(file_name);
// BAD: This could read any file on the filesystem.
fs::read_to_string(file_path).map_err(InternalServerError)
} If the input should only be a file name, you can check that it doesn't contain any path separators or ".." sequences. use poem::{error::InternalServerError, handler, http::StatusCode, web::Query, Error, Result};
use std::{fs, path::PathBuf};
#[handler]
fn tainted_path_handler(Query(file_name): Query<String>) -> Result<String> {
// GOOD: ensure that the filename has no path separators or parent directory references
if file_name.contains("..") || file_name.contains("/") || file_name.contains("\\") {
return Err(Error::from_status(StatusCode::BAD_REQUEST));
}
let file_path = PathBuf::from(file_name);
fs::read_to_string(file_path).map_err(InternalServerError)
} If the input should be within a specific directory, you can check that the resolved path is still contained within that directory. use poem::{error::InternalServerError, handler, http::StatusCode, web::Query, Error, Result};
use std::{env::home_dir, fs, path::PathBuf};
#[handler]
fn tainted_path_handler(Query(file_path): Query<String>) -> Result<String, Error> {
let public_path = home_dir().unwrap().join("public");
let file_path = public_path.join(PathBuf::from(file_path));
let file_path = file_path.canonicalize().unwrap();
// GOOD: ensure that the path stays within the public folder
if !file_path.starts_with(public_path) {
return Err(Error::from_status(StatusCode::BAD_REQUEST));
}
fs::read_to_string(file_path).map_err(InternalServerError)
} References |
8890b12
to
4bf8103
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request adds examples demonstrating both secure and insecure handling of file path tainting in Rust.
- Introduces a secure example that validates a file name against path traversal in TaintedPathGoodNormalize.rs.
- Implements a secure example that ensures file access remains within a designated public folder in TaintedPathGoodFolder.rs.
- Provides an insecure example in TaintedPath.rs to illustrate unsafe file reading.
- Updates model files to include new taint propagation rules.
Reviewed Changes
Copilot reviewed 9 out of 23 changed files in this pull request and generated no comments.
Show a summary per file
File | Description |
---|---|
rust/ql/src/queries/security/CWE-022/examples/TaintedPathGoodNormalize.rs | Adds a secure file read example with filename validation. |
rust/ql/src/queries/security/CWE-022/examples/TaintedPathGoodFolder.rs | Adds a secure file read example ensuring file stays within a public folder. |
rust/ql/src/queries/security/CWE-022/examples/TaintedPath.rs | Adds an insecure file read example to demonstrate tainted file path usage. |
rust/ql/lib/codeql/rust/frameworks/stdlib/fs.model.yml | Updates the model to include path-injection taint propagation for fs::read_to_string. |
rust/ql/lib/codeql/rust/frameworks/stdlib/lang-core.model.yml | Adds a taint propagation rule for Result::unwrap. |
Files not reviewed (14)
- rust/ql/integration-tests/hello-project/summary.expected: Language not supported
- rust/ql/integration-tests/hello-workspace/summary.cargo.expected: Language not supported
- rust/ql/integration-tests/hello-workspace/summary.rust-project.expected: Language not supported
- rust/ql/lib/codeql/rust/Concepts.qll: Language not supported
- rust/ql/lib/codeql/rust/Frameworks.qll: Language not supported
- rust/ql/lib/codeql/rust/dataflow/DataFlow.qll: Language not supported
- rust/ql/lib/codeql/rust/dataflow/internal/SsaImpl.qll: Language not supported
- rust/ql/lib/codeql/rust/frameworks/Poem.qll: Language not supported
- rust/ql/lib/codeql/rust/frameworks/stdlib/Stdlib.qll: Language not supported
- rust/ql/lib/codeql/rust/security/TaintedPathExtensions.qll: Language not supported
- rust/ql/src/queries/security/CWE-022/TaintedPath.qhelp: Language not supported
- rust/ql/src/queries/security/CWE-022/TaintedPath.ql: Language not supported
- rust/ql/test/query-tests/security/CWE-022/TaintedPath.expected: Language not supported
- rust/ql/test/query-tests/security/CWE-022/TaintedPath.qlref: Language not supported
Comments suppressed due to low confidence (1)
rust/ql/src/queries/security/CWE-022/examples/TaintedPathGoodFolder.rs:7
- The variable 'file_path' shadows the function parameter; use a distinct variable name for clarity.
let file_path = public_path.join(PathBuf::from(file_path));
Tip: Copilot code review supports C#, Go, Java, JavaScript, Markdown, Python, Ruby and TypeScript, with more languages coming soon. Learn more
11510a9
to
2daef7e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've asked some questions, query and qhelp otherwise LGTM. The examples in the .qhelp
are clear and helpful. 👍
Please do a DCA run and request a docs review when you're ready.
@geoffw0 The QHelp is exactly the same as C# and Java, the only difference are the examples which are meant to resemble the C#/Java variants as closely as possible. See for example: https://codeql.github.com/codeql-query-help/csharp/cs-path-injection/ . |
586759f
to
0724151
Compare
/** | ||
* A step in a flow summary defined using `OptionalStep[name]`. An `OptionalStep` is "opt-in", which means | ||
* that by default the step is not present in the flow summary and needs to be explicitly enabled by defining | ||
* an additional flow step. | ||
*/ |
Check warning
Code scanning / CodeQL
Predicate QLDoc style. Warning
/** | ||
* A step in a flow summary defined using `OptionalBarrier[name]`. An `OptionalBarrier` is "opt-out", by default | ||
* data can flow freely through the step. Flow through the step can be explicity blocked by defining its node as a barrier. | ||
*/ |
Check warning
Code scanning / CodeQL
Predicate QLDoc style. Warning
Python 👍 |
0724151
to
2804c13
Compare
e3761c8
to
b10a296
Compare
All of my concerns have been addressed, but I don't speak for other people's comments. I've taken the liberty of adding the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aibaars - Approving on behalf of Docs ⚡
I've left a couple of minor comments due to the repetition of the same verb in the same sentence. Would be good to find a synonym, but this can be merged with the sentences as they are 😅
"qhelp.dtd"> | ||
<qhelp> | ||
<overview> | ||
<p>Accessing paths controlled by users can allow an attacker to access unexpected resources. This |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM, just highlighting that we're using the verb "to access" twice in this sentence. Perhaps we could use a synonym to avoid repetition?
Not 100% happy with my suggestion though 🤔
<p>Accessing paths controlled by users can allow an attacker to access unexpected resources. This | |
<p>Accessing paths controlled by users can allow an attacker to reach unexpected resources. This |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with the comment, but I'm not 100% sold on the suggestion either. I just checked and the 7 other languages use the same phrase with the double use of "access". So we should either change all 8 QL and QHelp files consistently or none.
If you feel strongly about the need to avoid the double use of access then I'd be happy to address this in a separate pull request for all languages.
I don't really like "reach", it is even more vague than "access" . A clearer phrase would be ... to read, write or modify unexpected resources
, but that is perhaps a bit too long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave the verbiage as-is, if it's also used in other queries 👍🏻 😅
@@ -0,0 +1,99 @@ | |||
/** | |||
* @name Uncontrolled data used in path expression | |||
* @description Accessing paths influenced by users can allow an attacker to access unexpected resources. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor suggestions, otherwise LGTM
rust/ql/lib/codeql/rust/Concepts.qll
Outdated
/** | ||
* A data-flow node that performs path normalization. This is often needed in order | ||
* to safely access paths. | ||
*/ | ||
class PathNormalization extends DataFlow::Node instanceof PathNormalization::Range { | ||
/** Gets an argument to this path normalization that is interpreted as a path. */ | ||
DataFlow::Node getPathArg() { result = super.getPathArg() } | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | |
* A data-flow node that performs path normalization. This is often needed in order | |
* to safely access paths. | |
*/ | |
class PathNormalization extends DataFlow::Node instanceof PathNormalization::Range { | |
/** Gets an argument to this path normalization that is interpreted as a path. */ | |
DataFlow::Node getPathArg() { result = super.getPathArg() } | |
} | |
final class PathNormalization = PathNormalization::Range; |
* be treated as an ordinary content component. | ||
*/ | ||
cached | ||
predicate isSpecialContentSet(ContentSet cs) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be private and non-cached?
import TaintedPathFlow::PathGraph | ||
private import codeql.rust.Concepts | ||
|
||
abstract private class NormalizationState extends string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think it would be cleaner to use a newtype
to represent the state.
@@ -0,0 +1,3 @@ | |||
qltest_cargo_check: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this line is needed, it should be inherited from rust/ql/test/options.yml
?
cca001a
to
bf76505
Compare
No description provided.