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 Pos36C;
impl CertRule for Pos36C {
fn rule_id(&self) -> &'static str {
"POS36-C"
}
fn description(&self) -> &'static str {
"Observe correct revocation order while relinquishing privileges"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"POS36-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
if node.kind() == "function_definition" {
if let Some(body) = node.child_by_field_name("body") {
self.check_privilege_order(&body, source, &mut violations);
}
}
if node.kind() == "translation_unit" {
self.check_privilege_order(node, source, &mut violations);
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
violations.extend(self.check(&child, source));
}
}
violations
}
}
impl Pos36C {
fn check_privilege_order(
&self,
scope: &Node,
source: &str,
violations: &mut Vec<RuleViolation>,
) {
let mut calls = Vec::new();
self.collect_priv_calls(scope, source, &mut calls);
let mut seen_setuid = false;
for call in &calls {
if call.is_setuid {
seen_setuid = true;
} else if call.is_setgid && seen_setuid {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::High,
message: "setgid() called after setuid() - incorrect privilege revocation order".to_string(),
file_path: String::new(),
line: call.line,
column: call.column,
suggestion: Some(
"Call setgid() BEFORE setuid() to ensure group privileges are dropped while still having superuser privileges".to_string()
),
..Default::default()
});
}
}
if seen_setuid && !calls.iter().any(|c| c.is_setgid) {
if let Some(setuid_call) = calls.iter().find(|c| c.is_setuid) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
severity: Severity::High,
message: "setuid() called without setgid() - incomplete privilege revocation"
.to_string(),
file_path: String::new(),
line: setuid_call.line,
column: setuid_call.column,
suggestion: Some(
"Call setgid() before setuid() to drop both group and user privileges"
.to_string(),
),
..Default::default()
});
}
}
}
fn collect_priv_calls(&self, node: &Node, source: &str, calls: &mut Vec<PrivCall>) {
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 == "setuid" {
calls.push(PrivCall {
is_setuid: true,
is_setgid: false,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
});
} else if func_name == "setgid" {
calls.push(PrivCall {
is_setuid: false,
is_setgid: true,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
});
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.collect_priv_calls(&child, source, calls);
}
}
}
}
struct PrivCall {
is_setuid: bool,
is_setgid: bool,
line: usize,
column: usize,
}