1macro_rules! define_bitset_and_security_op {
31 (
32 $module:ident,
33 $function:ident,
34 $marker:ident,
35 $op_id:literal,
36 $left:ident,
37 $right:ident,
38 $doc:literal,
39 tests { $($test_name:ident: ($lhs:expr, $rhs:expr) => $expected:expr;)+ }
40 ) => {
41 #[doc = $doc]
42 pub mod $module {
43 use vyre::ir::Program;
44 use vyre_primitives::bitset::and::bitset_and;
45 use vyre_primitives::graph::csr_forward_traverse::bitset_words;
46
47 pub(crate) const OP_ID: &str = $op_id;
48
49 #[must_use]
51 pub fn $function(
52 node_count: u32,
53 $left: &str,
54 $right: &str,
55 out: &str,
56 ) -> Program {
57 let words = bitset_words(node_count);
58 crate::region::tag_program(OP_ID, bitset_and($left, $right, out, words))
59 }
60
61 #[must_use]
63 #[cfg(test)]
64 pub(crate) fn cpu_ref($left: &[u32], $right: &[u32]) -> Vec<u32> {
65 vyre_primitives::bitset::and::cpu_ref($left, $right)
66 }
67
68 #[doc = concat!("Soundness marker for [`", stringify!($function), "`].")]
69 pub struct $marker;
70
71 impl vyre::soundness::SoundnessTagged for $marker {
72 fn soundness(&self) -> vyre::soundness::Soundness {
73 vyre::soundness::Soundness::Exact
74 }
75 }
76
77 #[cfg(test)]
78 mod tests {
79 use super::*;
80
81 $(
82 #[test]
83 fn $test_name() {
84 assert_eq!(cpu_ref($lhs, $rhs), $expected);
85 }
86 )+
87 }
88 }
89 };
90}
91
92macro_rules! define_bitset_and_not_security_op {
93 (
94 $module:ident,
95 $function:ident,
96 $marker:ident,
97 $op_id:literal,
98 $left:ident,
99 $right:ident,
100 $doc:literal,
101 tests { $($test_name:ident: ($lhs:expr, $rhs:expr) => $expected:expr;)+ }
102 ) => {
103 #[doc = $doc]
104 pub mod $module {
105 use vyre::ir::Program;
106 use vyre_primitives::bitset::and_not::bitset_and_not;
107 use vyre_primitives::graph::csr_forward_traverse::bitset_words;
108
109 pub(crate) const OP_ID: &str = $op_id;
110
111 #[must_use]
113 pub fn $function(
114 node_count: u32,
115 $left: &str,
116 $right: &str,
117 out: &str,
118 ) -> Program {
119 let words = bitset_words(node_count);
120 crate::region::tag_program(OP_ID, bitset_and_not($left, $right, out, words))
121 }
122
123 #[must_use]
125 #[cfg(test)]
126 pub(crate) fn cpu_ref($left: &[u32], $right: &[u32]) -> Vec<u32> {
127 vyre_primitives::bitset::and_not::cpu_ref($left, $right)
128 }
129
130 #[doc = concat!("Soundness marker for [`", stringify!($function), "`].")]
131 pub struct $marker;
132
133 impl vyre::soundness::SoundnessTagged for $marker {
134 fn soundness(&self) -> vyre::soundness::Soundness {
135 vyre::soundness::Soundness::Exact
136 }
137 }
138
139 #[cfg(test)]
140 mod tests {
141 use super::*;
142
143 $(
144 #[test]
145 fn $test_name() {
146 assert_eq!(cpu_ref($lhs, $rhs), $expected);
147 }
148 )+
149 }
150 }
151 };
152}
153
154pub mod aliases_dataflow;
155define_bitset_and_security_op!(
156 auth_check_dominates,
157 auth_check_dominates,
158 AuthCheckDominates,
159 "vyre-libs::security::auth_check_dominates",
160 auth_doms,
161 sensitive_op_set,
162 "`auth_check_dominates` - authorization check dominates sensitive operation.",
163 tests {
164 protected_op_returns_set: (&[0b1100], &[0b0100]) => vec![0b0100];
165 unprotected_op_returns_empty: (&[0b0001], &[0b1110]) => vec![0];
166 no_sensitive_ops: (&[0xFFFF], &[0]) => vec![0];
167 no_auth_checks: (&[0], &[0xFFFF]) => vec![0];
168 }
169);
170pub mod bounded_by_comparison;
171define_bitset_and_security_op!(
172 buffer_size_check,
173 buffer_size_check,
174 BufferSizeCheck,
175 "vyre-libs::security::buffer_size_check",
176 size_compared,
177 user_input_set,
178 "`buffer_size_check` - buffer size is compared to user input.",
179 tests {
180 checked_size_returns_set: (&[0b1010], &[0b1100]) => vec![0b1000];
181 unchecked_size_returns_empty: (&[0b0001], &[0b1110]) => vec![0];
182 no_user_input_yields_empty: (&[0xFFFF], &[0]) => vec![0];
183 full_overlap: (&[0xDEAD], &[0xDEAD]) => vec![0xDEAD];
184 }
185);
186mod catalog;
187pub mod dominator_tree;
188pub mod facts;
189pub(crate) mod flow_composition;
190pub mod flows_to;
191pub mod flows_to_to_sink;
192pub mod flows_to_with_sanitizer;
193#[cfg(feature = "external_ifds_engine")]
207pub mod external_ifds;
208define_bitset_and_not_security_op!(
209 format_string_check,
210 format_string_check,
211 FormatStringCheck,
212 "vyre-libs::security::format_string_check",
213 format_arg_pts,
214 non_literal_set,
215 "`format_string_check` - format argument is reachable only from literals.",
216 tests {
217 literal_only_returns_full: (&[0xFFFF], &[0]) => vec![0xFFFF];
218 user_input_present_subtracts: (&[0xFFFF], &[0xFF00]) => vec![0x00FF];
219 fully_user_input_returns_empty: (&[0xDEAD], &[0xFFFF]) => vec![0];
220 distributes: (&[0xFFFF, 0x0F0F], &[0xFF00, 0x0000]) => vec![0x00FF, 0x0F0F];
221 }
222);
223pub mod integer_overflow_arith;
224pub mod label_by_family;
225define_bitset_and_security_op!(
226 lock_dominates,
227 lock_dominates,
228 LockDominates,
229 "vyre-libs::security::lock_dominates",
230 lock_doms,
231 shared_access_set,
232 "`lock_dominates` - lock acquisition dominates shared-state access.",
233 tests {
234 locked_access: (&[0b1110], &[0b0010]) => vec![0b0010];
235 unlocked_access: (&[0b0001], &[0b0010]) => vec![0];
236 no_accesses: (&[0xFFFF], &[0]) => vec![0];
237 empty_lock_set: (&[0], &[0xFFFF]) => vec![0];
238 }
239);
240define_bitset_and_security_op!(
241 path_canonical,
242 path_canonical,
243 PathCanonical,
244 "vyre-libs::security::path_canonical",
245 canonicalizer_dominates,
246 fs_op_set,
247 "`path_canonical` - path string was canonicalized before a filesystem operation.",
248 tests {
249 canonicalized_op: (&[0b1110], &[0b0010]) => vec![0b0010];
250 uncanonicalized_op: (&[0b0001], &[0b0010]) => vec![0];
251 no_fs_ops: (&[0xFFFF], &[0]) => vec![0];
252 distributes: (&[0xFF00, 0x00FF], &[0xFFFF, 0xFFFF]) => vec![0xFF00, 0x00FF];
253 }
254);
255pub mod path_reconstruct;
256pub mod predicate_catalog;
257pub mod relation_analyzer;
258pub mod reporter;
259pub mod sanitized_by;
260define_bitset_and_security_op!(
261 sanitizer_dominates,
262 sanitizer_dominates,
263 SanitizerDominates,
264 "vyre-libs::security::sanitizer_dominates",
265 sanitizer_doms,
266 sink_set,
267 "`sanitizer_dominates` - sanitizer dominates the queried sink.",
268 tests {
269 dominated_sink_returns_set: (&[0b1111], &[0b0010]) => vec![0b0010];
270 non_dominated_sink_returns_empty: (&[0b0001], &[0b0010]) => vec![0];
271 no_sinks_returns_empty: (&[0xFFFF], &[0]) => vec![0];
272 distributes_per_word: (&[0xFF00, 0x00FF], &[0x0FF0, 0x0FF0]) => vec![0x0F00, 0x00F0];
273 }
274);
275pub mod sink_intersection;
276define_bitset_and_security_op!(
277 sql_param_bound,
278 sql_param_bound,
279 SqlParamBound,
280 "vyre-libs::security::sql_param_bound",
281 param_binding_set,
282 sql_query_set,
283 "`sql_param_bound` - SQL query is built through parameter binding.",
284 tests {
285 parameterized_query: (&[0b1100], &[0b0100]) => vec![0b0100];
286 raw_concat_query: (&[0b0001], &[0b0010]) => vec![0];
287 no_queries: (&[0xFFFF], &[0]) => vec![0];
288 distributes: (&[0xFF00, 0xF0F0], &[0x0FF0, 0x0F0F]) => vec![0x0F00, 0x0000];
289 }
290);
291pub mod taint_flow;
292pub mod taint_kill;
293pub mod taint_pollution;
294pub mod topology;
295define_bitset_and_not_security_op!(
296 unchecked_return,
297 unchecked_return,
298 UncheckedReturn,
299 "vyre-libs::security::unchecked_return",
300 use_set,
301 check_dominates,
302 "`unchecked_return` - sensitive return-value use lacks a dominating check.",
303 tests {
304 use_without_check_returns_set: (&[0b1100], &[0b0001]) => vec![0b1100];
305 use_with_dominating_check_returns_empty: (&[0b0010], &[0b0010]) => vec![0];
306 no_uses_returns_empty: (&[0], &[0xFFFF]) => vec![0];
307 distributes: (&[0xFFFF, 0x0F0F], &[0x00FF, 0xF000]) => vec![0xFF00, 0x0F0F];
308 }
309);
310define_bitset_and_security_op!(
311 xss_escape,
312 xss_escape,
313 XssEscape,
314 "vyre-libs::security::xss_escape",
315 escape_dominates,
316 render_set,
317 "`xss_escape` - HTML output escaping dominates render sites.",
318 tests {
319 escaped_render: (&[0b1100], &[0b0100]) => vec![0b0100];
320 unescaped_render: (&[0b0001], &[0b0010]) => vec![0];
321 no_renders: (&[0xFFFF], &[0]) => vec![0];
322 no_escape_dominators: (&[0], &[0xFFFF]) => vec![0];
323 }
324);
325
326pub use aliases_dataflow::{aliases_dataflow, try_aliases_dataflow};
327pub use auth_check_dominates::auth_check_dominates;
328pub use bounded_by_comparison::bounded_by_comparison;
329pub use buffer_size_check::buffer_size_check;
330pub use dominator_tree::dominator_tree;
331pub use facts::{
332 AnalysisFact, AnalysisFactColumns, AnalysisFactError, AnalysisFactTable, AnalysisSourceSpan,
333 FactId, FactKind, FindingProofBundle, FindingProofStep,
334};
335pub use flows_to::flows_to;
336pub use flows_to_to_sink::flows_to_to_sink;
337pub use flows_to_with_sanitizer::flows_to_with_sanitizer;
338#[cfg(feature = "external_ifds_engine")]
342pub use external_ifds::{
343 route_security_taint_through_external_ifds, security_witness_path_from_external_path,
344 ExternalIfdsSecurityBuffers, ExternalIfdsSecurityDispatch, ExternalIfdsSecurityRouteError,
345 SecurityFindingWitnessPath, SecurityWitnessPathError, SecurityWitnessStatement,
346 EXTERNAL_IFDS_SECURITY_BACKEND_ID,
347};
348pub use format_string_check::format_string_check;
349pub use integer_overflow_arith::integer_overflow_arith;
350pub use label_by_family::label_by_family;
351pub use lock_dominates::lock_dominates;
352pub use path_canonical::path_canonical;
353pub use path_reconstruct::path_reconstruct;
354pub use predicate_catalog::{
355 security_predicate_row_by_op_id, security_predicate_rows, try_security_predicate_rows,
356 SecurityPredicateOperation, SecurityPredicateRow,
357};
358pub use relation_analyzer::{
359 generated_relation_finding_fact_ids, run_generated_security_relation_analyzer,
360 GeneratedSecurityRelationAnalyzerEvidence, GeneratedSecurityRelationAnalyzerReport,
361 GeneratedSecurityRelationAnalyzerRunStats, GeneratedSecurityRelationAnalyzerSpec,
362 SecurityRelationAnalyzerError, SecurityRelationQueryFamily,
363 SECURITY_RELATION_ANALYZER_SCHEMA_VERSION,
364};
365pub use reporter::{
366 render_security_reporter_output, SecurityReporterError, SecurityReporterFinding,
367 SecurityReporterOutputBytes, SecurityReporterPlannerPath, SecurityReporterSourceFile,
368 SECURITY_REPORTER_SCHEMA_VERSION,
369};
370pub use sanitized_by::sanitized_by;
371pub use sanitizer_dominates::sanitizer_dominates;
372pub use sink_intersection::sink_intersection;
373pub use sql_param_bound::sql_param_bound;
374pub use taint_flow::taint_flow;
375pub use taint_kill::taint_kill;
376pub use taint_pollution::taint_pollution;
377pub use unchecked_return::unchecked_return;
378pub use xss_escape::xss_escape;
379
380pub(crate) fn assert_security_inputs(op: &str, node_count: u32, buffers: &[(&str, &str)]) {
388 assert!(
389 node_count > 0,
390 "Fix: {op} node_count must be positive; got 0. \
391 A taint analysis over an empty program graph has no meaningful \
392 result - callers must skip empty translation units before lowering."
393 );
394 for (role, name) in buffers {
395 assert!(
396 !name.is_empty(),
397 "Fix: {op} requires non-empty buffer name for {role}. \
398 Empty buffer names alias to the zero-length lookup key in the \
399 validator and produce silent miscompiles. Pass a stable \
400 non-empty buffer identifier."
401 );
402 }
403}