use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
pub struct Win03C;
impl Win03C {
pub fn new() -> Self {
Self
}
fn check_function_call(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() != "call_expression" {
return;
}
if let Some(function_node) = node.child_by_field_name("function") {
let function_name = get_node_text(&function_node, source);
match function_name {
"OpenMutex" => {
self.check_open_mutex_call(node, source, violations);
}
"fopen" => {
self.check_fopen_call(node, source, violations);
}
"_strtoui64" | "_strtoul" | "strtoul" | "_atoi64" => {
self.check_handle_from_cmdline(node, source, violations);
}
_ => {}
}
}
}
fn check_open_mutex_call(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(args_node) = node.child_by_field_name("arguments") {
if let Some(second_arg) = self.get_nth_argument(&args_node, 1) {
let arg_text = get_node_text(&second_arg, source).trim();
if arg_text == "TRUE" || arg_text == "1" {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: format!(
"OpenMutex() called with inheritance enabled (second parameter is {}). This allows child processes to inherit the mutex handle, potentially leaking sensitive resources.",
arg_text
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Change the second parameter to FALSE to disable handle inheritance."
.to_string(),
),
..Default::default()
});
}
}
}
}
fn check_fopen_call(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if let Some(args_node) = node.child_by_field_name("arguments") {
if let Some(mode_arg) = self.get_nth_argument(&args_node, 1) {
let mode_text = get_node_text(&mode_arg, source);
if mode_arg.kind() == "string_literal" {
if !mode_text.contains('N') {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Medium,
message: format!(
"fopen() called without 'N' flag in mode string {}. On Windows, file handles are inheritable by default unless the 'N' flag is specified.",
mode_text
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Add 'N' flag to the mode string (e.g., \"rwN\") to disable handle inheritance on Windows."
.to_string(),
),
..Default::default()
});
}
}
}
}
}
fn check_handle_from_cmdline(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if let Some(args_node) = node.child_by_field_name("arguments") {
if let Some(first_arg) = self.get_nth_argument(&args_node, 0) {
let arg_text = get_node_text(&first_arg, source).trim();
if arg_text.contains("cmdLine")
|| arg_text.contains("CmdLine")
|| arg_text.contains("lpCmdLine")
|| arg_text.contains("CommandLine")
{
if let Some(parent) = node.parent() {
let parent_text = get_node_text(&parent, source);
if parent_text.contains("HANDLE") {
let scope = self.find_containing_scope(node);
if let Some(scope) = scope {
let scope_text = get_node_text(&scope, source);
if scope_text.contains("DuplicateHandle") {
return; }
}
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: "Converting command line argument to HANDLE without validation. Receiving handles via command line is insecure - it exposes handles to other processes and allows handle hijacking.".to_string(),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Validate the handle using DuplicateHandle() to ensure it's a valid handle with appropriate access rights, or use proper IPC mechanisms."
.to_string(),
),
..Default::default()
});
}
}
}
}
}
}
fn find_containing_scope<'a>(&self, node: &Node<'a>) -> Option<Node<'a>> {
let mut current = *node;
while let Some(parent) = current.parent() {
if parent.kind() == "function_definition"
|| parent.kind() == "compound_statement"
|| parent.kind() == "translation_unit"
{
return Some(parent);
}
current = parent;
}
None
}
fn get_nth_argument<'a>(&self, args_node: &Node<'a>, index: usize) -> Option<Node<'a>> {
let mut arg_count = 0;
for i in 0..args_node.child_count() {
if let Some(child) = args_node.child(i) {
if child.kind() != "(" && child.kind() != ")" && child.kind() != "," {
if arg_count == index {
return Some(child);
}
arg_count += 1;
}
}
}
None
}
}
impl CertRule for Win03C {
fn rule_id(&self) -> &'static str {
"WIN03-C"
}
fn description(&self) -> &'static str {
"Understand HANDLE inheritance"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"WIN03-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Win03C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_function_call(node, source, violations);
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
}