[Top] [Prev] [Next] [Bottom]

17

17 Multiple Interpreters and
Safe-Tcl

This chapter describes how to create more than one Tcl interpreter in your application. A child interpreter can be made safe so it can execute untrusted scripts without compromising your application or your computer. Command aliases, hidden commands, and shared I/O channels enable communication among interpreters. Tcl command: interp.This feature was added in Tcl 7.5.
Safe-Tcl was invented by Nathaniel Borenstein and Marshall Rose so they could send Tcl scripts via email and have the recipient safely execute the script without worry of viruses or other attacks. Safe-Tcl works by removing dangerous commands like exec and open that would let an untrusted script damage the host computer. You can think of this restricted interpreter as a "padded cell" in which it is safe to execute untrusted scripts. To continue the analogy, if the untrusted code wants to do anything potentially unsafe, it must ask permission. This works by adding additional commands, or aliases, that are implemented by a different Tcl interpreter. For example, a safeopen command could be implemented by limiting file space to a temporary directory that is deleted when the untrusted code terminates.

The key concept of Safe-Tcl is that there are two Tcl interpreters in the application, a trusted one and an untrusted (or "safe") one. The trusted interpreter can do anything, and it is used for the main application (e.g., the Web browser or email user interface). When the main application receives a message containing an untrusted script, it evaluates that script in the context of the untrusted interpreter. The restricted nature of the untrusted interpreter means the application is safe from attack. This model is much like user mode and kernel mode in a multi-user operating system like UNIX or Windows/NT. In these systems, applications run in user mode and trap into the kernel to access resources like files and the network. The kernel implements access controls so that users cannot read and write each others files, or hijack network services. In Safe-Tcl the application implements access controls for untrusted scripts.

The dual interpreter model of Safe-Tcl has been generalized in Tcl 7.5 and made accessible to Tcl scripts. A Tcl script can create other interpreters, destroy them, create command aliases among them, share I/O channels among them, and evaluate scripts in them.

The interp Command

The interp command is used to create and manipulate interpreters. The interpreter being created is called a slave, and the interpreter that creates it is called the master. The master has complete control over the slave. The interp command is summarized in Table 17-1.
The interp command.
interp aliases slave List aliases that are defined in slave.
interp alias slave cmd1 Return the target command and arguments for the alias cmd1 in slave.
interp alias slave cmd1 master cmd2 arg ... Define cmd1 in slave that is an alias to cmd2 in master with additional args.
interp create ?-safe? slave Create an interpreter named slave.
interp delete slave Destroy interpreter slave.
interp eval slave cmd args ... Evaluate cmd and args in slave.
interp exists slave Returns 1 if slave is an interpreter, else 0.
interp expose slave cmd Expose hidden command cmd in slave.
interp hide slave cmd Hide cmd from slave.
interp invokehidden slave cmd arg ... Invoke hidden command cmd and args in slave.
interp issafe slave Returns 1 if slave was created with -safe flag.
interp share master file slave Share the I/O descriptor named file in master with slave.
interp slaves master Return the list of slave interpreters of master.
interp target slave cmd Return the name of the interpreter that is the target of alias cmd in slave.
interp transfer master file slave Transfer the I/O descriptor named file from master to slave.

Creating Interpreters

Here is a simple example that creates an interpreter, evaluates a couple of commands in it, and then deletes the interpreter:

Creating and deleting an interpreter.
interp create foo
=> foo
interp eval foo {set a 5}
=> 5
set sum [interp eval foo {expr $a + $a}]
=> 10
interp delete foo
In Example 17-1 the interpreter is named foo. Two commands are evaluated in the foo interpreter:

set a 5
expr $a + $a
Note that curly braces are used to protect the commands from any interpretation by the main interpreter. The variable a is defined in the foo interpreter and does not conflict with variables in the main interpreter. The set of variables and procedures in each interpreter is completely independent.

The Interpreter Hierarchy

A slave interpreter can itself create interpreters, resulting in a hierarchy. The next examples illustrates this, and it shows how the grandparent of an interpreter can reference the grandchild by name. The example uses interp slaves to query the existence of child interpreters.

Creating a hierarchy of interpreters.
interp create foo
=> foo
interp eval foo {interp create bar}
=> bar
interp create {foo bar2}
=> foo bar2
interp slaves
=> foo
interp slaves foo
=> bar bar2
interp delete bar
=> interpreter named "bar" not found
interp delete {foo bar}
The example creates foo, and then it creates two children of foo. The first one is created by foo with this command:

