Skip to content

Commit af7dd28

Browse files
Rust: Do not treat the early return of ? as returning the unwrapped value
1 parent 2864ac8 commit af7dd28

7 files changed

Lines changed: 90 additions & 3 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The early return taken by the `?` operator no longer makes the unwrapped value of `x?` flow out of the enclosing function. This fixes false positive results from queries such as `rust/cleartext-logging`, where a sensitive value read with `?` was reported as part of an unrelated return value.

‎rust/ql/lib/codeql/rust/dataflow/internal/Node.qll‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,13 @@ abstract class ReturnNode extends Node {
473473
}
474474

475475
final class ExprReturnNode extends ExprNode, ReturnNode {
476-
ExprReturnNode() { n.getACfgNode().getASuccessor() instanceof AnnotatedExitCfgNode }
476+
ExprReturnNode() {
477+
exists(SuccessorType t | n.getACfgNode().getASuccessor(t) instanceof AnnotatedExitCfgNode |
478+
// The early return of `x?` returns the `Err` or `None` held by `x`, which is
479+
// a different value from the one `x?` evaluates to
480+
not (n instanceof TryExpr and t instanceof ReturnSuccessor)
481+
)
482+
}
477483

478484
override ReturnKind getKind() { result = TNormalReturnKind() }
479485
}

‎rust/ql/test/library-tests/dataflow/global/inline-flow.expected‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
models
22
| 1 | Summary: <& as core::ops::deref::Deref>::deref; Argument[self].Reference; ReturnValue; value |
33
| 2 | Summary: <core::ops::arith::Mul>::mul; Argument[self,0]; ReturnValue; taint |
4-
| 3 | Summary: futures_executor::local_pool::block_on; Argument[0]; ReturnValue; value |
4+
| 3 | Summary: <core::result::Result>::unwrap; Argument[self].Field[core::result::Result::Ok(0)]; ReturnValue; value |
5+
| 4 | Summary: futures_executor::local_pool::block_on; Argument[0]; ReturnValue; value |
56
edges
67
| main.rs:12:28:14:1 | { ... } | main.rs:17:13:17:23 | get_data(...) | provenance | |
78
| main.rs:13:5:13:13 | source(...) | main.rs:12:28:14:1 | { ... } | provenance | |
@@ -201,7 +202,7 @@ edges
201202
| main.rs:348:9:348:9 | c | main.rs:350:10:350:16 | await b | provenance | |
202203
| main.rs:354:9:354:9 | a | main.rs:355:10:355:10 | a | provenance | |
203204
| main.rs:354:13:354:55 | ...::block_on(...) | main.rs:354:9:354:9 | a | provenance | |
204-
| main.rs:354:41:354:54 | async_source(...) | main.rs:354:13:354:55 | ...::block_on(...) | provenance | MaD:3 |
205+
| main.rs:354:41:354:54 | async_source(...) | main.rs:354:13:354:55 | ...::block_on(...) | provenance | MaD:4 |
205206
| main.rs:366:44:368:9 | { ... } | main.rs:403:18:403:38 | t.get_double_number() | provenance | |
206207
| main.rs:366:44:368:9 | { ... } | main.rs:407:18:407:50 | ...::get_double_number(...) | provenance | |
207208
| main.rs:367:13:367:29 | self.get_number() | main.rs:367:13:367:33 | ... * ... | provenance | MaD:2 |
@@ -233,6 +234,14 @@ edges
233234
| main.rs:433:32:433:43 | STATIC_VALUE | main.rs:433:17:433:28 | static_value | provenance | |
234235
| main.rs:436:13:436:24 | STATIC_VALUE | main.rs:427:5:427:46 | static STATIC_VALUE | provenance | |
235236
| main.rs:436:28:436:37 | source(...) | main.rs:436:13:436:24 | STATIC_VALUE | provenance | |
237+
| main.rs:466:55:469:5 | { ... } [Ok] | main.rs:473:14:473:39 | unwrapped_value_returned(...) [Ok] | provenance | |
238+
| main.rs:467:13:467:13 | a [Ok, Ok] | main.rs:468:9:468:9 | a [Ok, Ok] | provenance | |
239+
| main.rs:467:48:467:65 | Ok(...) [Ok, Ok] | main.rs:467:13:467:13 | a [Ok, Ok] | provenance | |
240+
| main.rs:467:51:467:64 | Ok(...) [Ok] | main.rs:467:48:467:65 | Ok(...) [Ok, Ok] | provenance | |
241+
| main.rs:467:54:467:63 | source(...) | main.rs:467:51:467:64 | Ok(...) [Ok] | provenance | |
242+
| main.rs:468:9:468:9 | a [Ok, Ok] | main.rs:468:9:468:10 | TryExpr [Ok] | provenance | |
243+
| main.rs:468:9:468:10 | TryExpr [Ok] | main.rs:466:55:469:5 | { ... } [Ok] | provenance | |
244+
| main.rs:473:14:473:39 | unwrapped_value_returned(...) [Ok] | main.rs:473:14:473:48 | ... .unwrap() | provenance | MaD:3 |
236245
nodes
237246
| main.rs:12:28:14:1 | { ... } | semmle.label | { ... } |
238247
| main.rs:13:5:13:13 | source(...) | semmle.label | source(...) |
@@ -488,6 +497,15 @@ nodes
488497
| main.rs:436:13:436:24 | STATIC_VALUE | semmle.label | STATIC_VALUE |
489498
| main.rs:436:28:436:37 | source(...) | semmle.label | source(...) |
490499
| main.rs:437:18:437:29 | STATIC_VALUE | semmle.label | STATIC_VALUE |
500+
| main.rs:466:55:469:5 | { ... } [Ok] | semmle.label | { ... } [Ok] |
501+
| main.rs:467:13:467:13 | a [Ok, Ok] | semmle.label | a [Ok, Ok] |
502+
| main.rs:467:48:467:65 | Ok(...) [Ok, Ok] | semmle.label | Ok(...) [Ok, Ok] |
503+
| main.rs:467:51:467:64 | Ok(...) [Ok] | semmle.label | Ok(...) [Ok] |
504+
| main.rs:467:54:467:63 | source(...) | semmle.label | source(...) |
505+
| main.rs:468:9:468:9 | a [Ok, Ok] | semmle.label | a [Ok, Ok] |
506+
| main.rs:468:9:468:10 | TryExpr [Ok] | semmle.label | TryExpr [Ok] |
507+
| main.rs:473:14:473:39 | unwrapped_value_returned(...) [Ok] | semmle.label | unwrapped_value_returned(...) [Ok] |
508+
| main.rs:473:14:473:48 | ... .unwrap() | semmle.label | ... .unwrap() |
491509
subpaths
492510
| main.rs:38:16:38:24 | source(...) | main.rs:26:28:26:33 | ...: i64 | main.rs:26:17:26:25 | SelfParam [Return] [&ref, MyStruct] | main.rs:38:5:38:5 | [post] a [MyStruct] |
493511
| main.rs:39:10:39:10 | a [MyStruct] | main.rs:30:17:30:21 | SelfParam [&ref, MyStruct] | main.rs:30:31:32:5 | { ... } | main.rs:39:10:39:21 | a.get_data() |
@@ -555,3 +573,4 @@ testFailures
555573
| main.rs:434:18:434:29 | static_value | main.rs:436:28:436:37 | source(...) | main.rs:434:18:434:29 | static_value | $@ | main.rs:436:28:436:37 | source(...) | source(...) |
556574
| main.rs:437:18:437:29 | STATIC_VALUE | main.rs:427:36:427:45 | source(...) | main.rs:437:18:437:29 | STATIC_VALUE | $@ | main.rs:427:36:427:45 | source(...) | source(...) |
557575
| main.rs:437:18:437:29 | STATIC_VALUE | main.rs:436:28:436:37 | source(...) | main.rs:437:18:437:29 | STATIC_VALUE | $@ | main.rs:436:28:436:37 | source(...) | source(...) |
576+
| main.rs:473:14:473:48 | ... .unwrap() | main.rs:467:54:467:63 | source(...) | main.rs:473:14:473:48 | ... .unwrap() | $@ | main.rs:467:54:467:63 | source(...) | source(...) |

