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