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 Pos34C;
impl CertRule for Pos34C {
fn rule_id(&self) -> &'static str {
"POS34-C"
}
fn description(&self) -> &'static str {
"Do not call putenv() with a pointer to an automatic variable as the argument"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"POS34-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_putenv_calls(node, source, &mut violations);
violations
}
}
impl Pos34C {
fn check_putenv_calls(&self, node: &Node, source: &str, 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 == "putenv" {
if let Some(arguments) = node.child_by_field_name("arguments") {
if let Some(arg) = self.get_first_argument(&arguments) {
let arg_text = get_node_text(&arg, source);
if self.is_automatic_variable(&arg, source, node) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"putenv() called with pointer to automatic variable '{}'. \
The argument to putenv() must point to memory with static \
storage duration (static variable or heap allocation). \
Automatic variables are destroyed when the function returns, \
leaving a dangling pointer in the environment.",
arg_text
),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
suggestion: Some(
"Use 'static char env[...]' or malloc(), or prefer setenv() \
which handles memory management automatically"
.to_string(),
),
requires_manual_review: Some(true),
});
}
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_putenv_calls(&child, source, violations);
}
}
}
fn get_first_argument<'a>(&self, arguments: &Node<'a>) -> Option<Node<'a>> {
for i in 0..arguments.child_count() {
if let Some(child) = arguments.child(i) {
if child.kind() != "(" && child.kind() != ")" && child.kind() != "," {
return Some(child);
}
}
}
None
}
fn is_automatic_variable(&self, arg: &Node, source: &str, call_node: &Node) -> bool {
let arg_text = get_node_text(arg, source);
let var_name = if arg.kind() == "identifier" {
arg_text
} else if arg.kind() == "cast_expression" {
if let Some(value) = arg.child_by_field_name("value") {
get_node_text(&value, source)
} else {
return false;
}
} else {
arg_text
};
if let Some(function) = self.find_containing_function(call_node) {
if let Some(declaration) = self.find_variable_declaration(&function, &var_name, source)
{
if self.is_static_declaration(&declaration, source) {
return false; }
if self.is_heap_allocated(&declaration, source) {
return false; }
if self.is_array_declaration(&declaration, source) {
return true; }
if self.is_pointer_declaration(&declaration, source) {
return false; }
return true;
}
}
false
}
fn find_containing_function<'a>(&self, node: &Node<'a>) -> Option<Node<'a>> {
let mut current = Some(*node);
while let Some(node) = current {
if node.kind() == "function_definition" {
return Some(node);
}
current = node.parent();
}
None
}
fn find_variable_declaration<'a>(
&self,
function: &Node<'a>,
var_name: &str,
source: &str,
) -> Option<Node<'a>> {
self.search_declaration(function, var_name, source)
}
fn search_declaration<'a>(
&self,
node: &Node<'a>,
var_name: &str,
source: &str,
) -> Option<Node<'a>> {
if node.kind() == "declaration" {
if let Some(declarator) = node.child_by_field_name("declarator") {
let decl_text = get_node_text(&declarator, source);
if decl_text.contains(var_name) {
return Some(*node);
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
if let Some(found) = self.search_declaration(&child, var_name, source) {
return Some(found);
}
}
}
None
}
fn is_static_declaration(&self, declaration: &Node, source: &str) -> bool {
let decl_text = get_node_text(declaration, source);
decl_text.contains("static")
}
fn is_heap_allocated(&self, declaration: &Node, source: &str) -> bool {
let decl_text = get_node_text(declaration, source);
decl_text.contains("malloc")
|| decl_text.contains("calloc")
|| decl_text.contains("realloc")
}
fn is_array_declaration(&self, declaration: &Node, source: &str) -> bool {
let decl_text = get_node_text(declaration, source);
decl_text.contains('[') && decl_text.contains(']')
}
fn is_pointer_declaration(&self, declaration: &Node, source: &str) -> bool {
let decl_text = get_node_text(declaration, source);
decl_text.contains('*') && !decl_text.contains('[')
}
}