interp eval foo {interp create bar}
The second child is created by the main interpreter. In this case the grandchild must be named by a two-element list to indicate it is a child of a child. The same naming convention is used when the grandchild is deleted:

interp create {foo bar2}
interp delete {foo bar2}
The interp slaves operation returns the names of child (i.e., slave) interpreters. The names are relative to their parent, so the slaves of foo are reported simply as bar and bar2. The name for the current interpreter is the empty list, or {}. This is useful in command aliases and file sharing described later. For security reasons, it is not possible to name the master interpreter from within the slave.

The Interpreter Name as a Command

After interpreter slave is created, a new command is available in the main interpreter, also called slave, that operates on the child interpreter. The following two forms are equivalent most operations:

slave operation args ...
interp operation slave args ...
For example, the following are equivalent commands:

foo eval {set a 5}
interp eval foo {set a 5}
And so are these:

foo issafe
interp issafe foo
However, the operations delete, exists, share, slaves, target, and transfer cannot be used with the per interpreter command. In particular, there is no foo delete operation; you must use interp delete foo.

If you have a deep hierarchy of interpreters, the command corresponding to the slave is only defined in the parent. For example, if a master creates foo, and foo creates bar, then the master must operate on bar with the interp command. There is no "foo bar" command defined in the master.

Use list with interp eval

The interp eval command treats its arguments like eval. If there are extra arguments they are all concatenated together first. This can lose important structure as described in Chapter 10. To be safe, use list to construct your commands. For example, to safely define a variable in the slave, you should do this:

interp eval slave [list set var $value]

Safe Interpreters

A child can be created either safe (i.e., untrusted) or fully functional. In the examples so far, the children have been trusted and fully functional; they have all the basic Tcl commands available to them. An interpreter is made safe by eliminating certain commands. Table 17-2 lists the commands removed from safe interpreters. As described later, these commands can be used by the master on behalf of the safe interpreter. To create a safe interpreter, use the -safe flag:

interp create -safe untrusted 
Commands removed from safe interpreters.
cd Change directory.
exec Execute another program.
exit Terminate the process.
fconfigure Set modes of an I/O stream.
file Query file attributes.
glob Pattern match on file names.
load Dynamically load object code.
open Open files and process pipelines.
pwd Determine the current directory.
socket Open network sockets.
source Load scripts.
A safe interpreter does not have commands to manipulate the file system and other programs (e.g., cd, open, and exec). This ensures that untrusted scripts cannot harm the host computer. The socket command is removed so untrusted scripts cannot access the network. The exit, source, and load commands are removed so an untrusted script cannot harm the hosting application. Note that commands like puts and gets are not removed. A safe interpreter can still do I/O, but it cannot create an I/O channel. We will show how to pass an I/O channel to a child interpreter on page 211.

The initial state of a safe interpreter is very safe, but it is too limited. The only thing a safe interpreter can do is compute a string and return that value to the parent. By creating command aliases, a master can give a safe interpreter controlled access to resources. A security policy implements a set of command aliases that add controlled capabilities to a safe interpreter. We will show, for example, how to provide limited network and file system access to untrusted slaves. Tcl provides a framework to manage several security policies, which is described later.

Command Aliases

A command alias is a command in one interpreter that is implemented by a command in another interpreter. The master interpreter installs command aliases in its slaves. The command to create an alias has the following general form:

interp alias slave cmd1 target cmd2 ?arg arg ...?
This creates cmd1 in slave that is an alias for cmd2 in target. When cmd1 is invoked in slave, cmd2 is invoked in target. The alias mechanism is transparent to the slave. Whatever cmd2 returns, the slave sees as the return value of cmd1. If cmd2 raises an error, the error is propagated to the slave.

Name the current interpreter with {}.

If target is the current interpreter, name it with {}. The empty list is the way to name yourself as the interpreter. This is the most common case, although target could be a different slave. The slave and target can even be the same interpreter.

The arguments to cmd1 are passed to cmd2, after any additional arguments to cmd2 that were specified when the alias was created. These hidden arguments provide a safe way to pass extra arguments to an alias. For example, it is quite common to pass the name of the slave to the alias. In Example 17-3, exit in the interpreter foo is an alias that is implemented in the current interpreter (i.e., {}). When the slave executes exit, the master executes:

