1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024 BISSELL Homecare, Inc.
//! ERR32-C: Do not rely on indeterminate values of errno
//!
//! This rule detects violations where errno is checked without first calling
//! a function that sets it, or where errno is used in signal handlers.
//!
//! CERT C reference:
//! https://wiki.sei.cmu.edu/confluence/display/c/ERR32-C.+Do+not+rely+on+indeterminate+values+of+errno
use super::super::{CertRule, RuleViolation};
use crate::manifest::{RuleCategory, Severity};
use crate::utility::cert_c::ast_utils::get_node_text;
use lang_parsing_substrate::query;
use std::collections::HashSet;
use tree_sitter::Node;
#[derive(Debug)]
pub struct Err32C;
impl Err32C {
pub fn new() -> Self {
Err32C
}
/// First pass: collect function names registered as signal handlers
fn collect_signal_handlers(&self, node: &Node, source: &str, handlers: &mut HashSet<String>) {
for n in query::find_descendants(*node, |_| true) {
// Check for signal(SIG..., handler) pattern
if n.kind() == "call_expression" {
if let Some(function) = n.child_by_field_name("function") {
let func_name = get_node_text(&function, source);
if func_name == "signal" {
if let Some(args) = n.child_by_field_name("arguments") {
// Second argument is the handler
let mut arg_idx = 0;
let mut cursor = args.walk();
for child in args.children(&mut cursor) {
if child.kind() != "," && child.kind() != "(" && child.kind() != ")"
{
arg_idx += 1;
if arg_idx == 2 {
let handler_name =
get_node_text(&child, source).trim().to_string();
if !handler_name.starts_with("SIG_") {
handlers.insert(handler_name);
}
}
}
}
}
}
}
}
// Check for sigaction: .sa_handler = func_name or act.sa_handler = func_name
if n.kind() == "assignment_expression" {
if let Some(left) = n.child_by_field_name("left") {
let left_text = get_node_text(&left, source);
if left_text.contains("sa_handler") || left_text.contains("sa_sigaction") {
if let Some(right) = n.child_by_field_name("right") {
let handler_name = get_node_text(&right, source).trim().to_string();
if !handler_name.is_empty() && !handler_name.starts_with("SIG_") {
handlers.insert(handler_name);
}
}
}
}
}
}
}
/// Extract just the function name from a declarator node
fn get_function_name(&self, declarator: &Node, source: &str) -> String {
// For function_declarator nodes, get the nested declarator (function name)
if declarator.kind() == "function_declarator" {
if let Some(name_node) = declarator.child_by_field_name("declarator") {
return get_node_text(&name_node, source).trim().to_string();
}
}
// Fallback: extract function name by splitting on '(' to remove parameters
let declarator_text = get_node_text(declarator, source);
declarator_text
.split('(')
.next()
.unwrap_or(&declarator_text)
.trim()
.to_string()
}
/// Check if a function definition saves and restores errno (compliance pattern)
fn has_errno_save_restore(&self, func_node: &Node, source: &str) -> bool {
let func_text = get_node_text(func_node, source);
// Pattern: saves errno at start and restores it at end
// Look for: save_var = errno; ... errno = save_var;
// Common patterns: save_errno, saved_errno, errno_save, old_errno
let save_patterns = [
"save_errno",
"saved_errno",
"errno_save",
"old_errno",
"errno_saved",
];
for pattern in &save_patterns {
// Check for save: pattern = errno
let has_save = func_text.contains(&format!("{} = errno", pattern))
|| func_text.contains(&format!("{}=errno", pattern));
// Check for restore: errno = pattern
let has_restore = func_text.contains(&format!("errno = {}", pattern))
|| func_text.contains(&format!("errno={}", pattern));
if has_save && has_restore {
return true;
}
}
false
}
/// Check if we're in a signal handler function
fn is_in_signal_handler(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
) -> bool {
let mut current = Some(*node);
while let Some(n) = current {
if n.kind() == "function_definition" {
if let Some(declarator) = n.child_by_field_name("declarator") {
let func_name = self.get_function_name(&declarator, source);
// Check if registered as signal handler
if registered_handlers.contains(&func_name) {
return true;
}
// Also use heuristic: name contains "handler" or starts with "sig"
let lower_name = func_name.to_lowercase();
if lower_name.contains("handler") || lower_name.starts_with("sig") {
return true;
}
}
break;
}
current = n.parent();
}
false
}
/// Get the containing function definition for a node
fn get_containing_function<'a>(&self, node: &Node<'a>) -> Option<Node<'a>> {
let mut current = Some(*node);
while let Some(n) = current {
if n.kind() == "function_definition" {
return Some(n);
}
current = n.parent();
}
None
}
/// Check for errno usage in signal handlers
fn check_errno_in_handler(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
if node.kind() == "identifier" {
let name = get_node_text(node, source);
if name == "errno" && self.is_in_signal_handler(node, source, registered_handlers) {
// Check if the containing function has errno save/restore pattern
if let Some(func_node) = self.get_containing_function(node) {
if self.has_errno_save_restore(&func_node, source) {
return; // Compliant: errno is saved and restored
}
}
violations.push(RuleViolation {
rule_id: "ERR32-C".to_string(),
severity: Severity::High,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: "errno should not be used in signal handlers".to_string(),
file_path: String::new(),
suggestion: Some(
"Save and restore errno in signal handler if needed".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
/// Check for perror/strerror in signal handlers (they use errno)
fn check_error_functions_in_handler(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
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 == "perror" || func_name == "strerror")
&& self.is_in_signal_handler(node, source, registered_handlers)
{
violations.push(RuleViolation {
rule_id: "ERR32-C".to_string(),
severity: Severity::High,
line: node.start_position().row + 1,
column: node.start_position().column + 1,
message: format!(
"{}() should not be called in signal handlers (uses errno)",
func_name
),
file_path: String::new(),
suggestion: Some(
"Avoid error reporting functions in signal handlers".to_string(),
),
requires_manual_review: Some(false),
});
}
}
}
}
fn check_node(
&self,
node: &Node,
source: &str,
registered_handlers: &HashSet<String>,
violations: &mut Vec<RuleViolation>,
) {
for n in query::find_descendants(*node, |_| true) {
self.check_errno_in_handler(&n, source, registered_handlers, violations);
self.check_error_functions_in_handler(&n, source, registered_handlers, violations);
}
}
}
impl Default for Err32C {
fn default() -> Self {
Self::new()
}
}
impl CertRule for Err32C {
fn rule_id(&self) -> &'static str {
"ERR32-C"
}
fn description(&self) -> &'static str {
"Do not rely on indeterminate values of errno"
}
fn severity(&self) -> Severity {
Severity::High
}
fn category(&self) -> RuleCategory {
RuleCategory::Rule
}
fn cert_id(&self) -> &'static str {
"ERR32-C"
}
fn check(&self, root_node: &Node, source: &str) -> Vec<RuleViolation> {
let mut violations = Vec::new();
// First pass: collect signal handler function names
let mut registered_handlers = HashSet::new();
self.collect_signal_handlers(root_node, source, &mut registered_handlers);
// Second pass: check errno/perror/strerror usage in handlers
self.check_node(root_node, source, ®istered_handlers, &mut violations);
violations
}
}