‎rust/ql/test/library-tests/dataflow/global/main.rs‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,3 +454,22 @@ fn main() {
454454
test_operator_overloading();
455455
test_async_await();
456456
}
457+
458+
mod try_operator {
459+
use super::{sink, source};
460+
461+
fn unwrapped_value_not_returned() -> Option<i64> {
462+
let _a = Some(source(50))?;
463+
Some(0)
464+
}
465+
466+
fn unwrapped_value_returned() -> Result<i64, i64> {
467+
let a: Result<Result<i64, i64>, i64> = Ok(Ok(source(51)));
468+
a?
469+
}
470+
471+
fn test_try_operator() {
472+
sink(unwrapped_value_not_returned().unwrap());
473+
sink(unwrapped_value_returned().unwrap()); // $ hasValueFlow=51
474+
}
475+
}

‎rust/ql/test/library-tests/dataflow/global/viableCallable.expected‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,11 @@
145145
| main.rs:452:5:452:25 | data_through_method(...) | main.rs:187:1:199:1 | fn data_through_method |
146146
| main.rs:454:5:454:31 | test_operator_overloading(...) | main.rs:256:1:298:1 | fn test_operator_overloading |
147147
| main.rs:455:5:455:22 | test_async_await(...) | main.rs:353:1:358:1 | fn test_async_await |
148+
| main.rs:462:23:462:32 | source(...) | main.rs:1:1:3:1 | fn source |
149+
| main.rs:467:54:467:63 | source(...) | main.rs:1:1:3:1 | fn source |
150+
| main.rs:472:9:472:53 | sink(...) | main.rs:5:1:7:1 | fn sink |
151+
| main.rs:472:14:472:43 | unwrapped_value_not_returned(...) | main.rs:461:5:464:5 | fn unwrapped_value_not_returned |
152+
| main.rs:472:14:472:52 | ... .unwrap() | {EXTERNAL LOCATION} | [summarized] fn unwrap |
153+
| main.rs:473:9:473:49 | sink(...) | main.rs:5:1:7:1 | fn sink |
154+
| main.rs:473:14:473:39 | unwrapped_value_returned(...) | main.rs:466:5:469:5 | fn unwrapped_value_returned |
155+
| main.rs:473:14:473:48 | ... .unwrap() | {EXTERNAL LOCATION} | [summarized] fn unwrap |