interp delete foo
A command alias for exit.
interp create foo
interp alias foo exit {} interp delete foo
interp eval foo exit
# Child foo is gone.

Alias Introspection

You can query what aliases are defined for a child interpreter. The interp aliases command lists the aliases; the interp alias command can also return the value of an alias, and the interp target command tells you what interpreter implements an alias. These are illustrated in the following examples:

Querying aliases.
proc Interp_ListAliases {name out} {
	puts $out "Aliases for $name"
	foreach alias [interp aliases $name] {
		puts $out [format "%-20s => (%s) %s" $alias \
				[interp target $name $alias] \
				[interp alias $name $alias]]
	}
}
Example 17-4 generates output in a human readable format. Example 17-5 generates the aliases as Tcl commands that can be used to re-create them later:

Dumping aliases as Tcl commands.
proc Interp_DumpAliases {name out} {
	puts $out "# Aliases for $name"
	foreach alias [interp aliases $name] {
		puts $out [format "interp alias %s %s %s %s" \
			$name $alias [list [interp target $name $alias]] \
			[interp alias $name $alias]]
	}
}

Hidden Commands

The commands listed in Table 17-2 are hidden instead of being completely removed. A hidden command can be invoked in a slave by its master. For example, a master can load Tcl scripts into a slave by using its hidden source command:

interp create -safe slave
interp invokehidden slave source filename
Without hidden commands the master has to do a bit more work to achieve the same thing. It must open and read the file and eval the contents of the file in the slave. File operations are described in Chapter 9.

interp create -safe slave
set in [open filename]
interp eval slave [read $in]
close $in
Hidden commands were added in Tcl 7.7 in order to better support the Tcl/Tk browser plug-in described in Chapter 48. In some cases hidden commands are strictly necessary; it is not possible to simulate them any other way. The best examples are in the context of Safe-Tk, where the master creates widgets or does potentially dangerous things on behalf of the slave. These will be discussed in more detail later.

A master can hide and expose commands using the interp hide and interp expose operations, respectively. You can even hide Tcl procedures. However, the commands inside the procedure run with the same privilege as the slave. For example, if you are really paranoid you might not want an untrusted interpreter to read the clock or get timing information. You can hide the clock and time commands:

interp create -safe slave
interp hide slave clock
interp hide slave time
You can remove commands from the slave entirely like this:

interp eval slave [list rename clock {}]
interp eval slave [list rename time {}]

Substitutions

You must be aware of Tcl parsing and substitutions when commands are invoked in other interpreters. There are three cases corresponding to interp eval, interp invokehidden, and command aliases.

With interp eval the command is subject to a complete round of parsing and substitutions in the target interpreter. This occurs after the parsing and substitutions for the interp eval command itself. In addition, if you pass several arguments to interp eval, those are concatenated before evaluation. This is similar to the way the eval command works as described on page 113. The most reliable way to use interp eval is to construct a list to ensure the command is well structured:

interp eval slave [list cmd arg1 arg2]
With hidden commands, the command and arguments are taken directly from the arguments to interp invokehidden, and there are no substitutions done in the target interpreter. This means that the master has complete control over the command structure, and nothing funny can happen in the other interpreter. For this reason you should not create a list. If you do that, the whole list will be interpreted as the command name! Instead, just pass separate arguments to interp invokehidden and they are passed straight through to the target:

interp invokehidden slave command arg1 arg2
Never eval alias arguments.

With aliases, all the parsing and substitutions occur in the slave before the alias is invoked in the master. The alias implementation should never eval or subst any values it gets from the slave to avoid executing arbitrary code.

For example, suppose there is an alias to open files. The alias does some checking and then invokes the hidden open command. An untrusted script might pass [exit] as the name of the file to open in order to create mischief. The untrusted code is hoping that the master will accidentally eval the filename and cause the application to exit. This attack has nothing to do with opening files; it just hopes for a poor alias implementation. Example 17-6 shows an alias that is not subject to this attack:

Substitutions and hidden commands.
interp alias slave open {} safeopen slave
proc safeopen {slave filename {mode r}} {

	# do some checks, then...
	interp invokehidden $slave open $filename $mode
}
interp eval slave {open \[exit\]}
The command in the slave starts out as:

open \[exit\]
The master has to quote the brackets in its interp eval command or else the slave will try to invoke exit because of command substitution. Presumably exit isn't defined, or it is defined to terminate the slave. Once this quoting is done, the value of filename is [exit] and it is not subject to substitutions. It is safe to use $filename in the interp invokehidden command because it is only substituted once, in the master. The hidden open command also gets [exit] as its filename argument, which is never evaluated as a Tcl command.

I/O from Safe Interpreters

A safe child interpreter cannot open files or network sockets directly. An alias can create an I/O channel (i.e., open a file or socket) and give the child access to it. The parent can share the I/O channel with the child, or it can transfer the I/O channel to the child. If the channel is shared, both the parent and the child can use it. If the channel is transferred, the parent no longer has access to the channel. In general, transferring an I/O channel is simpler, but sharing an I/O channel gives the parent more control over an unsafe child. The differences are illustrated in Example 17-7 and Example 17-9.

There are three properties of I/O channels that are important to consider when choosing between sharing and transferring: the name, the seek offset, and the reference count.

interp share interp1 chanName interp2
interp transfer interp1 chanName interp2
In these commands, chanName exists in interp1 and is being shared or transferred to interp2. As with command aliases, if interp1 is the current interpreter, name it with {}.

The following example creates a temporary file for an unsafe interpreter. The file is opened for reading and writing, and the slave can use it to store data temporarily.

Opening a file for an unsafe interpreter.
proc TempfileAlias {slave} {
	set i 0
	while {[file exists Temp$slave$i]} {
		incr i
	}
	set out [open Temp$slave$i w+]
	interp transfer {} $out $slave
	return $out
}
proc TempfileExitAlias {slave} {
	foreach file [glob -nocomplain Temp$slave*] {
		file delete -force $file
	}
	interp delete $slave
}
interp create -safe foo
interp alias foo Tempfile {} TempfileAlias foo
interp alias foo exit {} TempfileExitAlias foo
The TempfileAlias procedure is invoked in the parent when the child interpreter invokes Tempfile. TempfileAlias returns the name of the open channel, and this becomes the return value from Tempfile so the child knows the name of the I/O channel. TempfileAlias uses interp transfer to pass the I/O channel to the child so the child has permission to access the I/O channel. In this example, it would also work to invoke the hidden open command to create the I/O channel directly in the slave.

Example 17-7 is not fully safe because the unsafe interpreter can still overflow the disk or create a million files. Because the parent has transferred the I/O channel to the child, it cannot easily monitor the I/O activity by the child. Example 17-9 addresses these issues.

The Safe Base

An safe interpreter created with interp create -safe has no script library environment and no way to source scripts. Tcl provides a safe base that extends a raw safe interpreter with the ability to source scripts and packages as described in Chapter 12. The safe base also defines an exit alias that terminates the slave like the one in Example 17-7. The safe base is implemented as Tcl scripts that are part of the standard Tcl script library. Create an interpreter that uses the safe base with safe::interpCreate:

safe::interpCreate foo
The safe base has source and load aliases that only access directories on an access path defined by the master interpreter. The master has complete control over what files can be loaded into a slave. In general it would be OK to source any Tcl program into an untrusted interpreter. However, untrusted scripts might learn things by sourcing arbitrary files. The safe base also has versions of the package and unknown commands that support the library facility.

Table 17-3 lists the Tcl procedures in the safe base:
The safe base master interface.
safe::interpCreate ?slave? ?options? Create a safe interpreter and initialize the security policy mechanism.
safe::interpInit slave ?options? Initialize a safe interpreter so it can use security policies.
safe::interpConfigure slave ?options? Options are -accessPath pathlist, -nostatics, -deleteHook script, -nestedLoadOk.
safe::interpDelete slave Delete a safe interpreter.
safe::interpAddToAccessPath slave directory Add a directory to the slave's access path.
safe::interpFindInAccessPath Map from a directory to the token visible in the slave for that directory.
safe::setLogCmd ?cmd arg ... ? Set or query the logging command used by the safe base.

Table 17-4 lists the aliases defined in a safe interpreter by the safe base.
The safe base slave aliases.
source Load scripts from directories in the access path
load Load binary extensions from the slaves access path
file Only the dirname, join, extension, root, tail, pathname, and split operations are allowed.
exit Destroy the slave interpreter.

Security Policies

A security policy defines what a safe interpreter can do. Designing security policies that are secure is difficult. If you design your own, make sure to have your colleagues review the code. Give out prizes to folks who can break your policy. Good policy implementations are proven with lots of review and trial attacks.

