use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use tree_sitter::Node;
#[derive(Debug)]
pub struct Env01C;
impl Env01C {
pub fn new() -> Self {
Env01C
}
fn is_getenv_call(&self, node: &Node, source: &str) -> bool {
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
return func_name == "getenv";
}
}
false
}
fn check_unsafe_usage(&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 == "strcpy" || func_name == "strcat" {
if let Some(arguments) = node.child_by_field_name("arguments") {
let mut cursor = arguments.walk();
for child in arguments.children(&mut cursor) {
if self.is_getenv_call(&child, source) {
violations.push(RuleViolation {
rule_id: "ENV01-C".to_string(),
severity: Severity::High,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"getenv() result used directly in {} without size or NULL check",
func_name
),
file_path: String::new(),
suggestion: Some("Check getenv() return for NULL and use strlen() to allocate appropriate buffer size".to_string()),
requires_manual_review: Some(false),
});
}
}
}
}
}
}
}
fn check_fixed_buffer(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "declaration" {
let mut has_fixed_size_array = false;
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "array_declarator" {
if let Some(size) = child.child_by_field_name("size") {
let size_text = get_node_text(&size, source);
if size_text.contains("PATH_MAX") || size_text.contains("MAX") {
has_fixed_size_array = true;
}
}
}
}
if has_fixed_size_array {
violations.push(RuleViolation {
rule_id: "ENV01-C".to_string(),
severity: Severity::Medium,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: "Fixed-size buffer may be used with environment variable".to_string(),
file_path: String::new(),
suggestion: Some(
"Use dynamic allocation based on strlen() of getenv() result".to_string(),
),
requires_manual_review: Some(true),
});
}
}
}
fn check_node(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
self.check_unsafe_usage(node, source, violations);
self.check_fixed_buffer(node, source, violations);
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.check_node(&child, source, violations);
}
}
}
impl Default for Env01C {
fn default() -> Self {
Self::new()
}
}
impl CertRule for Env01C {
fn rule_id(&self) -> &'static str {
"ENV01-C"
}
fn description(&self) -> &'static str {
"Do not make assumptions about the size of an environment variable"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"ENV01-C"
}
fn check(&self, root_node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.check_node(root_node, source, &mut violations);
violations
}
}