safely source a file

Arjen Markus I do not usually worry about safety too much, but sometimes, it is necessary or useful to think about ways in which to prevent stupid mistakes to do harm.

Configuration files are one way of introducing safety and security problems into your applications. So, I decided to do a small experiment. Here is the result.


The script below sources a configuration file "myconf.tcl" that may contain any valid Tcl code, e.g. "file delete -force *" (I did not experiment with that one though :), like:

 # Unsafe configuration file
 set ::myvar [exit]

 # Should have been:
 #set ::myvar 2 

The script sources this file via a safe interpreter and then updates any or all global variables that were set in the calling interpreter. This temporary interpreter being safe, it does not have the possibility to set important variables like tcl_precision and so on, but I remove this type of variables anyway.


 # Attempt to make configuration files safe to source
 #

 # safesource --
 #    Source a configuration file in a safe environment and copy the
 #    results back
 # Arguments:
 #    srcname    Name of the external file to be sourced
 #    vars       (Optional) list of variables to be considered
 # Result:
 #    0 if everything okay, possible error if unsafe commands used
 # Side effect:
 #    Variables in the caller's namespace set
 # Note:
 #    No provision yet for arrays!
 #
 proc safesource {srcname {vars *}} {
    #
    # Create a safe interpreter and source the file
    #
    set cnf _ConfSrc_
    interp create -safe $cnf
    interp invokehidden $cnf source $srcname

    #
    # Copy back those variables we are interested in
    # (never do tcl_platform, argv0, argv or env)
    #

    set srcvars {}
    foreach v $vars {
       set srcvars [interp eval $cnf info vars $v]
    }

    foreach v {tcl_platform tcl_version tcl_patchLevel tcl_interactive argv0 argv env} {
       set idx [lsearch $srcvars $v]
       set srcvars [lreplace $srcvars $idx $idx]
    }

    #
    # NOTE: Assume scalars for the moment!
    #
    foreach v $srcvars {
       set ::$v [interp eval $cnf set $v]
    }

    interp delete $cnf

    return 0
 }

 set myvar 1

 safesource "myconf.tcl"

 puts "Myvar: $myvar"

RS: Instead of explicitly enumerating "taboo" variables, you might get that list as follows:

 set taboo [$cnf eval info vars] ;# before sourcing
 ...
 foreach v $taboo {...
Experiments show only these variables - argc, argv, argv0, env seem to be considered unsafe:
 tcl_interactive tcl_version tcl_patchLevel tcl_platform

AM Good idea. I noticed that tcl_precision can not be changed in a safe interpreter. This does not show up?

TV Why not list all variables like above, and do a script to change every one of them and see what happens, what is scripting for ?


WJP-2006-07-25 I recently wanted to do something a little bit different, namely allow the user to make use of plugins written in Tcl that take a string as argument and return some transformation of it. In order to protect against rogue plugins, I arranged to run them within a safe interpreter.

 #Create a safe interpreter
 interp create -safe -- safeinterp
                                                                                  
 #Suppose that path contains the path to the plugin and
 #name the the name of the procedure of interest, supplied
 #somehow by the user.
                                                                                  
 #Source the file into the safe interpreter
 #If successful, see if this resulted in a procedure named
 #name being defined.
 if { [catch {interp invokehidden safeinterp source $path}] != 0} {
    #Handle error, e.g.:
    puts stderr "An error occurred while reading $name from $path."
 } elseif {[llength [interp eval safeinterp info commands $name]] == 0} {
    #Handle error, e.g.:
    puts stderr "The file $path does not contain a procedure named $name."
 } else {
    set PluginPath $path
    set PluginName $name
 }
 
 #Now, whenever we want to use the plugin, invoke it within the
 #safe interpreter like this:
 set Result [interp eval safeinterp $PluginName [list $l]]

schlenk For plugins there is code inside tcllib pluginmgr module to load code in safe interpreters.


MHo For a safe way to parse config files, take a look at Matthias Hoffmann - Tcl-Code-Snippets - Misc - Readprof!


AMG: Also try: Config file using slave interp.


See also Safe Interps for a discussion about safe interpreters in general.