The good news is that Safe-Tcl security policies can be implemented in relatively small amounts of Tcl code. This makes them easier to analyze and get correct. Here are a number of rules of thumb:

Limited Socket Access

The Safesock security policy provides limited socket access. The policy is designed around a simple table of allowed hosts and ports. An untrusted interpreter can only connect to addresses listed in the table. For example, I would never let untrusted code connect to the sendmail, ftp, or telnet ports on my hosts. There are just too many attacks possible on these ports. On the other hand, I might want to let untrusted code fetch a URL from certain hosts, or connect to a database server for an intranet application. The goal of this policy is to have a simple way to specify exactly what hosts and ports a slave can access. Example 17-8 shows a simplified version of the Safesock security policy that is distributed with Tcl 8.0.

The Safesock security policy.
# The index is a host name, and the
# value is a list of port specifications, which can be
# an exact port number
# a lower bound on port number: N-
# a range of port numbers, inclusive: N-M
array set safesock {
	sage.eng				3000-4000
	www.sun.com				80
	webcache.eng				{80 8080}
	bisque.eng				{80 1025-}
}
proc Safesock_PolicyInit {slave} {
	interp alias $slave socket {} SafesockAlias $slave
}
proc SafesockAlias {slave host port} {
	global safesock
	if ![info exists safesock($host)] {
		error "unknown host: $host"
	}

	foreach portspec $safesock($host) {
		set low [set high ""]
		if {[regexp {^([0-9]+)-([0-9]*)$} $portspec x low high]} {
			if {($low <= $port && $high == "") ||
					($low <= $port && $high >= $port)} {
				set good $port
				break
			}
		} elseif {$port == $portspec} {
			set good $port
		}
	}

	if [info exists good] {
		set sock [interp invokehidden $slave socket $host $good]
		interp invokehidden $slave fconfigure $sock \
			-blocking 0
		return $sock
	}
	error "bad port: $port"
}
The policy is initialized with Safesock_PolicyInit. The name of this procedure follows a naming convention used by the safe base. In this case, a single alias is installed. The alias gives the slave a socket command that is implemented by SafesockAlias in the master.

The alias checks for a port that matches one of the port specifications for the host. If a match is found, then the invokehidden operation is used to invoke two commands in the slave. The socket command creates the network connection, and the fconfigure command puts the socket into non-blocking mode so read and gets by the slave do not block the application:

set sock [interp invokehidden $slave socket $host $good]
interp invokehidden $slave fconfigure $sock -blocking 0
The socket alias in the slave does not conflict with the hidden socket command. There are two distinct sets of commands, hidden and exposed. It is quite common for the alias implementation to invoke the hidden command after various permission checks are made.

The Tcl Web browser plug-in ships with a slightly improved version of the Safesock policy. It adds an alias for fconfigure so the http package can set end of line translations and buffering modes. The fconfigure alias does not let you change the blocking behavior of the socket. The policy has also been extended to classify hosts into trusted and untrusted hosts based on their address. A different table of allowed ports is used for the two classes of hosts. The classification is done with two tables: one table lists patterns that match trusted hosts and the other table lists hosts that should not be trusted even though they match the first table. The improved also version lets a downloaded script connect to the Web server that it came from. The Web browser plug-in is described in Chapter 48.

Limited Temporary Files

Example 17-9 improves on Example 17-7 by limiting the number of temporary files and the size of the files. It is written to work with the safe base so it has a Tempfile_PolicyInit that takes the name of the slave as an argument. TempfileOpenAlias lets the child specify a file by name, yet it limits the files to a single directory.

The example demonstrates a shared I/O channel that gives the master control over output. TempfilePutsAlias restricts the amount of data that can be written to a file. By sharing the I/O channel for the temporary file the slave can use commands like gets, eof, and close, while the master does the puts. The need for shared I/O channels is somewhat reduced by hidden commands, which were added to Safe-Tcl more recently than shared I/O channels. For example, the puts alias can either write to a shared channel after checking the file size, or it could invoke the hidden puts in the slave. This alternative is shown in Example 17-10.

The Tempfile security policy.
# Policy parameters:
# 	directory is the location for the files
# 	maxfile is the number of files allowed in the directory
# 	maxsize is the max size for any single file.

array set tempfile {
	maxfile				4
	maxsize				65536
}
# tempfile(directory) is computed dynamically based on
# the source of the script