‎rust/ql/test/query-tests/security/CWE-312/CleartextLogging.expected‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
| test_logging.rs:245:20:245:65 | ... .as_bytes() | test_logging.rs:245:46:245:53 | password | test_logging.rs:245:20:245:65 | ... .as_bytes() | This operation writes $@ to a log file. | test_logging.rs:245:46:245:53 | password | password |
5656
| test_logging.rs:248:15:248:60 | ... .as_bytes() | test_logging.rs:248:41:248:48 | password | test_logging.rs:248:15:248:60 | ... .as_bytes() | This operation writes $@ to a log file. | test_logging.rs:248:41:248:48 | password | password |
5757
| test_logging.rs:251:15:251:60 | ... .as_bytes() | test_logging.rs:251:41:251:48 | password | test_logging.rs:251:15:251:60 | ... .as_bytes() | This operation writes $@ to a log file. | test_logging.rs:251:41:251:48 | password | password |
58+
| test_logging.rs:290:11:290:31 | MacroExpr | test_logging.rs:289:17:289:39 | get_password_or_error(...) | test_logging.rs:290:11:290:31 | MacroExpr | This operation writes $@ to a log file. | test_logging.rs:289:17:289:39 | get_password_or_error(...) | get_password_or_error(...) |
5859
edges
5960
| test_logging.rs:42:28:42:35 | password | test_logging.rs:42:12:42:35 | MacroExpr | provenance | Sink:MaD:11 |
6061
| test_logging.rs:43:28:43:35 | password | test_logging.rs:43:12:43:35 | MacroExpr | provenance | Sink:MaD:11 |
@@ -225,6 +226,8 @@ edges
225226
| test_logging.rs:251:23:251:48 | MacroExpr | test_logging.rs:251:23:251:48 | ...::format(...) | provenance | MaD:20 |
226227
| test_logging.rs:251:23:251:48 | { ... } | test_logging.rs:251:23:251:48 | ...::must_use(...) | provenance | MaD:21 |
227228
| test_logging.rs:251:41:251:48 | password | test_logging.rs:251:23:251:48 | MacroExpr | provenance | |
229+
| test_logging.rs:289:9:289:13 | value | test_logging.rs:290:11:290:31 | MacroExpr | provenance | Sink:MaD:11 |
230+
| test_logging.rs:289:17:289:39 | get_password_or_error(...) | test_logging.rs:289:9:289:13 | value | provenance | |
228231
models
229232
| 1 | Sink: <core::option::Option as log_err::LogErrOption>::log_expect; Argument[0]; log-injection |
230233
| 2 | Sink: <core::option::Option>::expect; Argument[0]; log-injection |
@@ -462,4 +465,7 @@ nodes
462465
| test_logging.rs:251:23:251:48 | MacroExpr | semmle.label | MacroExpr |
463466
| test_logging.rs:251:23:251:48 | { ... } | semmle.label | { ... } |
464467
| test_logging.rs:251:41:251:48 | password | semmle.label | password |
468+
| test_logging.rs:289:9:289:13 | value | semmle.label | value |
469+
| test_logging.rs:289:17:289:39 | get_password_or_error(...) | semmle.label | get_password_or_error(...) |
470+
| test_logging.rs:290:11:290:31 | MacroExpr | semmle.label | MacroExpr |
465471
subpaths

‎rust/ql/test/query-tests/security/CWE-312/test_logging.rs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,30 @@ fn test_collections(harmless: String) {
271271
password_queue.make_contiguous();
272272
}
273273

274+
fn get_password_or_error() -> Result<String, std::io::Error> {
275+
Ok("123456".to_string())
276+
}
277+
278+
#[derive(Debug)]
279+
struct Outcome {
280+
count: usize,
281+
}
282+
283+
fn count_entries() -> Result<Outcome, std::io::Error> {
284+
let _p = get_password_or_error()?;
285+
Ok(Outcome { count: 1 })
286+
}
287+
288+
fn test_try_operator() -> Result<(), std::io::Error> {
289+
let value = get_password_or_error()?; // $ Source[rust/cleartext-logging]=try
290+
info!("message = {}", value); // $ Alert[rust/cleartext-logging]=try
291+
292+
let outcome = count_entries()?;
293+
info!("message = {:?}", outcome); // (the outcome does not contain the password)
294+
295+
Ok(())
296+
}
297+
274298
fn main() {
275299
simple_logger::SimpleLogger::new().init().unwrap();
276300

@@ -281,4 +305,5 @@ fn main() {
281305
);
282306
test_std("123456".to_string(), 0, None);
283307
test_collections("harmless".to_string());
308+
_ = test_try_operator();
284309
}

0 commit comments

Comments
 (0)