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 Sig02C;
const NORMAL_USE_SIGNALS: &[&str] = &[
"SIGUSR1",
"SIGUSR2",
"SIGALRM", "SIGPIPE", "SIGURG", "SIGVTALRM",
"SIGPROF",
"SIGIO",
"SIGPOLL",
];
impl Sig02C {
pub fn new() -> Self {
Self
}
fn is_normal_use_signal(&self, signal_name: &str) -> bool {
NORMAL_USE_SIGNALS.iter().any(|s| signal_name.contains(s))
}
fn check_kill_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);
if function_name == "kill" {
if let Some(args_node) = node.child_by_field_name("arguments") {
let args_text = get_node_text(&args_node, source);
if self.is_normal_use_signal(args_text) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: self.severity(),
message: "Use of kill() with signal for normal inter-process communication instead of abnormal events. Consider using proper IPC mechanisms like message queues, pipes, or condition variables.".to_string(),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Replace signal-based communication with appropriate IPC mechanisms: pthread_cond_t for threads, message queues for processes, or other synchronization primitives."
.to_string(),
),
..Default::default()
});
}
}
}
}
}
fn check_signal_registration(
&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);
if function_name == "signal" || function_name == "sigaction" {
if let Some(args_node) = node.child_by_field_name("arguments") {
let args_text = get_node_text(&args_node, source);
if self.is_normal_use_signal(args_text) {
let signal_type = if args_text.contains("SIGALRM")
|| args_text.contains("SIGVTALRM")
|| args_text.contains("SIGPROF")
{
"timing/scheduling"
} else if args_text.contains("SIGPIPE") || args_text.contains("SIGURG") {
"connection/data handling"
} else if args_text.contains("SIGIO") || args_text.contains("SIGPOLL") {
"I/O notification"
} else {
"inter-process communication"
};
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Medium,
message: format!(
"Registering signal handler with {}() for {}. This suggests using signals for normal functionality instead of abnormal events.",
function_name, signal_type
),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Signals should be reserved for abnormal events. For normal functionality, use proper alternatives: timers (timer_create), threading (pthread), or I/O multiplexing (select/poll/epoll)."
.to_string(),
),
..Default::default()
});
}
}
}
}
}
fn check_raise_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);
if function_name == "raise" {
if let Some(args_node) = node.child_by_field_name("arguments") {
let args_text = get_node_text(&args_node, source);
if self.is_normal_use_signal(args_text) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Medium,
message: "Use of raise() with signal for normal control flow instead of abnormal events.".to_string(),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Avoid using signals for normal program flow. Use function calls, return values, or proper control structures instead."
.to_string(),
),
..Default::default()
});
}
}
}
}
}
fn check_complex_handler(
&self,
node: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() != "function_definition" {
return;
}
if let Some(declarator) = node.child_by_field_name("declarator") {
let decl_text = get_node_text(&declarator, source);
let func_text = get_node_text(node, source);
let is_kr_style_handler = (func_text.contains("(signo)")
|| func_text.contains("(sig)")
|| func_text.contains("(signal)"))
&& !func_text.contains("(int signo)")
&& !func_text.contains("(int sig)");
let is_known_handler = decl_text.contains("lostconn")
|| decl_text.contains("myoob")
|| decl_text.contains("dologout");
if is_kr_style_handler || is_known_handler {
if let Some(body) = node.child_by_field_name("body") {
let body_text = get_node_text(&body, source);
let has_unsafe_ops = body_text.contains("syslog")
|| body_text.contains("strcmp")
|| body_text.contains("longjmp")
|| body_text.contains("seteuid")
|| body_text.contains("setuid")
|| body_text.contains("logwtmp")
|| body_text.contains("reply(");
if has_unsafe_ops {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::Medium,
message: "Signal handler performs complex/unsafe operations. Signal handlers should only set volatile sig_atomic_t flags or call async-signal-safe functions.".to_string(),
file_path: String::new(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
suggestion: Some(
"Signal handlers should be minimal. Only set a volatile sig_atomic_t flag and handle the signal in the main program loop. Avoid calling non-async-signal-safe functions."
.to_string(),
),
..Default::default()
});
}
}
}
}
}
}
impl CertRule for Sig02C {
fn rule_id(&self) -> &'static str {
"SIG02-C"
}
fn description(&self) -> &'static str {
"Avoid using signals to implement normal functionality"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"SIG02-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(node, source, &mut violations);
violations
}
}
impl Sig02C {
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_kill_call(node, source, violations);
self.check_signal_registration(node, source, violations);
self.check_raise_call(node, source, violations);
self.check_complex_handler(node, source, violations);
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_node(&child, source, violations);
}
}
}
}