proc Tempfile_PolicyInit {slave} {
	global tempfile				
	interp alias $slave open {} \
		TempfileOpenAlias $slave $tempfile(directory) \
			$tempfile(maxfile)
	interp alias $slave puts {} TempfilePutsAlias $slave \
		$tempfile(maxsize)
	interp alias $slave exit {} TempfileExitAlias $slave
}
proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} {
	global tempfile
	# remove sneaky characters
	regsub -all {|/:} [file tail $name] {} real
	set real [file join $dir $real]
	# Limit the number of files
	set files [glob -nocomplain [file join $dir *]]
	set N [llength $files]
	if {($N >= $maxfile) && (\
			[lsearch -exact $files $real] < 0)} {
		error "permission denied"
	}
	if [catch {open $real $m $p} out] {
		return -code error "$name: permission denied"
	}
	lappend tempfile(channels,$slave) $out
	interp share {} $out $slave
	return $out
}
proc TempfileExitAlias {slave} {
	global tempfile
	interp delete $slave
	if [info exists tempfile(channels,$slave)] {
		foreach out $tempfile(channels,$slave) {
			catch {close $out}
		}
		unset tempfile(channels,$slave)
	}
}
# See also the puts alias in Example 19-4 on page 245
proc TempfilePutsAlias {slave max chan args} {
	# max is the file size limit, in bytes
	# chan is the I/O channel
	# args is either a single string argument,
	# or the -nonewline flag plus the string.

	if {[llength $args] > 2} {
		error "invalid arguments"
	}
	if {[llength $args] == 2} {
		if {![string match -n* [lindex $argv 0]]} {
			error "invalid arguments"
		}
		set string [lindex $args 1]
	} else {
		set string [lindex $args 0]\n
	}
	set size [expr [tell $chan] + [string length $string]]
	if {$size > $max} {
		error "File size exceeded"
	} else {
		puts -nonewline $chan $string
	}
}
The TempfileAlias procedure is generalized in Example 17-9 to have parameters that specify the directory, name, and a limit to the number of files allowed. The directory and maxfile limit are part of the alias definition. Their existence is transparent to the slave. The slave only specifies the name and access mode (i.e., for reading or writing.) The Tempfile policy could be used by different slave interpreters with different parameters.

The master is careful to restrict the files to the specified directory. It uses file tail to strip off any leading pathname components that the slave might specify. The tempfile(directory) definition is not shown in the example. The application must choose a directory when it creates the safe interpreter. The Browser security policy described on page 611 chooses a directory based on the name of the URL containing the untrusted script.

The TempfilePutsAlias procedure implements a limited form of puts. It checks the size of the file with tell and measures the output string to see if the total exceeds the limit. The limit comes from a parameter defined when the alias is created. The file cannot grow past the limit, at least not by any action of the child interpreter. The args parameter is used to allow an optional -nonewline flag to puts. The value of args is checked explicitly instead of using the eval trick described in Example 10-2 on page 116. Never eval arguments to aliases or else a slave can attack you with arguments that contain embedded Tcl commands.

The master and slave share the I/O channel. The name of the I/O channel is recorded in tempfile, and TempfileExitAlias uses this information to close the channel when the child interpreter is deleted. This is necessary because both parent and child have a reference to the channel when it is shared. The child's reference is automatically removed when the interpreter is deleted, but the parent must close its own reference.

The shared I/O channel lets the master use puts and tell. It is also possible to implement this policy by using hidden puts and tell commands. The reason tell must be hidden is to prevent the slave from implementing its own version of tell that lies about the seek offset value. One advantage of using hidden commands is there is no need to clean up the tempfile state about open channels. You can also layer the puts alias on top of any existing puts implementation. For example, a script may define puts to be a procedure that inserts data into a text widget. Example x shows the difference when using hidden commands.

