# Sandboxing modules

**URL:** <https://swi-prolog.discourse.group/t/sandboxing-modules/6980>\
**Category:** General\
**Created:** [November 19, 2023, 4:45pm UTC](https://swi-prolog.discourse.group/t/sandboxing-modules/6980 "2023-11-19T16:45:57Z")\
**Posts on this page:** 1\
**Showing post:** 3

<div class="post-metadata">

**Author:** ![ridgeworks](https://yyz2.discourse-cdn.com/free1/user_avatar/swi-prolog.discourse.group/ridgeworks/32/886_2.png) [@ridgeworks](https://swi-prolog.discourse.group/u/ridgeworks)\
**Post date:** [November 21, 2023, 4:35pm UTC](https://swi-prolog.discourse.group/t/sandboxing-modules/6980/3 "2023-11-21T16:35:19Z")

</div>

I have no issues with your comments so:

1. Move any `create_prolog_flag` calls to a directive. (They were in a local predicate called at module `initialization` time.)

2. Accept that `debug(Topic)` is deemed unsafe which will mean some clpBNR features (e.g., tracing and debug message control) will not be available when running on pengines. (Subject to future mods to make debug topics thread-local, which I think is a good idea.)

3. `prolog_current_frame` was only called when the debug topic was enabled. Since that can’t be done on pengines, I can declare the internal predicate that used it as safe.

I’ve corrected my notion of a safe module as one whose “safe” exported predicates pass the `safe_goal` test and whose “unsafe” exported predicates generate an error. (Sounds obvious but it took me a while to get here.) So a simple test on a loaded module to generate the safe and unsafe exported predicates:

```prolog
test_module_safety(M,Safe,Others) :-
	findall(M:H,predicate_property(M:H,exported),Exported),
	safe_goals_(Exported,Safe,Others).

safe_goals_([],[],[]).
safe_goals_([Goal|Goals],[Goal|Safe],Others) :-
	catch(safe_goal(Goal),_Err,fail), !,
	safe_goals_(Goals,Safe,Others).
safe_goals_([Goal|Goals],Safe,[Goal|Others]) :- % failed `safe_goal`
	safe_goals_(Goals,Safe,Others).

```

`test_module_safety` produces two lists of goals for module `M`; `Safe` is a list of safe goals for _all_ values of their arguments and `Others` contains goals which do not pass this test. Therefore, goals whose safety depends on particular instantiations of their arguments will be in the `Others` list as well as any strictly unsafe predicates, so the safety check for calls to these goals must be done dynamically.

So if I run this on a development version of `clpBNR`, I get an `Others` list of `[clpBNR:trace_clpBNR(_), clpBNR:print_interval(_, _)]`. `trace_clpBNR/1` is unsafe because it depends on debug topics and the underlying implementation uses debugger predicates.

`print_interval/2` outputs to a stream (via `format/3`) so that’s potentially unsafe depending on the value of its stream argument: `print_interval(current_output,Term)` is safe but `print_interval(OpenStream,Term)` is not, so that requires a runtime check.

Next topic: Using `safe_meta/2` for meta predicates: `clpBNR` supports a hook for users to define their own custom interval arithmetic primitives. They do this by defining a goal in the `clpBNR` namespace (analogous to the way other hooks are defined, e.g., `exception/3` in `user`, or `safe_global_variable/1` in `sandbox`). So it’ll look something like:

```prolog
clpBNR:my_primitive(`$op`, InputArgs, OutputArgs, Persistent) :-
    % implementation of 'my_primitive/4' ... .

```

When a constraint is “compiled” into the constraint network, the existence of this predicate is checked (via a “safe” application of `predicate_property/2`). When the constraint network is subsequently “evaluated” for consistency the user primitive is invoked, but this requires the use of `call` to invoke the custom primitive. A `safe_meta` declaration is required - what should it be? Perhaps:

```prolog
call_user_primitive(Prim, P, InArgs, OutArgs) :- % wraps unsafe meta call/N
	call(clpBNR:Prim, '$op', InArgs, OutArgs, P).

sandbox:safe_meta(clpBNR:call_user_primitive(Prim, P, InArgs, OutArgs), []).

```

This works fine but doesn’t really check if the user defined primitive is itself safe. I think the safety of hooks like this is generally covered by (from the sandbox source code):

```prolog
%! safe_assert(+Term) is semidet.
%
% True if assert(Term) is safe, which means it asserts in the
% current module. Cross-module asserts are considered unsafe. We
% only allow for adding facts. In theory, we could also allow for
% rules if we prove the safety of the body.

```

So there’s no way to inject unsafe code (as a user defined primitive) into the `clpBNR` namespace from outside (e.g., a SWISH user). So I think this is fine

My second question about meta-predicates is just to validate the correct usage of `safe_meta` in the following (from the `clpBNR_toolkit` module):

```prolog
%
% General purpose iterator: execute Goal a maximum of N times or until Test succeeds
%
iterate_until(N,Test,Goal) :- N>0, !,
	Goal,
	N1 is N-1,
	(Test
	 -> true
	  ; iterate_until(N1,Test,Goal)
	).
iterate_until(_N,_,_). % non-positive N --> exit

sandbox:safe_meta(clpBNR_toolkit:iterate_until(_N,Test,Goal), [Test, Goal]).

```

so any external calls to `iterate_until/3` will only be allowed if `Test` and `Goal` are safe, again not my responsibility. (`test_module_safety` puts `iterate_until/3` in the `Others` list, so calls using safe (runtime) `Test` and `Goal` will be permitted.)

Final topic: I have some examples of finding optima of linear systems that use `library(simplex)`. The `Others` list for this module is `[simplex:maximize(_, _, _), simplex:minimize(_, _, _)]` due to an unnecessary use of meta calls in the internal predicate `pivot_row`

```prolog
pivot_row([], _, Bounded, _, _, Row, Row) :- Bounded. %%% here
pivot_row([Row|Rows], PCol, Bounded0, Min0, Index0, PRow0, PRow) :-
        Row = row(_Var, Left, B),
        nth0(PCol, Left, Ae),
        ( Ae > 0 ->
            Bounded1 = true,
            Bound is B rdiv Ae,
            ( Bounded0 -> %%% and here
                ( Bound < Min0 -> Min1 = Bound, PRow1 = Index0
                ; Min1 = Min0, PRow1 = PRow0
                )
            ; Min1 = Bound, PRow1 = Index0
            )
        ; Bounded1 = Bounded0, Min1 = Min0, PRow1 = PRow0
        ),
        Index1 is Index0 + 1,
        pivot_row(Rows, PCol, Bounded1, Min1, Index1, PRow1, PRow).

```

Since the only usage of this predicate has an initial value of `false`, the meta call can be eliminated with two simple modifications:

```prolog
pivot_row([], _, true, _, _, Row, Row). %%% change to this
pivot_row([Row|Rows], PCol, Bounded0, Min0, Index0, PRow0, PRow) :-
        Row = row(_Var, Left, B),
        nth0(PCol, Left, Ae),
        ( Ae > 0 ->
            Bounded1 = true,
            Bound is B rdiv Ae,
            ( Bounded0 == true -> %%% change to this
                ( Bound < Min0 -> Min1 = Bound, PRow1 = Index0
                ; Min1 = Min0, PRow1 = PRow0
                )
            ; Min1 = Bound, PRow1 = Index0
            )
        ; Bounded1 = Bounded0, Min1 = Min0, PRow1 = PRow0
        ),
        Index1 is Index0 + 1,
        pivot_row(Rows, PCol, Bounded1, Min1, Index1, PRow1, PRow).

```

Now `test_module_safety` returns an empty list for `Others` (i.e.,`library(simplex)` is totally safe and no runtime checking is required).

Think I’m finally ready to test all this on a pengine.

---

_[View the full topic](https://swi-prolog.discourse.group/t/sandboxing-modules/6980)._
