Thanks for the help!
I formalized the guarding:
Code: Select all
(defvar *guards* nil "List of guards.")
(defun guard-name (name)
(declare (type symbol name))
(intern (concatenate 'string "GUARD-" (symbol-name name))))
(defmacro def-guard (name (&rest args) &body body)
"Defines a guard onto a function. Essentially you redefine the \
function/macro see if anything malicious can be done with it, and\
disallowing it if so. (It must also be clear what sort of things are\
allowed.)"
(with-gensyms (form)
`(flet ((alarm (datum &rest arguments)
"Function to call if the guards finds something it\
distrusts.)"
(apply #'error (cons datum arguments))
(values))
(guard-assert (test-form datum &rest arguments)
(when test-form
(apply #'alarm (cons datum arguments)))))
(pushnew ',name *guards*)
(defun ,(guard-name name) (&rest ,form)
(destructuring-bind (,@args) ,form ,@body)))))
(defmacro guarded (() &body body)
"Checks if the symbols are allowed, and executes body with guards"
(dolist (expr body)
(multiple-value-bind (trust-p distrust) (trust-expr-p expr)
(unless (eql trust-p :yes)
(error "Don't trust the body. Distrust ~s" distrust))))
`(macrolet
(,@(mapcan
(lambda (name)
(with-gensyms (args)
(when (macro-function name)
`((,name (&rest ,args)
(apply (function ,(guard-name name)) ,args))))))
*guards*))
(flet (,@(mapcan
(lambda (name)
(unless (macro-function name)
`((,name (&rest args)
(apply (function ,(guard-name name)) args)))))
*guards*))
,@body)))
Then i started with guards for Intern, Funcall, and Apply.
Code: Select all
(def-guard intern (name &optional (package *package*))
"Guarding symbols, important because functions call be funcalled thusly"
(if (trust-p package)
(intern name package)
(let ((result (intern name package)))
(if (trust-p result)
result
(alarm "Disallowed symbol ~s" result)))))
(def-guard apply (fun args)
(typecase fun
(symbol
(assert (trust-p fun) nil "BUG: symbol found here should already have\
been noticed as not being trusted!")
(if (find fun *guards*) ;But this is really what it is about:
(apply (guard-name fun) args) fun))
(function
(apply fun args))))
(def-guard funcall (fun &rest args)
(guard-apply fun args))
As you should be able to test, without the guards on funcall and apply you can hack it:(On sbcl, disable the lock with sb:ext-unlock-package, weirdly enough sb-ext:disable-package-locks doesn't seem to work.)
Code: Select all
(trust-these :yes funcall apply intern)
(guarded ()
(funcall (intern "INTERN") "HAX")) ;Where Hax is not allowed as an symbol.
Apparently Funcall/Apply don't pick the local function when fed a symbol.
Haven't gotten to filesystem stuff yet, for that i'd like to allow/disallow directories/files(I mean, you can allow a directory and then disallow some subdirectory/files in there) and that needs some sort of decent representation. I have no idea how many guards will be needed for a decent subset of CL, just what is mentioned now, and probably more in obscure functions, although Funcall and Apply already surprised me.. Anything making variables will probably also require a guard if i am going to allow stuff like *default-pathname-defaults*. (Probably will need a 'var-guard')
Note that the code in OP is a little older, than current but i checked that it works together if you just add Alexandria to the :use'd packages. I pushed the stuff in the
lisp-editor git it is in 'tools/' might be better to have this in a separate project, though. (should i make one?)
We could play 'guard and sneak past the guard' though

. Making guards is easy enough, certainty of being secure doesn't seem very easy.. Dare you to sneak past Funcall, Apply and Intern guards! Edit: you may presume other symbols allowed, of course! (needless to say, or make a guard to sneak by)