Restricted puts using hidden commands.
proc Tempfile_PolicyInit {slave} {
	global tempfile				
	interp alias $slave open {} \
		TempfileOpenAlias $slave $tempfile(directory) \
			$tempfile(maxfile)
	interp hide $slave tell
	interp alias $slave tell {} TempfileTellAlias $slave
	interp hide $slave puts
	interp alias $slave puts {} TempfilePutsAlias $slave \
		$tempfile(maxsize)
	# no special exit alias required
}
proc TempfileOpenAlias {slave dir maxfile name {m r} {p 0777}} {
	# remove sneaky characters
	regsub -all {|/:} [file tail $name] {} real
	set real [file join $dir $real]
	# Limit the number of files
	set files [glob -nocomplain [file join $dir *]]
	set N [llength $files]
	if {($N >= $maxfile) && (\
			[lsearch -exact $files $real] < 0)} {
		error "permission denied"
	}
	if [catch {interp invokehidden $slave \
			open $real $m $p} out] {
		return -code error "$name: permission denied"
	}
	return $out
}
proc TempfileTellAlias {slave chan} {
	interp invokehidden $slave tell $chan
}
proc TempfilePutsAlias {slave max chan args} {
	if {[llength $args] > 2} {
		error "invalid arguments"
	}
	if {[llength $args] == 2} {
		if {![string match -n* [lindex $args 0]]} {
			error "invalid arguments"
		}
		set string [lindex $args 1]
	} else {
		set string [lindex $args 0]\n
	}
	set size [interp invokehidden $slave tell $chan]
	incr size [string length $string]
	if {$size > $max} {
		error "File size exceeded"
	} else {
		interp invokehidden $slave \
			puts -nonewline $chan $string
	}
}

Safe after Command

The after command is unsafe because it can block the application for an arbitrary amount of time. This happens if you only specify a time but do not specify a command. In this case Tcl just waits for the time period and processes no events. This will stop all interpreters, not just the one doing the after command. This is a kind of resource attack. It doesn't leak information or damage anything, but it disrupts the main application.

Example 17-11 defines an alias that implements after on behalf of safe interpreters. The basic idea is to carefully check the arguments, and then do the after in the parent interpreter. As an additional feature, the number of outstanding after events is limited. The master keeps a record of each after event scheduled. Two IDs are associated with each event: one chosen by the master (i.e., myid), and the other chosen by the after command (i.e., id). The master keeps a map from myid to id. The map serves two purposes. The number of map entries counts the number of outstanding events. The map also hides the real after ID from the slave, which prevents a slave from attempting mischief by specifying invalid after IDs to after cancel. The SafeAfterCallback is the procedure scheduled. It maintains state and then invokes the original callback in the slave.

A safe after command.
# SafeAfter_PolicyInit creates a child with 
# a safe after command

proc SafeAfter_PolicyInit {slave max} {
	# max limits the number of outstanding after events
	global after
	interp alias $slave after {} SafeAfterAlias $slave $max
	interp alias $slave exit {} SafeAfterExitAlias $slave
	# This is used to generate after IDs for the slave.
	set after(id,$slave) 0
}

# SafeAfterAlias is an alias for after. It disallows after
# with only a time argument and no command.

proc SafeAfterAlias {slave max args} {
	global after
	set argc [llength $args]
	if {$argc == 0} {
		error "Usage: after option args"
	}
	switch -- [lindex $args 0] {
		cancel {
			# A naive implementation would just
			# eval after cancel $args
			# but something dangerous could be hiding in args.
			set myid [lindex $args 1]
			if {[info exists after(id,$slave,$myid)]} {
				set id $after(id,$slave,$myid)
				unset after(id,$slave,$myid)
				after cancel $id
			}
			return ""
		}
		default {
			if {$argc == 1} {
				error "Usage: after time command args..."
			}
			if {[llength [array names after id,$slave,*]]\
					>= $max} {
				error "Too many after events"
			}
			# Maintain concat semantics
			set command [concat [lrange $args 1 end]]
			# Compute our own id to pass the callback.
			set myid after#[incr after(id,$slave)]
			set id [after [lindex $args 0] \
				[list SafeAfterCallback $slave $myid $command]]
			set after(id,$slave,$myid) $id
			return $myid
		}
	}
}

# SafeAfterCallback is the after callback in the master.
# It evaluates its command in the safe interpreter.

proc SafeAfterCallback {slave myid cmd} {
	global after
	unset after(id,$slave,$myid)
	if [catch {
		interp eval $slave $cmd
	} err] {
		catch {interp eval $slave bgerror $error}
	}
}

# SafeAfterExitAlias is an alias for exit that does cleanup.

proc SafeAfterExitAlias {slave} {
	global after
	foreach id [array names after id,$slave,*] {
		after cancel $after($id)
		unset after($id)
	}
	interp delete $slave
}


[Top] [Prev] [Next] [Bottom]

welch@acm.org
Copyright © 1997, Brent Welch. All rights reserved.
This will be published by Prentice Hall as the 2nd Edition of
Practical Programming in Tcl and Tk