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 Pos05C;
impl CertRule for Pos05C {
fn rule_id(&self) -> &'static str {
"POS05-C"
}
fn description(&self) -> &'static str {
"Limit access to files by creating a jail"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"POS05-C"
}
fn check(&self, node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
let has_chroot_setup = self.has_chroot_jail_setup(node, source);
if !has_chroot_setup {
self.check_file_operations_without_jail(node, source, &mut violations);
}
violations
}
}
impl Pos05C {
fn has_chroot_jail_setup(&self, node: &Node, source: &str) -> bool {
let mut has_chroot = false;
let mut has_chdir = false;
let mut has_setuid = false;
self.search_chroot_pattern(
node,
source,
&mut has_chroot,
&mut has_chdir,
&mut has_setuid,
);
has_chroot && has_chdir && has_setuid
}
#[allow(clippy::only_used_in_recursion)]
fn search_chroot_pattern(
&self,
node: &Node,
source: &str,
has_chroot: &mut bool,
has_chdir: &mut bool,
has_setuid: &mut bool,
) {
if node.kind() == "call_expression" {
if let Some(function) = node.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
match func_name {
"chroot" => *has_chroot = true,
"chdir" => {
if let Some(arguments) = node.child_by_field_name("arguments") {
let args_text = get_node_text(&arguments, source);
if args_text.contains("\"/\"") {
*has_chdir = true;
}
}
}
"setuid" | "setgid" => *has_setuid = true,
_ => {}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.search_chroot_pattern(&child, source, has_chroot, has_chdir, has_setuid);
}
}
}
fn check_file_operations_without_jail(
&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 self.is_file_operation(func_name) {
if let Some(arguments) = node.child_by_field_name("arguments") {
if self.uses_user_input(&arguments, source) {
violations.push(RuleViolation {
rule_id: self.rule_id().to_string(),
message: format!(
"File operation '{}' uses potentially user-controlled input \
without a chroot jail. Programs running with elevated \
privileges should establish a chroot jail (chroot + chdir + \
setuid) before performing file operations with user input.",
func_name
),
severity: self.severity(),
line: node.start_position().row + 1,
column: node.start_position().column + 1,
file_path: String::new(),
suggestion: Some(
"Create a chroot jail before file operations: \
setuid(0); chroot(\"jail\"); chdir(\"/\"); \
setgid(getgid()); setuid(getuid());"
.to_string(),
),
requires_manual_review: Some(true),
});
}
}
}
}
}
for i in 0..node.child_count() {
if let Some(child) = node.child(i) {
self.check_file_operations_without_jail(&child, source, violations);
}
}
}
fn is_file_operation(&self, func_name: &str) -> bool {
matches!(
func_name,
"fopen"
| "open"
| "creat"
| "freopen"
| "fopen_s"
| "remove"
| "rename"
| "unlink"
| "chmod"
| "chown"
)
}
fn uses_user_input(&self, arguments: &Node, source: &str) -> bool {
let args_text = get_node_text(arguments, source);
args_text.contains("argv[")
|| args_text.contains("getenv")
|| args_text.contains("gets")
|| args_text.contains("fgets")
|| args_text.contains("scanf")
}
}