a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
125 lines
5.2 KiB
Go
125 lines
5.2 KiB
Go
package astquery
|
|
|
|
// Rust SAST starter pack. The legacy unsafe-rust-* family already
|
|
// covers unsafe blocks / unwrap / panic-macro / assert-macro. This
|
|
// file adds security-specific patterns absent from that bundle.
|
|
|
|
func init() {
|
|
registerRustCommandShell()
|
|
registerRustWeakCryptoCrate()
|
|
registerRustHttpInsecure()
|
|
registerRustMemForget()
|
|
registerRustTransmute()
|
|
registerRustEnvFromUser()
|
|
registerRustStdRandForCrypto()
|
|
}
|
|
|
|
func registerRustCommandShell() {
|
|
mustRegisterSAST(sastRule{
|
|
Name: "rust-command-sh-c",
|
|
Description: "`Command::new(\"sh\").arg(\"-c\").arg(cmd)` — runs `cmd` through the shell. Any caller-controlled portion is command injection. Build an argv directly.",
|
|
Severity: "error",
|
|
CWE: "CWE-78",
|
|
OWASP: "A03:2021-Injection",
|
|
Tags: []string{"command-injection"},
|
|
Pat: map[string]string{
|
|
"rust": `((call_expression
|
|
function: (scoped_identifier path: (identifier) @ty name: (identifier) @method)
|
|
arguments: (arguments (string_literal) @shell)) @match
|
|
(#eq? @ty "Command") (#eq? @method "new")
|
|
(#match? @shell "\"(sh|bash|zsh|cmd|powershell)\""))`,
|
|
},
|
|
})
|
|
}
|
|
|
|
func registerRustWeakCryptoCrate() {
|
|
mustRegisterSAST(sastRule{
|
|
Name: "rust-md5-or-sha1-crate",
|
|
Description: "`use md5::*` / `use sha1::*` — crates that wrap broken hashes. Switch to `sha2`, `sha3`, `blake3`, or `argon2`.",
|
|
Severity: "error",
|
|
CWE: "CWE-327",
|
|
Tags: []string{"crypto", "weak-hash"},
|
|
Pat: map[string]string{
|
|
"rust": `((use_declaration argument: (scoped_use_list path: (identifier) @crate)) @match
|
|
(#match? @crate "^(md5|sha1|md4|md2)$"))
|
|
((use_declaration argument: (scoped_identifier path: (identifier) @crate)) @match
|
|
(#match? @crate "^(md5|sha1|md4|md2)$"))`,
|
|
},
|
|
})
|
|
}
|
|
|
|
func registerRustHttpInsecure() {
|
|
mustRegisterSAST(sastRule{
|
|
Name: "rust-reqwest-danger-accept-invalid-certs",
|
|
Description: "`reqwest::Client::builder().danger_accept_invalid_certs(true)` — disables TLS validation. Any MITM intercepts.",
|
|
Severity: "error",
|
|
CWE: "CWE-295",
|
|
Tags: []string{"tls", "no-verify"},
|
|
Pat: map[string]string{
|
|
"rust": `((call_expression
|
|
function: (field_expression field: (field_identifier) @fn)
|
|
arguments: (arguments (boolean_literal) @v)) @match
|
|
(#match? @fn "^(danger_accept_invalid_certs|danger_accept_invalid_hostnames)$") (#eq? @v "true"))`,
|
|
},
|
|
})
|
|
}
|
|
|
|
func registerRustMemForget() {
|
|
mustRegisterSAST(sastRule{
|
|
Name: "rust-mem-forget",
|
|
Description: "`mem::forget(x)` — leaks `x` without running its destructor. Sometimes intentional (FFI hand-off); usually a bug. Audit every call site.",
|
|
Severity: "info",
|
|
CWE: "CWE-401",
|
|
Tags: []string{"memory", "audit-hook"},
|
|
Pat: map[string]string{
|
|
"rust": `((call_expression
|
|
function: (scoped_identifier path: (identifier) @mod name: (identifier) @fn)) @match
|
|
(#eq? @mod "mem") (#eq? @fn "forget"))`,
|
|
},
|
|
})
|
|
}
|
|
|
|
func registerRustTransmute() {
|
|
mustRegisterSAST(sastRule{
|
|
Name: "rust-mem-transmute",
|
|
Description: "`mem::transmute::<_, _>(...)` — bit-cast between unrelated types. Often the wrong primitive; usually a sign that a `From` / safe API exists. Hand-audit boundary.",
|
|
Severity: "warning",
|
|
CWE: "CWE-704",
|
|
Tags: []string{"memory", "unsafe"},
|
|
Pat: map[string]string{
|
|
"rust": `((call_expression
|
|
function: [(scoped_identifier name: (identifier) @fn) (generic_function function: (scoped_identifier name: (identifier) @fn))]) @match
|
|
(#eq? @fn "transmute"))`,
|
|
},
|
|
})
|
|
}
|
|
|
|
func registerRustEnvFromUser() {
|
|
mustRegisterSAST(sastRule{
|
|
Name: "rust-env-set-from-user",
|
|
Description: "`std::env::set_var(k, v)` — process-wide; thread-unsafe pre-1.0 / racy on Unix. Avoid using it to plumb user input into the env (subsequent processes inherit).",
|
|
Severity: "info",
|
|
CWE: "CWE-454",
|
|
Tags: []string{"env"},
|
|
Pat: map[string]string{
|
|
"rust": `((call_expression
|
|
function: (scoped_identifier path: (scoped_identifier path: (identifier) @std name: (identifier) @env) name: (identifier) @fn)) @match
|
|
(#eq? @std "std") (#eq? @env "env") (#eq? @fn "set_var"))`,
|
|
},
|
|
})
|
|
}
|
|
|
|
func registerRustStdRandForCrypto() {
|
|
mustRegisterSAST(sastRule{
|
|
Name: "rust-thread-rng-for-crypto",
|
|
Description: "`rand::thread_rng()` — chacha20-based and cryptographically secure *in current versions*, but `SmallRng::seed_from_u64` and other `rand` PRNGs are not. Audit each call site to confirm the chosen RNG meets the use case (use `rand::rngs::OsRng` / `ring::rand` for keys / nonces / tokens unambiguously).",
|
|
Severity: "info",
|
|
CWE: "CWE-338",
|
|
Tags: []string{"crypto", "audit-hook"},
|
|
Pat: map[string]string{
|
|
"rust": `((call_expression function: (scoped_identifier path: (identifier) @crate name: (identifier) @fn)) @match
|
|
(#eq? @crate "rand") (#match? @fn "^(thread_rng|random)$"))`,
|
|
},
|
|
})
|
|
}
|