mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
Merge branch 'main' into scinit
This commit is contained in:
@@ -5,6 +5,7 @@ ql/rust/ql/src/queries/diagnostics/ExtractedFiles.ql
|
||||
ql/rust/ql/src/queries/diagnostics/ExtractionErrors.ql
|
||||
ql/rust/ql/src/queries/diagnostics/ExtractionWarnings.ql
|
||||
ql/rust/ql/src/queries/diagnostics/SsaConsistencyCounts.ql
|
||||
ql/rust/ql/src/queries/diagnostics/TypeInferenceConsistencyCounts.ql
|
||||
ql/rust/ql/src/queries/diagnostics/UnextractedElements.ql
|
||||
ql/rust/ql/src/queries/diagnostics/UnresolvedMacroCalls.ql
|
||||
ql/rust/ql/src/queries/security/CWE-020/RegexInjection.ql
|
||||
@@ -12,6 +13,7 @@ ql/rust/ql/src/queries/security/CWE-022/TaintedPath.ql
|
||||
ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
|
||||
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
|
||||
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
|
||||
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
|
||||
ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
|
||||
ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql
|
||||
|
||||
@@ -5,6 +5,7 @@ ql/rust/ql/src/queries/diagnostics/ExtractedFiles.ql
|
||||
ql/rust/ql/src/queries/diagnostics/ExtractionErrors.ql
|
||||
ql/rust/ql/src/queries/diagnostics/ExtractionWarnings.ql
|
||||
ql/rust/ql/src/queries/diagnostics/SsaConsistencyCounts.ql
|
||||
ql/rust/ql/src/queries/diagnostics/TypeInferenceConsistencyCounts.ql
|
||||
ql/rust/ql/src/queries/diagnostics/UnextractedElements.ql
|
||||
ql/rust/ql/src/queries/diagnostics/UnresolvedMacroCalls.ql
|
||||
ql/rust/ql/src/queries/security/CWE-020/RegexInjection.ql
|
||||
@@ -12,6 +13,7 @@ ql/rust/ql/src/queries/security/CWE-022/TaintedPath.ql
|
||||
ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
|
||||
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
|
||||
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
|
||||
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
|
||||
ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
|
||||
ql/rust/ql/src/queries/security/CWE-696/BadCtorInitialization.ql
|
||||
|
||||
@@ -5,6 +5,7 @@ ql/rust/ql/src/queries/diagnostics/ExtractedFiles.ql
|
||||
ql/rust/ql/src/queries/diagnostics/ExtractionErrors.ql
|
||||
ql/rust/ql/src/queries/diagnostics/ExtractionWarnings.ql
|
||||
ql/rust/ql/src/queries/diagnostics/SsaConsistencyCounts.ql
|
||||
ql/rust/ql/src/queries/diagnostics/TypeInferenceConsistencyCounts.ql
|
||||
ql/rust/ql/src/queries/diagnostics/UnextractedElements.ql
|
||||
ql/rust/ql/src/queries/diagnostics/UnresolvedMacroCalls.ql
|
||||
ql/rust/ql/src/queries/security/CWE-020/RegexInjection.ql
|
||||
@@ -12,6 +13,7 @@ ql/rust/ql/src/queries/security/CWE-022/TaintedPath.ql
|
||||
ql/rust/ql/src/queries/security/CWE-089/SqlInjection.ql
|
||||
ql/rust/ql/src/queries/security/CWE-311/CleartextTransmission.ql
|
||||
ql/rust/ql/src/queries/security/CWE-312/CleartextLogging.ql
|
||||
ql/rust/ql/src/queries/security/CWE-312/CleartextStorageDatabase.ql
|
||||
ql/rust/ql/src/queries/security/CWE-327/BrokenCryptoAlgorithm.ql
|
||||
ql/rust/ql/src/queries/security/CWE-328/WeakSensitiveDataHashing.ql
|
||||
ql/rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSize.ql
|
||||
|
||||
@@ -120,7 +120,7 @@ module Stages {
|
||||
or
|
||||
exists(resolvePath(_))
|
||||
or
|
||||
exists(any(ItemNode i).getASuccessorFull(_))
|
||||
exists(any(ItemNode i).getASuccessor(_))
|
||||
or
|
||||
exists(any(ItemNode i).getASuccessorRec(_))
|
||||
or
|
||||
|
||||
@@ -194,7 +194,7 @@ abstract class ItemNode extends Locatable {
|
||||
* both are included
|
||||
*/
|
||||
cached
|
||||
ItemNode getASuccessorFull(string name) {
|
||||
ItemNode getASuccessor(string name) {
|
||||
Stages::PathResolutionStage::ref() and
|
||||
result = this.getASuccessorRec(name)
|
||||
or
|
||||
@@ -261,27 +261,6 @@ abstract class ItemNode extends Locatable {
|
||||
item instanceof TypeParamItemNode
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate hasSourceFunction(string name) {
|
||||
this.getASuccessorFull(name).(Function).fromSource()
|
||||
}
|
||||
|
||||
/** Gets a successor named `name` of this item, if any. */
|
||||
pragma[nomagic]
|
||||
ItemNode getASuccessor(string name) {
|
||||
result = this.getASuccessorFull(name) and
|
||||
(
|
||||
// when a function exists in both source code and in library code, it is because
|
||||
// we also extracted the source code as library code, and hence we only want
|
||||
// the function from source code
|
||||
result.fromSource()
|
||||
or
|
||||
not result instanceof Function
|
||||
or
|
||||
not this.hasSourceFunction(name)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if this item has a canonical path belonging to the crate `c`. */
|
||||
abstract predicate hasCanonicalPath(Crate c);
|
||||
|
||||
@@ -345,7 +324,7 @@ abstract private class ModuleLikeNode extends ItemNode {
|
||||
private class SourceFileItemNode extends ModuleLikeNode, SourceFile {
|
||||
pragma[nomagic]
|
||||
ModuleLikeNode getSuper() {
|
||||
result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessorFull("super")
|
||||
result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super")
|
||||
}
|
||||
|
||||
override string getName() { result = "(source file)" }
|
||||
@@ -393,7 +372,7 @@ class CrateItemNode extends ItemNode instanceof Crate {
|
||||
predicate isPotentialDollarCrateTarget() {
|
||||
exists(string name, RelevantPath p |
|
||||
p.isDollarCrateQualifiedPath(name) and
|
||||
exists(this.getASuccessorFull(name))
|
||||
exists(this.getASuccessor(name))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1245,8 +1224,8 @@ private predicate unqualifiedPathLookup(ItemNode encl, string name, Namespace ns
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private ItemNode getASuccessorFull(ItemNode pred, string name, Namespace ns) {
|
||||
result = pred.getASuccessorFull(name) and
|
||||
private ItemNode getASuccessor(ItemNode pred, string name, Namespace ns) {
|
||||
result = pred.getASuccessor(name) and
|
||||
ns = result.getNamespace()
|
||||
}
|
||||
|
||||
@@ -1281,7 +1260,7 @@ private predicate keywordLookup(ItemNode encl, string name, RelevantPath p) {
|
||||
pragma[nomagic]
|
||||
private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns) {
|
||||
exists(ItemNode encl, string name |
|
||||
result = getASuccessorFull(encl, name, ns) and not encl.excludedLocally(name, result)
|
||||
result = getASuccessor(encl, name, ns) and not encl.excludedLocally(name, result)
|
||||
|
|
||||
unqualifiedPathLookup(encl, name, ns, p)
|
||||
or
|
||||
@@ -1306,7 +1285,7 @@ private ItemNode resolvePath0(RelevantPath path, Namespace ns) {
|
||||
or
|
||||
exists(ItemNode q, string name |
|
||||
q = resolvePathQualifier(path, name) and
|
||||
result = getASuccessorFull(q, name, ns) and
|
||||
result = getASuccessor(q, name, ns) and
|
||||
not q.excludedExternally(name, result)
|
||||
)
|
||||
or
|
||||
@@ -1383,12 +1362,12 @@ private ItemNode resolveUseTreeListItem(Use use, UseTree tree, RelevantPath path
|
||||
mid = resolveUseTreeListItem(use, midTree) and
|
||||
tree = midTree.getUseTreeList().getAUseTree() and
|
||||
isUseTreeSubPathUnqualified(tree, path, pragma[only_bind_into](name)) and
|
||||
result = mid.getASuccessorFull(pragma[only_bind_into](name))
|
||||
result = mid.getASuccessor(pragma[only_bind_into](name))
|
||||
)
|
||||
or
|
||||
exists(ItemNode q, string name |
|
||||
q = resolveUseTreeListItemQualifier(use, tree, path, name) and
|
||||
result = q.getASuccessorFull(name)
|
||||
result = q.getASuccessor(name)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1418,7 +1397,7 @@ private predicate useImportEdge(Use use, string name, ItemNode item) {
|
||||
then
|
||||
exists(ItemNode encl, Namespace ns |
|
||||
encl.getADescendant() = use and
|
||||
item = getASuccessorFull(used, name, ns) and
|
||||
item = getASuccessor(used, name, ns) and
|
||||
// glob imports can be shadowed
|
||||
not declares(encl, ns, name) and
|
||||
not name = ["super", "self", "Self", "$crate", "crate"]
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Provides classes and predicates for reasoning about cleartext storage
|
||||
* of sensitive information in a database.
|
||||
*/
|
||||
|
||||
import rust
|
||||
private import codeql.rust.dataflow.DataFlow
|
||||
private import codeql.rust.dataflow.internal.DataFlowImpl
|
||||
private import codeql.rust.security.SensitiveData
|
||||
private import codeql.rust.Concepts
|
||||
|
||||
/**
|
||||
* Provides default sources, sinks and barriers for detecting cleartext storage
|
||||
* of sensitive information in a database, as well as extension points for
|
||||
* adding your own.
|
||||
*/
|
||||
module CleartextStorageDatabase {
|
||||
/**
|
||||
* A data flow source for cleartext storage vulnerabilities.
|
||||
*/
|
||||
abstract class Source extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A data flow sink for cleartext storage vulnerabilities.
|
||||
*/
|
||||
abstract class Sink extends QuerySink::Range {
|
||||
override string getSinkType() { result = "CleartextStorageDatabase" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A barrier for cleartext storage vulnerabilities.
|
||||
*/
|
||||
abstract class Barrier extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* Sensitive data, considered as a flow source.
|
||||
*/
|
||||
private class SensitiveDataAsSource extends Source instanceof SensitiveData { }
|
||||
|
||||
/**
|
||||
* A sink for cleartext storage vulnerabilities from model data.
|
||||
* - SQL commands
|
||||
* - other database storage operations
|
||||
*/
|
||||
private class ModelsAsDataSink extends Sink {
|
||||
ModelsAsDataSink() { sinkNode(this, ["sql-injection", "database-store"]) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: newQuery
|
||||
---
|
||||
* Added a new query, `rust/cleartext-storage-database`, for detecting cases where sensitive information is stored non-encrypted in a database.
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @name Type inference inconsistency counts
|
||||
* @description Counts the number of type inference inconsistencies of each type. This query is intended for internal use.
|
||||
* @kind diagnostic
|
||||
* @id rust/diagnostics/type-inference-consistency-counts
|
||||
*/
|
||||
|
||||
private import codeql.rust.internal.TypeInferenceConsistency as Consistency
|
||||
|
||||
// see also `rust/diagnostics/type-inference-consistency`, which lists the
|
||||
// individual inconsistency results.
|
||||
from string type, int num
|
||||
where num = Consistency::getTypeInferenceInconsistencyCounts(type)
|
||||
select type, num
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
<overview>
|
||||
<p>
|
||||
Sensitive information that is stored unencrypted in a database is accessible to an attacker
|
||||
who gains access to that database. For example, the information could be accessed by any
|
||||
process or user in a rooted device, or exposed through another vulnerability.
|
||||
</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>
|
||||
Either encrypt the entire database, or ensure that each piece of sensitive information is
|
||||
encrypted before being stored. In general, decrypt sensitive information only at the point
|
||||
where it is necessary for it to be used in cleartext. Avoid storing sensitive information
|
||||
at all if you do not need to keep it.
|
||||
</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>
|
||||
The following example stores sensitive information into a database without encryption, using the
|
||||
SQLx library:
|
||||
</p>
|
||||
<sample src="CleartextStorageDatabaseBad.rs"/>
|
||||
<p>
|
||||
This is insecure because the sensitive data is stored in cleartext, making it accessible to anyone
|
||||
with access to the database.
|
||||
</p>
|
||||
<p>
|
||||
To fix this, we can either encrypt the entire database or encrypt just the sensitive data before it
|
||||
is stored. Take care to select a secure modern encryption algorithm and put suitable key management
|
||||
practices into place. In the following example, we have encrypted the sensitive data using 256-bit
|
||||
AES before storing it in the database:
|
||||
</p>
|
||||
<sample src="CleartextStorageDatabaseGood.rs"/>
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>
|
||||
OWASP Top 10:2021:
|
||||
<a href="https://owasp.org/Top10/A02_2021-Cryptographic_Failures/">A02:2021 - Cryptographic Failures</a>.
|
||||
</li>
|
||||
<li>
|
||||
OWASP:
|
||||
<a href="https://cheatsheetseries.owasp.org/cheatsheets/Key_Management_Cheat_Sheet.html">Key Management Cheat Sheet</a>.
|
||||
</li>
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @name Cleartext storage of sensitive information in a database
|
||||
* @description Storing sensitive information in a non-encrypted
|
||||
* database can expose it to an attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 7.5
|
||||
* @precision high
|
||||
* @id rust/cleartext-storage-database
|
||||
* @tags security
|
||||
* external/cwe/cwe-312
|
||||
*/
|
||||
|
||||
import rust
|
||||
import codeql.rust.dataflow.DataFlow
|
||||
import codeql.rust.dataflow.TaintTracking
|
||||
import codeql.rust.security.CleartextStorageDatabaseExtensions
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* stored in a database.
|
||||
*/
|
||||
module CleartextStorageDatabaseConfig implements DataFlow::ConfigSig {
|
||||
import CleartextStorageDatabase
|
||||
|
||||
predicate isSource(DataFlow::Node node) { node instanceof Source }
|
||||
|
||||
predicate isSink(DataFlow::Node node) { node instanceof Sink }
|
||||
|
||||
predicate isBarrier(DataFlow::Node barrier) { barrier instanceof Barrier }
|
||||
|
||||
predicate isBarrierIn(DataFlow::Node node) {
|
||||
// make sources barriers so that we only report the closest instance
|
||||
isSource(node)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
// flow from `a` to `&a`
|
||||
node2.asExpr().getExpr().(RefExpr).getExpr() = node1.asExpr().getExpr()
|
||||
}
|
||||
|
||||
predicate observeDiffInformedIncrementalMode() { any() }
|
||||
}
|
||||
|
||||
module CleartextStorageDatabaseFlow = TaintTracking::Global<CleartextStorageDatabaseConfig>;
|
||||
|
||||
import CleartextStorageDatabaseFlow::PathGraph
|
||||
|
||||
from
|
||||
CleartextStorageDatabaseFlow::PathNode sourceNode, CleartextStorageDatabaseFlow::PathNode sinkNode
|
||||
where CleartextStorageDatabaseFlow::flowPath(sourceNode, sinkNode)
|
||||
select sinkNode.getNode(), sourceNode, sinkNode,
|
||||
"This database operation may read or write unencrypted sensitive data from $@.", sourceNode,
|
||||
sourceNode.getNode().toString()
|
||||
@@ -0,0 +1,6 @@
|
||||
let query = "INSERT INTO PAYMENTDETAILS(ID, CARDNUM) VALUES(?, ?)";
|
||||
let result = sqlx::query(query)
|
||||
.bind(id)
|
||||
.bind(credit_card_number) // BAD: Cleartext storage of sensitive data in the database
|
||||
.execute(pool)
|
||||
.await?;
|
||||
@@ -0,0 +1,41 @@
|
||||
fn encrypt(text: String, encryption_key: &aes_gcm::Key<Aes256Gcm>) -> String {
|
||||
// encrypt text -> ciphertext
|
||||
let cipher = Aes256Gcm::new(&encryption_key);
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
let ciphertext = cipher.encrypt(&nonce, text.as_ref()).unwrap();
|
||||
|
||||
// append (nonce, ciphertext)
|
||||
let mut combined = nonce.to_vec();
|
||||
combined.extend(ciphertext);
|
||||
|
||||
// encode to base64 string
|
||||
BASE64_STANDARD.encode(combined)
|
||||
}
|
||||
|
||||
fn decrypt(data: String, encryption_key: &aes_gcm::Key<Aes256Gcm>) -> String {
|
||||
let cipher = Aes256Gcm::new(&encryption_key);
|
||||
|
||||
// decode base64 string
|
||||
let decoded = BASE64_STANDARD.decode(data).unwrap();
|
||||
|
||||
// split into (nonce, ciphertext)
|
||||
let nonce_size = <Aes256Gcm as AeadCore>::NonceSize::to_usize();
|
||||
let (nonce, ciphertext) = decoded.split_at(nonce_size);
|
||||
|
||||
// decrypt ciphertext -> plaintext
|
||||
let plaintext = cipher.decrypt(nonce.into(), ciphertext).unwrap();
|
||||
String::from_utf8(plaintext).unwrap()
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
let encryption_key = Aes256Gcm::generate_key(OsRng);
|
||||
|
||||
...
|
||||
|
||||
let query = "INSERT INTO PAYMENTDETAILS(ID, CARDNUM) VALUES(?, ?)";
|
||||
let result = sqlx::query(query)
|
||||
.bind(id)
|
||||
.bind(encrypt(credit_card_number, &encryption_key)) // GOOD: Encrypted storage of sensitive data in the database
|
||||
.execute(pool)
|
||||
.await?;
|
||||
@@ -32,8 +32,7 @@ class CtorAttr extends Attr {
|
||||
*/
|
||||
class StdCall extends Expr {
|
||||
StdCall() {
|
||||
this.(CallExpr).getFunction().(PathExpr).getResolvedCrateOrigin() = "lang:std" or
|
||||
this.(MethodCallExpr).getResolvedCrateOrigin() = "lang:std"
|
||||
this.(CallExprBase).getStaticTarget().getCanonicalPath().matches(["std::%", "<std::%"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ private import TaintReach
|
||||
private import codeql.rust.security.regex.RegexInjectionExtensions
|
||||
private import codeql.rust.security.AccessInvalidPointerExtensions
|
||||
private import codeql.rust.security.CleartextLoggingExtensions
|
||||
private import codeql.rust.security.CleartextStorageDatabaseExtensions
|
||||
private import codeql.rust.security.CleartextTransmissionExtensions
|
||||
private import codeql.rust.security.SqlInjectionExtensions
|
||||
private import codeql.rust.security.TaintedPathExtensions
|
||||
|
||||
@@ -1,10 +1,143 @@
|
||||
multipleCallTargets
|
||||
| test_logging.rs:42:5:42:36 | ...::max_level(...) |
|
||||
| test_logging.rs:43:5:43:36 | ...::max_level(...) |
|
||||
| test_logging.rs:44:5:44:35 | ...::max_level(...) |
|
||||
| test_logging.rs:45:5:45:36 | ...::max_level(...) |
|
||||
| test_logging.rs:46:5:46:35 | ...::max_level(...) |
|
||||
| test_logging.rs:47:5:47:48 | ...::max_level(...) |
|
||||
| test_logging.rs:50:5:50:21 | ...::max_level(...) |
|
||||
| test_logging.rs:51:5:51:36 | ...::max_level(...) |
|
||||
| test_logging.rs:52:5:52:36 | ...::max_level(...) |
|
||||
| test_logging.rs:53:5:53:46 | ...::max_level(...) |
|
||||
| test_logging.rs:54:5:54:49 | ...::max_level(...) |
|
||||
| test_logging.rs:55:5:55:34 | ...::max_level(...) |
|
||||
| test_logging.rs:56:5:56:47 | ...::max_level(...) |
|
||||
| test_logging.rs:57:5:57:34 | ...::max_level(...) |
|
||||
| test_logging.rs:58:5:58:36 | ...::max_level(...) |
|
||||
| test_logging.rs:59:5:59:54 | ...::max_level(...) |
|
||||
| test_logging.rs:60:5:60:54 | ...::max_level(...) |
|
||||
| test_logging.rs:61:5:61:55 | ...::max_level(...) |
|
||||
| test_logging.rs:64:5:64:48 | ...::max_level(...) |
|
||||
| test_logging.rs:65:5:65:48 | ...::max_level(...) |
|
||||
| test_logging.rs:66:5:66:66 | ...::max_level(...) |
|
||||
| test_logging.rs:67:5:67:66 | ...::max_level(...) |
|
||||
| test_logging.rs:68:5:68:67 | ...::max_level(...) |
|
||||
| test_logging.rs:71:5:71:47 | ...::max_level(...) |
|
||||
| test_logging.rs:72:5:72:47 | ...::max_level(...) |
|
||||
| test_logging.rs:73:5:73:50 | ...::max_level(...) |
|
||||
| test_logging.rs:74:5:74:65 | ...::max_level(...) |
|
||||
| test_logging.rs:75:5:75:51 | ...::max_level(...) |
|
||||
| test_logging.rs:76:5:76:47 | ...::max_level(...) |
|
||||
| test_logging.rs:77:5:77:48 | ...::max_level(...) |
|
||||
| test_logging.rs:77:20:77:36 | password.as_str() |
|
||||
| test_logging.rs:78:5:78:50 | ...::max_level(...) |
|
||||
| test_logging.rs:78:22:78:38 | password.as_str() |
|
||||
| test_logging.rs:81:5:81:44 | ...::max_level(...) |
|
||||
| test_logging.rs:82:5:82:44 | ...::max_level(...) |
|
||||
| test_logging.rs:83:5:83:47 | ...::max_level(...) |
|
||||
| test_logging.rs:84:5:84:62 | ...::max_level(...) |
|
||||
| test_logging.rs:85:5:85:48 | ...::max_level(...) |
|
||||
| test_logging.rs:86:5:86:44 | ...::max_level(...) |
|
||||
| test_logging.rs:88:18:88:34 | password.as_str() |
|
||||
| test_logging.rs:89:5:89:29 | ...::max_level(...) |
|
||||
| test_logging.rs:90:5:90:31 | ...::max_level(...) |
|
||||
| test_logging.rs:94:5:94:29 | ...::max_level(...) |
|
||||
| test_logging.rs:97:5:97:19 | ...::max_level(...) |
|
||||
| test_logging.rs:100:5:100:19 | ...::max_level(...) |
|
||||
| test_logging.rs:104:5:104:19 | ...::max_level(...) |
|
||||
| test_logging.rs:108:5:108:19 | ...::max_level(...) |
|
||||
| test_logging.rs:112:5:112:50 | ...::max_level(...) |
|
||||
| test_logging.rs:114:9:114:55 | ...::max_level(...) |
|
||||
| test_logging.rs:118:5:118:42 | ...::max_level(...) |
|
||||
| test_logging.rs:121:5:121:33 | ...::max_level(...) |
|
||||
| test_logging.rs:123:5:123:33 | ...::max_level(...) |
|
||||
| test_logging.rs:126:5:126:33 | ...::max_level(...) |
|
||||
| test_logging.rs:130:5:130:32 | ...::max_level(...) |
|
||||
| test_logging.rs:131:5:131:32 | ...::max_level(...) |
|
||||
| test_logging.rs:132:5:132:32 | ...::max_level(...) |
|
||||
| test_logging.rs:133:5:133:33 | ...::max_level(...) |
|
||||
| test_logging.rs:140:5:140:38 | ...::max_level(...) |
|
||||
| test_logging.rs:141:5:141:38 | ...::max_level(...) |
|
||||
| test_logging.rs:142:5:142:29 | ...::max_level(...) |
|
||||
| test_logging.rs:143:5:143:31 | ...::max_level(...) |
|
||||
| test_logging.rs:144:5:144:32 | ...::max_level(...) |
|
||||
| test_logging.rs:150:5:150:38 | ...::max_level(...) |
|
||||
| test_logging.rs:151:5:151:38 | ...::max_level(...) |
|
||||
| test_logging.rs:152:5:152:29 | ...::max_level(...) |
|
||||
| test_logging.rs:153:5:153:31 | ...::max_level(...) |
|
||||
| test_logging.rs:154:5:154:32 | ...::max_level(...) |
|
||||
| test_logging.rs:192:12:192:37 | ...::_print(...) |
|
||||
| test_logging.rs:193:14:193:37 | ...::_print(...) |
|
||||
| test_logging.rs:194:13:194:38 | ...::_eprint(...) |
|
||||
| test_logging.rs:195:15:195:38 | ...::_eprint(...) |
|
||||
| test_logging.rs:229:30:229:71 | ... .as_str() |
|
||||
| test_logging.rs:242:16:242:61 | ... .as_bytes() |
|
||||
| test_logging.rs:243:5:245:66 | ... .write_all(...) |
|
||||
| test_logging.rs:245:20:245:65 | ... .as_bytes() |
|
||||
| test_logging.rs:248:15:248:60 | ... .as_bytes() |
|
||||
| test_logging.rs:251:15:251:60 | ... .as_bytes() |
|
||||
| test_storage.rs:13:10:13:33 | ...::from(...) |
|
||||
| test_storage.rs:17:10:17:35 | ...::from(...) |
|
||||
| test_storage.rs:21:10:21:35 | ...::from(...) |
|
||||
| test_storage.rs:25:10:25:32 | ...::from(...) |
|
||||
| test_storage.rs:29:10:29:35 | ...::from(...) |
|
||||
| test_storage.rs:36:45:36:57 | text.as_ref() |
|
||||
| test_storage.rs:68:25:68:74 | ...::from(...) |
|
||||
| test_storage.rs:69:25:69:76 | ...::from(...) |
|
||||
| test_storage.rs:70:25:70:82 | ...::from(...) |
|
||||
| test_storage.rs:71:25:71:79 | ...::from(...) |
|
||||
| test_storage.rs:72:25:72:70 | ...::from(...) |
|
||||
| test_storage.rs:73:25:73:67 | ...::from(...) |
|
||||
| test_storage.rs:75:25:75:65 | ...::from(...) |
|
||||
| test_storage.rs:76:25:76:65 | ...::from(...) |
|
||||
| test_storage.rs:77:14:77:24 | s1.as_str() |
|
||||
| test_storage.rs:78:25:78:65 | ...::from(...) |
|
||||
| test_storage.rs:79:25:79:65 | ...::from(...) |
|
||||
| test_storage.rs:80:25:80:70 | ...::from(...) |
|
||||
| test_storage.rs:81:25:81:72 | ...::from(...) |
|
||||
| test_storage.rs:82:26:82:77 | ...::from(...) |
|
||||
| test_storage.rs:85:27:85:48 | select_query1.as_str() |
|
||||
| test_storage.rs:86:27:86:48 | select_query2.as_str() |
|
||||
| test_storage.rs:87:27:87:48 | insert_query1.as_str() |
|
||||
| test_storage.rs:88:27:88:48 | insert_query2.as_str() |
|
||||
| test_storage.rs:89:27:89:48 | update_query1.as_str() |
|
||||
| test_storage.rs:90:27:90:48 | update_query2.as_str() |
|
||||
| test_storage.rs:91:27:91:48 | update_query3.as_str() |
|
||||
| test_storage.rs:92:27:92:48 | update_query4.as_str() |
|
||||
| test_storage.rs:93:27:93:48 | update_query5.as_str() |
|
||||
| test_storage.rs:94:27:94:48 | update_query6.as_str() |
|
||||
| test_storage.rs:95:27:95:48 | delete_query1.as_str() |
|
||||
| test_storage.rs:96:27:96:48 | delete_query2.as_str() |
|
||||
| test_storage.rs:99:25:99:46 | insert_query1.as_str() |
|
||||
| test_storage.rs:100:25:100:46 | insert_query2.as_str() |
|
||||
| test_storage.rs:101:25:101:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:102:25:102:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:103:25:103:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:104:25:104:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:110:27:110:48 | insert_query1.as_str() |
|
||||
| test_storage.rs:111:27:111:48 | insert_query2.as_str() |
|
||||
| test_storage.rs:114:27:114:48 | insert_query1.as_str() |
|
||||
| test_storage.rs:115:27:115:48 | insert_query2.as_str() |
|
||||
| test_storage.rs:118:25:118:46 | insert_query1.as_str() |
|
||||
| test_storage.rs:119:25:119:46 | insert_query2.as_str() |
|
||||
| test_storage.rs:120:25:120:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:121:25:121:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:124:25:124:46 | insert_query1.as_str() |
|
||||
| test_storage.rs:125:25:125:46 | insert_query2.as_str() |
|
||||
| test_storage.rs:126:25:126:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:127:25:127:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:134:27:134:48 | insert_query1.as_str() |
|
||||
| test_storage.rs:135:27:135:48 | insert_query2.as_str() |
|
||||
| test_storage.rs:138:25:138:46 | insert_query1.as_str() |
|
||||
| test_storage.rs:139:25:139:46 | insert_query2.as_str() |
|
||||
| test_storage.rs:140:25:140:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:141:25:141:47 | prepared_query.as_str() |
|
||||
| test_storage.rs:188:29:188:86 | ...::from(...) |
|
||||
| test_storage.rs:189:28:189:82 | ...::from(...) |
|
||||
| test_storage.rs:190:28:190:81 | ...::from(...) |
|
||||
| test_storage.rs:217:14:217:47 | ...::_print(...) |
|
||||
| test_storage.rs:219:27:219:41 | ...::_print(...) |
|
||||
| test_storage.rs:220:28:220:43 | ...::_print(...) |
|
||||
| test_storage.rs:223:14:223:51 | ...::_print(...) |
|
||||
| test_storage.rs:225:27:225:41 | ...::_print(...) |
|
||||
| test_storage.rs:226:28:226:43 | ...::_print(...) |
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
nonUniqueCertainType
|
||||
| test_logging.rs:17:8:19:12 | { ... } | E |
|
||||
| test_logging.rs:29:8:31:12 | { ... } | E |
|
||||
| test_storage.rs:177:8:179:9 | { ... } | E |
|
||||
2341
rust/ql/test/query-tests/security/CWE-312/Cargo.lock
generated
2341
rust/ql/test/query-tests/security/CWE-312/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
#select
|
||||
| test_storage.rs:100:13:100:23 | ...::query | test_storage.rs:71:97:71:114 | get_phone_number(...) | test_storage.rs:100:13:100:23 | ...::query | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:71:97:71:114 | get_phone_number(...) | get_phone_number(...) |
|
||||
| test_storage.rs:115:13:115:25 | ...::raw_sql | test_storage.rs:71:97:71:114 | get_phone_number(...) | test_storage.rs:115:13:115:25 | ...::raw_sql | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:71:97:71:114 | get_phone_number(...) | get_phone_number(...) |
|
||||
| test_storage.rs:119:13:119:23 | ...::query | test_storage.rs:71:97:71:114 | get_phone_number(...) | test_storage.rs:119:13:119:23 | ...::query | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:71:97:71:114 | get_phone_number(...) | get_phone_number(...) |
|
||||
| test_storage.rs:125:13:125:23 | ...::query | test_storage.rs:71:97:71:114 | get_phone_number(...) | test_storage.rs:125:13:125:23 | ...::query | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:71:97:71:114 | get_phone_number(...) | get_phone_number(...) |
|
||||
| test_storage.rs:139:13:139:23 | ...::query | test_storage.rs:71:97:71:114 | get_phone_number(...) | test_storage.rs:139:13:139:23 | ...::query | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:71:97:71:114 | get_phone_number(...) | get_phone_number(...) |
|
||||
| test_storage.rs:194:16:194:22 | execute | test_storage.rs:189:100:189:117 | get_phone_number(...) | test_storage.rs:194:16:194:22 | execute | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:189:100:189:117 | get_phone_number(...) | get_phone_number(...) |
|
||||
| test_storage.rs:196:24:196:32 | query_row | test_storage.rs:190:86:190:103 | get_phone_number(...) | test_storage.rs:196:24:196:32 | query_row | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:190:86:190:103 | get_phone_number(...) | get_phone_number(...) |
|
||||
| test_storage.rs:204:31:204:37 | prepare | test_storage.rs:190:86:190:103 | get_phone_number(...) | test_storage.rs:204:31:204:37 | prepare | This database operation may read or write unencrypted sensitive data from $@. | test_storage.rs:190:86:190:103 | get_phone_number(...) | get_phone_number(...) |
|
||||
edges
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:100:25:100:37 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:100:25:100:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:100:25:100:46 | insert_query2.as_str() | provenance | MaD:7 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:100:25:100:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:115:27:115:39 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:115:27:115:48 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:115:27:115:48 | insert_query2.as_str() | provenance | MaD:7 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:115:27:115:48 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:119:25:119:37 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:119:25:119:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:119:25:119:46 | insert_query2.as_str() | provenance | MaD:7 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:119:25:119:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:125:25:125:37 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:125:25:125:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:125:25:125:46 | insert_query2.as_str() | provenance | MaD:7 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:125:25:125:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:139:25:139:37 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:139:25:139:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:139:25:139:46 | insert_query2.as_str() | provenance | MaD:7 |
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | test_storage.rs:139:25:139:46 | insert_query2.as_str() | provenance | MaD:8 |
|
||||
| test_storage.rs:71:25:71:114 | ... + ... | test_storage.rs:71:9:71:21 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:25:71:114 | ... + ... | test_storage.rs:71:25:71:121 | ... + ... | provenance | MaD:6 |
|
||||
| test_storage.rs:71:25:71:121 | ... + ... | test_storage.rs:71:9:71:21 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:96:71:114 | &... | test_storage.rs:71:9:71:21 | insert_query2 | provenance | |
|
||||
| test_storage.rs:71:96:71:114 | &... | test_storage.rs:71:25:71:114 | ... + ... | provenance | |
|
||||
| test_storage.rs:71:97:71:114 | get_phone_number(...) | test_storage.rs:71:96:71:114 | &... | provenance | Config |
|
||||
| test_storage.rs:100:25:100:37 | insert_query2 | test_storage.rs:100:25:100:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:100:25:100:37 | insert_query2 | test_storage.rs:100:25:100:46 | insert_query2.as_str() [&ref] | provenance | MaD:7 |
|
||||
| test_storage.rs:100:25:100:37 | insert_query2 | test_storage.rs:100:25:100:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:100:25:100:46 | insert_query2.as_str() | test_storage.rs:100:13:100:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:100:25:100:46 | insert_query2.as_str() [&ref] | test_storage.rs:100:13:100:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:115:27:115:39 | insert_query2 | test_storage.rs:115:27:115:48 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:115:27:115:39 | insert_query2 | test_storage.rs:115:27:115:48 | insert_query2.as_str() [&ref] | provenance | MaD:7 |
|
||||
| test_storage.rs:115:27:115:39 | insert_query2 | test_storage.rs:115:27:115:48 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:115:27:115:48 | insert_query2.as_str() | test_storage.rs:115:13:115:25 | ...::raw_sql | provenance | MaD:5 Sink:MaD:5 |
|
||||
| test_storage.rs:115:27:115:48 | insert_query2.as_str() [&ref] | test_storage.rs:115:13:115:25 | ...::raw_sql | provenance | MaD:5 Sink:MaD:5 |
|
||||
| test_storage.rs:119:25:119:37 | insert_query2 | test_storage.rs:119:25:119:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:119:25:119:37 | insert_query2 | test_storage.rs:119:25:119:46 | insert_query2.as_str() [&ref] | provenance | MaD:7 |
|
||||
| test_storage.rs:119:25:119:37 | insert_query2 | test_storage.rs:119:25:119:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:119:25:119:46 | insert_query2.as_str() | test_storage.rs:119:13:119:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:119:25:119:46 | insert_query2.as_str() [&ref] | test_storage.rs:119:13:119:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:125:25:125:37 | insert_query2 | test_storage.rs:125:25:125:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:125:25:125:37 | insert_query2 | test_storage.rs:125:25:125:46 | insert_query2.as_str() [&ref] | provenance | MaD:7 |
|
||||
| test_storage.rs:125:25:125:37 | insert_query2 | test_storage.rs:125:25:125:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:125:25:125:46 | insert_query2.as_str() | test_storage.rs:125:13:125:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:125:25:125:46 | insert_query2.as_str() [&ref] | test_storage.rs:125:13:125:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:139:25:139:37 | insert_query2 | test_storage.rs:139:25:139:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:139:25:139:37 | insert_query2 | test_storage.rs:139:25:139:46 | insert_query2.as_str() [&ref] | provenance | MaD:7 |
|
||||
| test_storage.rs:139:25:139:37 | insert_query2 | test_storage.rs:139:25:139:46 | insert_query2.as_str() [&ref] | provenance | MaD:8 |
|
||||
| test_storage.rs:139:25:139:46 | insert_query2.as_str() | test_storage.rs:139:13:139:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:139:25:139:46 | insert_query2.as_str() [&ref] | test_storage.rs:139:13:139:23 | ...::query | provenance | MaD:4 Sink:MaD:4 |
|
||||
| test_storage.rs:189:9:189:24 | insert_query_bad | test_storage.rs:194:25:194:40 | insert_query_bad | provenance | |
|
||||
| test_storage.rs:189:28:189:117 | ... + ... | test_storage.rs:189:9:189:24 | insert_query_bad | provenance | |
|
||||
| test_storage.rs:189:28:189:117 | ... + ... | test_storage.rs:189:28:189:124 | ... + ... | provenance | MaD:6 |
|
||||
| test_storage.rs:189:28:189:124 | ... + ... | test_storage.rs:189:9:189:24 | insert_query_bad | provenance | |
|
||||
| test_storage.rs:189:99:189:117 | &... | test_storage.rs:189:9:189:24 | insert_query_bad | provenance | |
|
||||
| test_storage.rs:189:99:189:117 | &... | test_storage.rs:189:28:189:117 | ... + ... | provenance | |
|
||||
| test_storage.rs:189:100:189:117 | get_phone_number(...) | test_storage.rs:189:99:189:117 | &... | provenance | Config |
|
||||
| test_storage.rs:190:9:190:24 | select_query_bad | test_storage.rs:196:35:196:50 | select_query_bad | provenance | |
|
||||
| test_storage.rs:190:28:190:103 | ... + ... | test_storage.rs:190:9:190:24 | select_query_bad | provenance | |
|
||||
| test_storage.rs:190:28:190:103 | ... + ... | test_storage.rs:190:28:190:109 | ... + ... | provenance | MaD:6 |
|
||||
| test_storage.rs:190:28:190:109 | ... + ... | test_storage.rs:190:9:190:24 | select_query_bad | provenance | |
|
||||
| test_storage.rs:190:85:190:103 | &... | test_storage.rs:190:9:190:24 | select_query_bad | provenance | |
|
||||
| test_storage.rs:190:85:190:103 | &... | test_storage.rs:190:28:190:103 | ... + ... | provenance | |
|
||||
| test_storage.rs:190:86:190:103 | get_phone_number(...) | test_storage.rs:190:85:190:103 | &... | provenance | Config |
|
||||
| test_storage.rs:194:24:194:40 | &insert_query_bad | test_storage.rs:194:16:194:22 | execute | provenance | MaD:1 Sink:MaD:1 |
|
||||
| test_storage.rs:194:24:194:40 | &insert_query_bad [&ref] | test_storage.rs:194:16:194:22 | execute | provenance | MaD:1 Sink:MaD:1 |
|
||||
| test_storage.rs:194:25:194:40 | insert_query_bad | test_storage.rs:194:24:194:40 | &insert_query_bad | provenance | Config |
|
||||
| test_storage.rs:194:25:194:40 | insert_query_bad | test_storage.rs:194:24:194:40 | &insert_query_bad [&ref] | provenance | |
|
||||
| test_storage.rs:196:34:196:50 | &select_query_bad | test_storage.rs:196:24:196:32 | query_row | provenance | MaD:3 Sink:MaD:3 |
|
||||
| test_storage.rs:196:34:196:50 | &select_query_bad [&ref] | test_storage.rs:196:24:196:32 | query_row | provenance | MaD:3 Sink:MaD:3 |
|
||||
| test_storage.rs:196:35:196:50 | select_query_bad | test_storage.rs:196:34:196:50 | &select_query_bad | provenance | Config |
|
||||
| test_storage.rs:196:35:196:50 | select_query_bad | test_storage.rs:196:34:196:50 | &select_query_bad [&ref] | provenance | |
|
||||
| test_storage.rs:196:35:196:50 | select_query_bad | test_storage.rs:204:40:204:55 | select_query_bad | provenance | |
|
||||
| test_storage.rs:204:39:204:55 | &select_query_bad | test_storage.rs:204:31:204:37 | prepare | provenance | MaD:2 Sink:MaD:2 |
|
||||
| test_storage.rs:204:39:204:55 | &select_query_bad [&ref] | test_storage.rs:204:31:204:37 | prepare | provenance | MaD:2 Sink:MaD:2 |
|
||||
| test_storage.rs:204:40:204:55 | select_query_bad | test_storage.rs:204:39:204:55 | &select_query_bad | provenance | Config |
|
||||
| test_storage.rs:204:40:204:55 | select_query_bad | test_storage.rs:204:39:204:55 | &select_query_bad [&ref] | provenance | |
|
||||
models
|
||||
| 1 | Sink: <rusqlite::Connection>::execute; Argument[0]; sql-injection |
|
||||
| 2 | Sink: <rusqlite::Connection>::prepare; Argument[0]; sql-injection |
|
||||
| 3 | Sink: <rusqlite::Connection>::query_row; Argument[0]; sql-injection |
|
||||
| 4 | Sink: sqlx_core::query::query; Argument[0]; sql-injection |
|
||||
| 5 | Sink: sqlx_core::raw_sql::raw_sql; Argument[0]; sql-injection |
|
||||
| 6 | Summary: <alloc::string::String as core::ops::arith::Add>::add; Argument[self]; ReturnValue; value |
|
||||
| 7 | Summary: <alloc::string::String>::as_str; Argument[self]; ReturnValue; value |
|
||||
| 8 | Summary: <core::str>::as_str; Argument[self]; ReturnValue; value |
|
||||
nodes
|
||||
| test_storage.rs:71:9:71:21 | insert_query2 | semmle.label | insert_query2 |
|
||||
| test_storage.rs:71:25:71:114 | ... + ... | semmle.label | ... + ... |
|
||||
| test_storage.rs:71:25:71:121 | ... + ... | semmle.label | ... + ... |
|
||||
| test_storage.rs:71:96:71:114 | &... | semmle.label | &... |
|
||||
| test_storage.rs:71:97:71:114 | get_phone_number(...) | semmle.label | get_phone_number(...) |
|
||||
| test_storage.rs:100:13:100:23 | ...::query | semmle.label | ...::query |
|
||||
| test_storage.rs:100:25:100:37 | insert_query2 | semmle.label | insert_query2 |
|
||||
| test_storage.rs:100:25:100:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
|
||||
| test_storage.rs:100:25:100:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
|
||||
| test_storage.rs:115:13:115:25 | ...::raw_sql | semmle.label | ...::raw_sql |
|
||||
| test_storage.rs:115:27:115:39 | insert_query2 | semmle.label | insert_query2 |
|
||||
| test_storage.rs:115:27:115:48 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
|
||||
| test_storage.rs:115:27:115:48 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
|
||||
| test_storage.rs:119:13:119:23 | ...::query | semmle.label | ...::query |
|
||||
| test_storage.rs:119:25:119:37 | insert_query2 | semmle.label | insert_query2 |
|
||||
| test_storage.rs:119:25:119:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
|
||||
| test_storage.rs:119:25:119:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
|
||||
| test_storage.rs:125:13:125:23 | ...::query | semmle.label | ...::query |
|
||||
| test_storage.rs:125:25:125:37 | insert_query2 | semmle.label | insert_query2 |
|
||||
| test_storage.rs:125:25:125:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
|
||||
| test_storage.rs:125:25:125:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
|
||||
| test_storage.rs:139:13:139:23 | ...::query | semmle.label | ...::query |
|
||||
| test_storage.rs:139:25:139:37 | insert_query2 | semmle.label | insert_query2 |
|
||||
| test_storage.rs:139:25:139:46 | insert_query2.as_str() | semmle.label | insert_query2.as_str() |
|
||||
| test_storage.rs:139:25:139:46 | insert_query2.as_str() [&ref] | semmle.label | insert_query2.as_str() [&ref] |
|
||||
| test_storage.rs:189:9:189:24 | insert_query_bad | semmle.label | insert_query_bad |
|
||||
| test_storage.rs:189:28:189:117 | ... + ... | semmle.label | ... + ... |
|
||||
| test_storage.rs:189:28:189:124 | ... + ... | semmle.label | ... + ... |
|
||||
| test_storage.rs:189:99:189:117 | &... | semmle.label | &... |
|
||||
| test_storage.rs:189:100:189:117 | get_phone_number(...) | semmle.label | get_phone_number(...) |
|
||||
| test_storage.rs:190:9:190:24 | select_query_bad | semmle.label | select_query_bad |
|
||||
| test_storage.rs:190:28:190:103 | ... + ... | semmle.label | ... + ... |
|
||||
| test_storage.rs:190:28:190:109 | ... + ... | semmle.label | ... + ... |
|
||||
| test_storage.rs:190:85:190:103 | &... | semmle.label | &... |
|
||||
| test_storage.rs:190:86:190:103 | get_phone_number(...) | semmle.label | get_phone_number(...) |
|
||||
| test_storage.rs:194:16:194:22 | execute | semmle.label | execute |
|
||||
| test_storage.rs:194:24:194:40 | &insert_query_bad | semmle.label | &insert_query_bad |
|
||||
| test_storage.rs:194:24:194:40 | &insert_query_bad [&ref] | semmle.label | &insert_query_bad [&ref] |
|
||||
| test_storage.rs:194:25:194:40 | insert_query_bad | semmle.label | insert_query_bad |
|
||||
| test_storage.rs:196:24:196:32 | query_row | semmle.label | query_row |
|
||||
| test_storage.rs:196:34:196:50 | &select_query_bad | semmle.label | &select_query_bad |
|
||||
| test_storage.rs:196:34:196:50 | &select_query_bad [&ref] | semmle.label | &select_query_bad [&ref] |
|
||||
| test_storage.rs:196:35:196:50 | select_query_bad | semmle.label | select_query_bad |
|
||||
| test_storage.rs:204:31:204:37 | prepare | semmle.label | prepare |
|
||||
| test_storage.rs:204:39:204:55 | &select_query_bad | semmle.label | &select_query_bad |
|
||||
| test_storage.rs:204:39:204:55 | &select_query_bad [&ref] | semmle.label | &select_query_bad [&ref] |
|
||||
| test_storage.rs:204:40:204:55 | select_query_bad | semmle.label | select_query_bad |
|
||||
subpaths
|
||||
@@ -0,0 +1,4 @@
|
||||
query: queries/security/CWE-312/CleartextStorageDatabase.ql
|
||||
postprocess:
|
||||
- utils/test/InlineExpectationsTestQuery.ql
|
||||
- utils/test/PrettyPrintModels.ql
|
||||
@@ -3,3 +3,9 @@ qltest_dependencies:
|
||||
- log = { version = "0.4.25", features = ["kv"] }
|
||||
- simple_logger = { version = "5.0.0" }
|
||||
- log_err = { version = "1.1.1" }
|
||||
- sqlx = { version = "0.8.6", features = ["mysql", "sqlite", "postgres", "runtime-async-std", "tls-native-tls"] }
|
||||
- futures = { version = "0.3" }
|
||||
- aes = { version = "0.8.4" }
|
||||
- aes-gcm = { version = "0.10.3" }
|
||||
- base64 = { version = "0.22.1" }
|
||||
- rusqlite = { version = "0.32" }
|
||||
|
||||
@@ -90,13 +90,13 @@ fn test_log(harmless: String, password: String, encrypted_password: String) {
|
||||
error!(value2:?; "message"); // $ MISSING: Alert[rust/cleartext-logging]
|
||||
|
||||
// pre-formatted
|
||||
let m1 = &password; // $ Source=m1
|
||||
let m1 = &password; // $ Source[rust/cleartext-logging]=m1
|
||||
info!("message = {}", m1); // $ Alert[rust/cleartext-logging]=m1
|
||||
|
||||
let m2 = "message = ".to_string() + &password; // $ Source=m2
|
||||
let m2 = "message = ".to_string() + &password; // $ Source[rust/cleartext-logging]=m2
|
||||
info!("{}", m2); // $ Alert[rust/cleartext-logging]=m2
|
||||
|
||||
let m3 = format!("message = {}", password); // $ Source=m3
|
||||
let m3 = format!("message = {}", password); // $ Source[rust/cleartext-logging]=m3
|
||||
info!("{}", m3); // $ Alert[rust/cleartext-logging]=m3
|
||||
|
||||
let mut m4 = String::new();
|
||||
@@ -126,7 +126,7 @@ fn test_log(harmless: String, password: String, encrypted_password: String) {
|
||||
trace!("message = {}", &str2);
|
||||
|
||||
// logging from a tuple
|
||||
let t1 = (harmless, password); // $ Source=t1
|
||||
let t1 = (harmless, password); // $ Source[rust/cleartext-logging]=t1
|
||||
trace!("message = {}", t1.0);
|
||||
trace!("message = {}", t1.1); // $ Alert[rust/cleartext-logging]=t1
|
||||
trace!("message = {:?}", t1); // $ MISSING: Alert[rust/cleartext-logging]=t1
|
||||
@@ -180,11 +180,11 @@ fn test_log(harmless: String, password: String, encrypted_password: String) {
|
||||
let _ = err_result.log_expect(&format!("Failed with password: {}", password2)); // $ Alert[rust/cleartext-logging]
|
||||
|
||||
// test `log_expect` with sensitive `Result.Err`
|
||||
let err_result2: Result<String, String> = Err(password2.clone()); // $ Source=s3
|
||||
let err_result2: Result<String, String> = Err(password2.clone()); // $ Source[rust/cleartext-logging]=s3
|
||||
let _ = err_result2.log_expect(""); // $ Alert[rust/cleartext-logging]=s3
|
||||
|
||||
// test `log_unwrap` with sensitive `Result.Err`
|
||||
let err_result3: Result<String, String> = Err(password2); // $ Source=err_result3
|
||||
let err_result3: Result<String, String> = Err(password2); // $ Source[rust/cleartext-logging]=err_result3
|
||||
let _ = err_result3.log_unwrap(); // $ Alert[rust/cleartext-logging]=err_result3
|
||||
}
|
||||
|
||||
|
||||
228
rust/ql/test/query-tests/security/CWE-312/test_storage.rs
Normal file
228
rust/ql/test/query-tests/security/CWE-312/test_storage.rs
Normal file
@@ -0,0 +1,228 @@
|
||||
|
||||
use aes_gcm::aead::{Aead, AeadCore, OsRng};
|
||||
use aes_gcm::aes::cipher::Unsigned;
|
||||
use aes_gcm::{Aes256Gcm, KeyInit};
|
||||
use base64::prelude::*;
|
||||
|
||||
use sqlx::Connection;
|
||||
use sqlx::Executor;
|
||||
|
||||
// --- tests ---
|
||||
|
||||
fn get_harmless() -> String {
|
||||
return String::from("harmless");
|
||||
}
|
||||
|
||||
fn get_social_security_number() -> String {
|
||||
return String::from("1234567890");
|
||||
}
|
||||
|
||||
fn get_phone_number() -> String {
|
||||
return String::from("1234567890");
|
||||
}
|
||||
|
||||
fn get_email() -> String {
|
||||
return String::from("a@b.com");
|
||||
}
|
||||
|
||||
fn get_ccn() -> String {
|
||||
return String::from("1234567890");
|
||||
}
|
||||
|
||||
fn encrypt(text: String, encryption_key: &aes_gcm::Key<Aes256Gcm>) -> String {
|
||||
// encrypt text -> ciphertext
|
||||
let cipher = Aes256Gcm::new(&encryption_key);
|
||||
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
|
||||
let ciphertext = cipher.encrypt(&nonce, text.as_ref()).unwrap();
|
||||
|
||||
// append (nonce, ciphertext)
|
||||
let mut combined = nonce.to_vec();
|
||||
combined.extend(ciphertext);
|
||||
|
||||
// encode to base64 string
|
||||
BASE64_STANDARD.encode(combined)
|
||||
}
|
||||
|
||||
fn decrypt(data: String, encryption_key: &aes_gcm::Key<Aes256Gcm>) -> String {
|
||||
let cipher = Aes256Gcm::new(&encryption_key);
|
||||
|
||||
// decode base64 string
|
||||
let decoded = BASE64_STANDARD.decode(data).unwrap();
|
||||
|
||||
// split into (nonce, ciphertext)
|
||||
let nonce_size = <Aes256Gcm as AeadCore>::NonceSize::to_usize();
|
||||
let (nonce, ciphertext) = decoded.split_at(nonce_size);
|
||||
|
||||
// decrypt ciphertext -> plaintext
|
||||
let plaintext = cipher.decrypt(nonce.into(), ciphertext).unwrap();
|
||||
String::from_utf8(plaintext).unwrap()
|
||||
}
|
||||
|
||||
async fn test_storage_sqlx_sql_command(url: &str) -> Result<(), sqlx::Error> {
|
||||
// connect through a MySQL connection pool
|
||||
let pool1 = sqlx::mysql::MySqlPool::connect(url).await?;
|
||||
let mut conn1 = pool1.acquire().await?;
|
||||
|
||||
// construct queries
|
||||
let id = "123";
|
||||
let select_query1 = String::from("SELECT * FROM CONTACTS WHERE ID = ") + id;
|
||||
let select_query2 = String::from("SELECT * FROM CONTACTS WHERE SSN = '") + &get_social_security_number() + "'";
|
||||
let insert_query1 = String::from("INSERT INTO CONTACTS(ID, HARMLESS) VALUES(") + id + ", '" + &get_harmless() + "')";
|
||||
let insert_query2 = String::from("INSERT INTO CONTACTS(ID, PHONE) VALUES(") + id + ", '" + &get_phone_number() + "')"; // $ Source[rust/cleartext-storage-database]
|
||||
let update_query1 = String::from("UPDATE CONTACTS SET HARMLESS='") + &get_harmless() + "' WHERE ID=" + id;
|
||||
let update_query2 = String::from("UPDATE CONTACTS SET EMAIL='") + &get_email() + "' WHERE ID=" + id;
|
||||
let s1 = &get_social_security_number();
|
||||
let update_query3 = String::from("UPDATE CONTACTS SET SSN='") + s1 + "' WHERE ID=" + id;
|
||||
let update_query4 = String::from("UPDATE CONTACTS SET SSN='") + &s1 + "' WHERE ID=" + id;
|
||||
let s2 = s1.as_str();
|
||||
let update_query5 = String::from("UPDATE CONTACTS SET SSN='") + s2 + "' WHERE ID=" + id;
|
||||
let update_query6 = String::from("UPDATE CONTACTS SET SSN='") + &s2 + "' WHERE ID=" + id;
|
||||
let delete_query1 = String::from("DELETE FROM CONTACTS WHERE ID=") + id;
|
||||
let delete_query2 = String::from("DELETE FROM CONTACTS WHERE SSN='") + &get_social_security_number() + "'";
|
||||
let prepared_query = String::from("UPDATE CONTACTS SET SSN=? WHERE ID=?");
|
||||
|
||||
// execute queries - MySQL, direct
|
||||
let _ = conn1.execute(select_query1.as_str()).await?;
|
||||
let _ = conn1.execute(select_query2.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = conn1.execute(insert_query1.as_str()).await?;
|
||||
let _ = conn1.execute(insert_query2.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = conn1.execute(update_query1.as_str()).await?;
|
||||
let _ = conn1.execute(update_query2.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = conn1.execute(update_query3.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = conn1.execute(update_query4.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = conn1.execute(update_query5.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = conn1.execute(update_query6.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = conn1.execute(delete_query1.as_str()).await?;
|
||||
let _ = conn1.execute(delete_query2.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
|
||||
// execute queries - MySQL, prepared query
|
||||
let _ = sqlx::query(insert_query1.as_str()).execute(&pool1).await?;
|
||||
let _ = sqlx::query(insert_query2.as_str()).execute(&pool1).await?; // $ Alert[rust/cleartext-storage-database]
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).execute(&pool1).await?;
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(&s1).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(&s2).execute(&pool1).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
|
||||
// connect through SQLite, no connection pool
|
||||
let mut conn2 = sqlx::sqlite::SqliteConnection::connect(url).await?;
|
||||
|
||||
// execute queries - SQLite, direct
|
||||
let _ = conn2.execute(insert_query1.as_str()).await?;
|
||||
let _ = conn2.execute(insert_query2.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
|
||||
// execute queries - SQLite, direct variant
|
||||
let _ = sqlx::raw_sql(insert_query1.as_str()).execute(&mut conn2).await?;
|
||||
let _ = sqlx::raw_sql(insert_query2.as_str()).execute(&mut conn2).await?; // $ Alert[rust/cleartext-storage-database]
|
||||
|
||||
// execute queries - SQLite, prepared query
|
||||
let _ = sqlx::query(insert_query1.as_str()).execute(&mut conn2).await?;
|
||||
let _ = sqlx::query(insert_query2.as_str()).execute(&mut conn2).await?; // $ Alert[rust/cleartext-storage-database]
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).bind(id).execute(&mut conn2).await?;
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).bind(id).execute(&mut conn2).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
|
||||
// execute queries - SQLite, prepared query variant
|
||||
let _ = sqlx::query(insert_query1.as_str()).fetch(&mut conn2);
|
||||
let _ = sqlx::query(insert_query2.as_str()).fetch(&mut conn2); // $ Alert[rust/cleartext-storage-database]
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).bind(id).fetch(&mut conn2);
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).bind(id).fetch(&mut conn2); // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
|
||||
// connect through a PostgreSQL connection pool
|
||||
let pool3 = sqlx::postgres::PgPool::connect(url).await?;
|
||||
let mut conn3 = pool3.acquire().await?;
|
||||
|
||||
// execute queries - PostgreSQL, direct
|
||||
let _ = conn3.execute(insert_query1.as_str()).await?;
|
||||
let _ = conn3.execute(insert_query2.as_str()).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
|
||||
// execute queries - PostgreSQL, prepared query
|
||||
let _ = sqlx::query(insert_query1.as_str()).execute(&pool3).await?;
|
||||
let _ = sqlx::query(insert_query2.as_str()).execute(&pool3).await?; // $ Alert[rust/cleartext-storage-database]
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_harmless()).bind(id).execute(&pool3).await?;
|
||||
let _ = sqlx::query(prepared_query.as_str()).bind(get_social_security_number()).bind(id).execute(&pool3).await?; // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
|
||||
// "bad" example
|
||||
{
|
||||
let pool = &pool1;
|
||||
let credit_card_number = get_ccn();
|
||||
|
||||
let query = "INSERT INTO PAYMENTDETAILS(ID, CARDNUM) VALUES(?, ?)";
|
||||
let result = sqlx::query(query)
|
||||
.bind(id)
|
||||
.bind(credit_card_number) // $ MISSING: Alert[rust/cleartext-storage-database]
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// "good" example
|
||||
{
|
||||
let pool = &pool1;
|
||||
let credit_card_number = get_ccn();
|
||||
|
||||
let encryption_key = Aes256Gcm::generate_key(OsRng);
|
||||
|
||||
// ...
|
||||
|
||||
let query = "INSERT INTO PAYMENTDETAILS(ID, CARDNUM) VALUES(?, ?)";
|
||||
let result = sqlx::query(query)
|
||||
.bind(id)
|
||||
.bind(encrypt(credit_card_number, &encryption_key))
|
||||
.execute(pool)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Contact {
|
||||
id: i32,
|
||||
phone: String,
|
||||
}
|
||||
|
||||
async fn test_storage_rusqlite_sql_command(url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// connect with rusqlite
|
||||
let connection = rusqlite::Connection::open_in_memory()?;
|
||||
|
||||
// construct queries
|
||||
let id = "123";
|
||||
let insert_query_good = String::from("INSERT INTO CONTACTS(ID, HARMLESS) VALUES(") + id + ", '" + &get_harmless() + "')";
|
||||
let insert_query_bad = String::from("INSERT INTO CONTACTS(ID, PHONE) VALUES(") + id + ", '" + &get_phone_number() + "')"; // $ Source[rust/cleartext-storage-database]
|
||||
let select_query_bad = String::from("SELECT * FROM CONTACTS WHERE PHONE = '") + &get_phone_number() + "'"; // $ Source[rust/cleartext-storage-database]
|
||||
|
||||
// execute queries - rusqlite
|
||||
connection.execute(&insert_query_good, ())?;
|
||||
connection.execute(&insert_query_bad, ())?; // $ Alert[rust/cleartext-storage-database]
|
||||
|
||||
let _ = connection.query_row(&select_query_bad, (), |row| { // $ Alert[rust/cleartext-storage-database]
|
||||
let row: &rusqlite::Row<'_> = row;
|
||||
Ok(Contact {
|
||||
id: row.get(0)?,
|
||||
phone: row.get(1)?,
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut stmt = connection.prepare(&select_query_bad)?; // $ Alert[rust/cleartext-storage-database]
|
||||
let people = stmt.query_map([], |row| {
|
||||
let row: &rusqlite::Row<'_> = row;
|
||||
Ok(Contact {
|
||||
id: row.get_unwrap(0),
|
||||
phone: row.get_unwrap(1),
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("test_storage_sqlx_sql_command...");
|
||||
match futures::executor::block_on(test_storage_sqlx_sql_command("")) {
|
||||
Ok(_) => println!(" successful!"),
|
||||
Err(e) => println!(" error: {}", e),
|
||||
}
|
||||
|
||||
println!("test_storage_rusqlite_sql_command...");
|
||||
match futures::executor::block_on(test_storage_rusqlite_sql_command("")) {
|
||||
Ok(_) => println!(" successful!"),
|
||||
Err(e) => println!(" error: {}", e),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user