use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use std::collections::{HashMap, HashSet};
use tree_sitter::Node;
pub struct Dcl39C;
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct StructVarInfo {
var_name: String,
#[allow(dead_code)]
struct_type: String,
is_zeroed: bool,
}
impl CertRule for Dcl39C {
fn rule_id(&self) -> &'static str {
"DCL39-C"
}
fn description(&self) -> &'static str {
"Avoid information leakage when passing a structure across a trust boundary"
}
fn severity(&self) -> Severity {
Severity::Medium
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"DCL39-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let mut struct_vars: HashMap<String, StructVarInfo> = HashMap::new();
let mut safe_struct_types: HashSet<String> = HashSet::new();
self.find_safe_struct_types(node, source, &mut safe_struct_types);
self.analyze_structures(
node,
source,
&mut struct_vars,
&safe_struct_types,
&mut violations,
);
violations
}
}
impl Dcl39C {
fn find_safe_struct_types(&self, node: &Node, source: &str, safe_types: &mut HashSet<String>) {
if node.kind() == "struct_specifier" {
let struct_text = get_node_text(node, source);
let struct_name = self.extract_struct_name(node, source);
if struct_text.contains("__attribute__") && struct_text.contains("packed") {
if !struct_name.is_empty() {
safe_types.insert(format!("struct {}", struct_name));
}
}
if self.has_explicit_padding_fields(node, source) {
if !struct_name.is_empty() {
safe_types.insert(format!("struct {}", struct_name));
}
}
if self.has_bitfield_padding(node, source) {
if !struct_name.is_empty() {
safe_types.insert(format!("struct {}", struct_name));
}
}
if self.is_inside_pragma_pack(node, source) {
if !struct_name.is_empty() {
safe_types.insert(format!("struct {}", struct_name));
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.find_safe_struct_types(&child, source, safe_types);
}
}
}
fn is_inside_pragma_pack(&self, node: &Node, source: &str) -> bool {
let struct_line = node.start_position().row;
let source_lines: Vec<&str> = source.lines().collect();
let mut found_push = false;
for line_idx in (0..struct_line).rev() {
if line_idx >= source_lines.len() {
continue;
}
let line = source_lines[line_idx];
if line.contains("#pragma") && line.contains("pack") {
if line.contains("push") && line.contains("1") {
found_push = true;
break;
} else if line.contains("pop") {
return false;
}
}
}
if found_push {
for line in source_lines.iter().skip(struct_line + 1) {
if line.contains("#pragma") && line.contains("pack") && line.contains("pop") {
return true;
}
}
}
false
}
fn extract_struct_name(&self, struct_node: &Node, source: &str) -> String {
for i in 0..struct_node.child_count() {
if let Some(child) = struct_node.child(i) {
if child.kind() == "type_identifier" {
return get_node_text(&child, source).to_string();
}
}
}
String::new()
}
fn has_explicit_padding_fields(&self, struct_node: &Node, source: &str) -> bool {
for i in 0..struct_node.child_count() {
if let Some(child) = struct_node.child(i) {
if child.kind() == "field_declaration_list" {
for j in 0..child.child_count() {
if let Some(field) = child.child(j) {
let field_text = get_node_text(&field, source).to_lowercase();
if field_text.contains("padding") {
return true;
}
}
}
}
}
}
false
}
fn has_bitfield_padding(&self, struct_node: &Node, source: &str) -> bool {
for i in 0..struct_node.child_count() {
if let Some(child) = struct_node.child(i) {
if child.kind() == "field_declaration_list" {
for j in 0..child.child_count() {
if let Some(field) = child.child(j) {
if field.kind() == "field_declaration" {
let field_text = get_node_text(&field, source).to_lowercase();
if field_text.contains(":") && field_text.contains("padding") {
return true;
}
}
}
}
}
}
}
false
}
fn analyze_structures(
&self,
node: &Node,
source: &str,
struct_vars: &mut HashMap<String, StructVarInfo>,
safe_struct_types: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "declaration" {
if let Some((var_name, struct_type)) = self.extract_struct_declaration(node, source) {
struct_vars.insert(
var_name.clone(),
StructVarInfo {
var_name,
struct_type,
is_zeroed: false, },
);
}
}
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_trust_boundary_function(&func_name) {
if let Some(args) = node.child_by_field_name("arguments") {
let arg_list = self.get_arguments(&args, source);
for arg in &arg_list {
if let Some(stripped) = arg.strip_prefix('&') {
let var_name = stripped.trim().to_string();
if let Some(info) = struct_vars.get(&var_name) {
if safe_struct_types.contains(&info.struct_type) {
continue;
}
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"Structure '{}' passed to trust boundary function '{}' \
may leak padding bytes. Use packed attributes, explicit \
padding fields, or serialize fields individually.",
var_name, func_name
),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
suggestion: Some(format!(
"Use __attribute__((__packed__)) or serialize fields: \
copy_to_user(buf, &{}.a, sizeof({}.a)); ...",
var_name, var_name
)),
requires_manual_review: None,
});
}
}
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.analyze_structures(&child, source, struct_vars, safe_struct_types, violations);
}
}
}
fn extract_struct_declaration(&self, decl: &Node, source: &str) -> Option<(String, String)> {
let decl_text = get_node_text(decl, source);
if !decl_text.contains("struct ") {
return None;
}
let mut struct_type = String::new();
let mut var_name = String::new();
for i in 0..decl.child_count() {
if let Some(child) = decl.child(i) {
if child.kind() == "struct_specifier" || child.kind() == "type_identifier" {
struct_type = get_node_text(&child, source).to_string();
}
if child.kind() == "init_declarator" || child.kind() == "identifier" {
var_name = self.extract_var_name(&child, source);
}
}
}
if !var_name.is_empty() && !struct_type.is_empty() {
Some((var_name, struct_type))
} else {
None
}
}
fn extract_var_name(&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.extract_var_name(&child, source);
if !name.is_empty() {
return name;
}
}
}
String::new()
}
fn is_trust_boundary_function(&self, name: &str) -> bool {
matches!(
name,
"copy_to_user"
| "write"
| "send"
| "sendto"
| "sendmsg"
| "ioctl"
| "fwrite"
| "writev"
)
}
fn get_arguments(&self, args_node: &Node, source: &str) -> Vec<String> {
let mut arguments = Vec::new();
for i in 0..args_node.child_count() {
if let Some(child) = args_node.child(i) {
let kind = child.kind();
if kind != "," && kind != "(" && kind != ")" {
let arg_text = get_node_text(&child, source).to_string();
arguments.push(arg_text);
}
}
}
arguments
}
}