use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::HashSet;
use tree_sitter::Node;
#[derive(Debug)]
pub struct Err32C;
impl Err32C {
pub fn new() -> Self {
Err32C
}
fn collect_signal_handlers(&self, node: &Node, source: &str, handlers: &mut HashSet<String>) {
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
if func_name == "signal" {
if let Some(args) = node.child_by_field_name("arguments") {
let mut arg_idx = 0;
let mut cursor = args.walk();
for child in args.children(&mut cursor) {
if child.kind() != "," && child.kind() != "(" && child.kind() != ")" {
arg_idx += 1;
if arg_idx == 2 {
let handler_name =
get_node_text(&child, source).trim().to_string();
if !handler_name.starts_with("SIG_") {
handlers.insert(handler_name);
}
}
}
}
}
}
}
}
if node.kind() == "assignment_expression" {
if let Some(left) = node.child_by_field_name("left") {
let left_text = get_node_text(&left, source);
if left_text.contains("sa_handler") || left_text.contains("sa_sigaction") {
if let Some(right) = node.child_by_field_name("right") {
let handler_name = get_node_text(&right, source).trim().to_string();
if !handler_name.is_empty() && !handler_name.starts_with("SIG_") {
handlers.insert(handler_name);
}
}
}
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.collect_signal_handlers(&child, source, handlers);
}
}
fn get_function_name(&self, declarator: &Node, source: &str) -> String {
if declarator.kind() == "function_declarator" {
if let Some(name_node) = declarator.child_by_field_name("declarator") {
return get_node_text(&name_node, source).trim().to_string();
}
}
let declarator_text = get_node_text(declarator, source);
declarator_text
.split('(')
.next()
.unwrap_or(&declarator_text)
.trim()
.to_string()
}
fn has_errno_save_restore(&self, func_node: &Node, source: &str) -> bool {
let func_text = get_node_text(func_node, source);
let save_patterns = [
"save_errno",
"saved_errno",
"errno_save",
"old_errno",
"errno_saved",
];
for pattern in &save_patterns {
let has_save = func_text.contains(&format!("{} = errno", pattern))
|| func_text.contains(&format!("{}=errno", pattern));
let has_restore = func_text.contains(&format!("errno = {}", pattern))
|| func_text.contains(&format!("errno={}", pattern));
if has_save && has_restore {
return true;
}
}
false
}
fn is_in_signal_handler(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
) -> bool {
let mut current = Some(*node);
while let Some(n) = current {
if n.kind() == "function_definition" {
if let Some(declarator) = n.child_by_field_name("declarator") {
let func_name = self.get_function_name(&declarator, source);
if registered_handlers.contains(&func_name) {
return true;
}
let lower_name = func_name.to_lowercase();
if lower_name.contains("handler") || lower_name.starts_with("sig") {
return true;
}
}
break;
}
current = n.parent();
}
false
}
fn get_containing_function<'a>(&self, node: &Node<'a>) -> Option<Node<'a>> {
let mut current = Some(*node);
while let Some(n) = current {
if n.kind() == "function_definition" {
return Some(n);
}
current = n.parent();
}
None
}
fn check_errno_in_handler(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "identifier" {
let name = get_node_text(node, source);
if name == "errno" && self.is_in_signal_handler(node, source, registered_handlers) {
if let Some(func_node) = self.get_containing_function(node) {
if self.has_errno_save_restore(&func_node, source) {
return; }
}
violations.push(RuleViolation {
rule_id: "ERR32-C".to_string(),
severity: Severity::High,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: "errno should not be used in signal handlers".to_string(),
file_path: String::new(),
suggestion: Some(
"Save and restore errno in signal handler if needed".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
fn check_error_functions_in_handler(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
if (func_name == "perror" || func_name == "strerror")
&& self.is_in_signal_handler(node, source, registered_handlers)
{
violations.push(RuleViolation {
rule_id: "ERR32-C".to_string(),
severity: Severity::High,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"{}() should not be called in signal handlers (uses errno)",
func_name
),
file_path: String::new(),
suggestion: Some(
"Avoid error reporting functions in signal handlers".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
}
fn check_node(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
self.check_errno_in_handler(node, source, registered_handlers, violations);
self.check_error_functions_in_handler(node, source, registered_handlers, violations);
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_node(&child, source, registered_handlers, violations);
}
}
}
impl Default for Err32C {
fn default() -> Self {
Self::new()
}
}
impl CertRule for Err32C {
fn rule_id(&self) -> &'static str {
"ERR32-C"
}
fn description(&self) -> &'static str {
"Do not rely on indeterminate values of errno"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"ERR32-C"
}
fn check(&self, root_node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut registered_handlers = HashSet::new();
self.collect_signal_handlers(root_node, source, &mut registered_handlers);
self.check_node(root_node, source, ®istered_handlers, &mut violations);
violations
}
}