Skip to main content

cljrs_runtime/env/
policy.rs

1//! Dynamic execution policy for restricted evaluator profiles.
2
3use std::cell::Cell;
4
5use crate::env::error::{EvalError, EvalResult};
6
7thread_local! {
8    static TRANSACTION_DEPTH: Cell<usize> = const { Cell::new(0) };
9    static TRANSACTION_GENSYM: Cell<u64> = const { Cell::new(0) };
10}
11cljrs_gc::mutator_slot!("cljrs-runtime/TRANSACTION_DEPTH", cell TRANSACTION_DEPTH: usize);
12cljrs_gc::mutator_slot!("cljrs-runtime/TRANSACTION_GENSYM", cell TRANSACTION_GENSYM: u64);
13
14/// Installs the side-effect-free transaction policy for its dynamic extent.
15#[must_use = "dropping the guard removes the transaction execution policy"]
16pub struct TransactionPolicyGuard;
17
18impl TransactionPolicyGuard {
19    pub fn install() -> Self {
20        TRANSACTION_DEPTH.with(|depth| {
21            if depth.get() == 0 {
22                TRANSACTION_GENSYM.with(|counter| counter.set(0));
23            }
24            depth.set(depth.get() + 1);
25        });
26        Self
27    }
28}
29
30impl Drop for TransactionPolicyGuard {
31    fn drop(&mut self) {
32        TRANSACTION_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
33    }
34}
35
36pub fn transaction_policy_active() -> bool {
37    TRANSACTION_DEPTH.with(|depth| depth.get() != 0)
38}
39
40/// Return an invocation-local deterministic gensym sequence number when the
41/// transaction policy is active.
42pub fn next_transaction_gensym() -> Option<u64> {
43    if !transaction_policy_active() {
44        return None;
45    }
46    Some(TRANSACTION_GENSYM.with(|counter| {
47        let current = counter.get();
48        counter.set(current.wrapping_add(1));
49        current
50    }))
51}
52
53fn forbidden(operation: &str) -> EvalError {
54    EvalError::ForbiddenEffect(operation.to_string())
55}
56
57/// Check a native builtin at its final call boundary.
58///
59/// The transaction environment registers only clojurust's builtins, so this
60/// denylist is the capability surface: filesystem, output, clocks, randomness,
61/// process-global state, blocking/concurrency, and Rust object construction.
62pub fn check_native(name: &str) -> EvalResult<()> {
63    if !transaction_policy_active() {
64        return Ok(());
65    }
66    const DENIED: &[&str] = &[
67        "print",
68        "println",
69        "pr",
70        "prn",
71        "printf",
72        "newline",
73        "flush",
74        "spit",
75        "slurp",
76        "close",
77        "nanotime",
78        "sleep",
79        "rand",
80        "rand-int",
81        "random-sample",
82        "shuffle",
83        "random-uuid",
84        "gensym",
85        "add-tap",
86        "remove-tap",
87        "tap>",
88        "shared-atom",
89        "promise",
90        "deliver",
91        "send",
92        "send-off",
93        "new",
94        "Exception.",
95        "push-precision!",
96        "pop-precision!",
97        // Reads process-global state the transaction did not receive as an
98        // argument, and which can change under it between calls.
99        "System/getenv",
100        // clojure.java.shell's substrate: a child process reaches everything
101        // this policy exists to forbid.
102        "clojure.rust.process/run",
103    ];
104    if DENIED.contains(&name) {
105        Err(forbidden(name))
106    } else {
107        Ok(())
108    }
109}
110
111/// Check special forms that can reach outside the invocation environment.
112pub fn check_special(name: &str) -> EvalResult<()> {
113    if !transaction_policy_active() {
114        return Ok(());
115    }
116    const DENIED: &[&str] = &[
117        ".",
118        "ns",
119        "require",
120        "in-ns",
121        "alias",
122        "load-file",
123        "with-out-str",
124        "await",
125    ];
126    if DENIED.contains(&name) {
127        Err(forbidden(name))
128    } else {
129        Ok(())
130    }
131}
132
133pub fn check_versioned_lookup() -> EvalResult<()> {
134    if transaction_policy_active() {
135        Err(forbidden("versioned namespace lookup"))
136    } else {
137        Ok(())
138    }
139}