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 Env31C;
impl CertRule for Env31C {
fn rule_id(&self) -> &'static str {
"ENV31-C"
}
fn description(&self) -> &'static str {
"Do not rely on an environment pointer following an operation that may invalidate it"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"ENV31-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
self.find_main_functions(node, source, &mut violations);
violations
}
}
impl Env31C {
fn find_main_functions(&self, node: &Node, source: &str, violations: &mut Vec<RuleViolation>) {
if node.kind() == "function_definition" {
if let Some(declarator) = node.child_by_field_name("declarator") {
let func_name = self.get_function_name(&declarator, source);
if func_name == "main" {
if let Some(envp_param) = self.find_envp_parameter(&declarator, source) {
if let Some(body) = node.child_by_field_name("body") {
self.analyze_main_body(&body, source, &envp_param, violations);
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.find_main_functions(&child, source, violations);
}
}
}
fn get_function_name(&self, declarator: &Node, source: &str) -> String {
if declarator.kind() == "identifier" {
return get_node_text(declarator, source).to_string();
}
if declarator.kind() == "function_declarator" {
if let Some(name_node) = declarator.child_by_field_name("declarator") {
return get_node_text(&name_node, source).to_string();
}
}
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
let name = self.get_function_name(&child, source);
if !name.is_empty() {
return name;
}
}
}
String::new()
}
fn find_envp_parameter(&self, declarator: &Node, source: &str) -> Option<String> {
if declarator.kind() == "function_declarator" {
if let Some(params) = declarator.child_by_field_name("parameters") {
let mut param_count = 0;
for i in 0..params.child_count() {
if let Some(child) = params.child(i) {
if child.kind() == "parameter_declaration" {
param_count += 1;
if param_count == 3 {
let param_name = self.extract_param_name(&child, source);
if !param_name.is_empty() {
return Some(param_name);
}
}
}
}
}
}
}
for i in 0..declarator.child_count() {
if let Some(child) = declarator.child(i) {
if let Some(envp) = self.find_envp_parameter(&child, source) {
return Some(envp);
}
}
}
None
}
fn extract_param_name(&self, param: &Node, source: &str) -> String {
for i in 0..param.child_count() {
if let Some(child) = param.child(i) {
if child.kind() == "pointer_declarator" || child.kind() == "array_declarator" {
let name = self.find_identifier(&child, source);
if !name.is_empty() {
return name;
}
} else if child.kind() == "identifier" {
return get_node_text(&child, source).to_string();
}
}
}
String::new()
}
fn find_identifier(&self, node: &Node, source: &str) -> String {
if node.kind() == "identifier" {
return get_node_text(node, source).to_string();
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
let name = self.find_identifier(&child, source);
if !name.is_empty() {
return name;
}
}
}
String::new()
}
fn analyze_main_body(
&self,
body: &Node,
source: &str,
envp_name: &str,
violations: &mut Vec<RuleViolation>,
) {
let mut env_mod_line = None;
let mut env_mod_func = String::new();
self.find_env_modification(body, source, &mut env_mod_line, &mut env_mod_func);
if let Some(mod_line) = env_mod_line {
self.find_envp_usage_after(
body,
source,
envp_name,
mod_line,
&env_mod_func,
violations,
);
}
}
fn find_env_modification(
&self,
node: &Node,
source: &str,
mod_line: &mut Option<usize>,
mod_func: &mut 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 self.is_env_modifying_function(&func_name) {
let line = node.start_position().row + 1;
if mod_line.is_none() {
*mod_line = Some(line);
*mod_func = func_name.to_string();
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.find_env_modification(&child, source, mod_line, mod_func);
}
}
}
fn find_envp_usage_after(
&self,
node: &Node,
source: &str,
envp_name: &str,
mod_line: usize,
mod_func: &str,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "identifier" {
let ident = get_node_text(node, source);
if ident == envp_name {
let usage_line = node.start_position().row + 1;
if usage_line > mod_line {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"Using '{}' after call to '{}' on line {}. The environment pointer \
may be invalidated after environment-modifying functions. \
Use 'environ' global variable instead.",
envp_name, mod_func, mod_line
),
severity: self.severity(),
line: usage_line,
column: node.start_position().column + 1,
file_path: String::new(),
suggestion: Some(format!(
"Replace '{}' with 'extern char **environ' and use 'environ' instead",
envp_name
)),
requires_manual_review: None,
});
return; }
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.find_envp_usage_after(
&child, source, envp_name, mod_line, mod_func, violations,
);
}
}
}
fn is_env_modifying_function(&self, name: &str) -> bool {
matches!(
name,
"setenv" | "putenv" | "_putenv_s" | "unsetenv" | "_wputenv_s"
)
}
}