Chapter 19. 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 that 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 described is: interp.

Safe-Tcl was invented by Nathaniel Borenstein and Marshall Rose so that 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 that the application is safe from attack. This model is much like user mode and kernel mode in a multiuser 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 other's 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 19-1.

Table 19-1. The interp command

interp aliases slave

Lists aliases that are defined in slave.

interp alias slave cmd1

Returns target command and arguments for the alias cmd1 in slave.

interp alias slave cmd1 master cmd2 arg ...

Defines cmd1 in slave that is an alias to cmd2 in master with additional args.

interp create ?-safe? slave

Creates an interpreter named slave.

interp delete slave

Destroys interpreter slave.

interp eval slave cmd args ...

Evaluates cmd and args in slave.

interp exists slave

Returns 1 if slave is an interpreter, else 0.

interp expose slave cmd

Exposes hidden command cmd in slave.

interp hide slave cmd

Hides cmd from slave.

interp hidden slave

Returns the commands hidden from slave.

interp invokehidden slave cmd arg ...

Invokes hidden command cmd and args in slave.

interp issafe slave

Returns 1 if slave was created with -safe flag.

interp marktrusted slave

Clears the issafe property of slave.

interp recursionlimit slave ?limit?

Set or get the interpreter recursion limit for slave. (Tcl 8.4)

interp share master file slave

Shares the I/O descriptor named file in master with slave.

interp slaves master

Returns the list of slave interpreters of master.

interp target slave cmd

Returns the name of the interpreter that is the target of alias cmd in slave.

interp transfer master file slave

Transfers 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:

Example 19-1. 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 19-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.

Example 19-2. 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 that 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 defined only 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 19-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

Table 19-2. Commands hidden from safe interpreters

cd

Changes directory.

exec

Executes another program.

exit

Terminates the process.

fconfigure

Sets modes of an I/O stream.

file

Queries file attributes.

glob

Matches on file name patterns.

load

Dynamically loads object code.

open

Opens files and process pipelines.

pwd

Determines the current directory.

socket

Opens network sockets.

source

Loads 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 that untrusted scripts cannot access the network. The exit, source, and load commands are removed so that 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 299.

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 in Chapter 20.

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.

Note

Command Aliases

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 can 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 19-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

Example 19-3. 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:

Example 19-4. 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 19-4 generates output in a human readable format. Example 19-5 generates the aliases as Tcl commands that can be used to re-create them later:

Example 19-5. 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 19-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 20. 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 that of 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 in Chapter 19. 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

Note

Substitutions

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 19-6 shows an alias that is not subject to this attack:

Example 19-6. 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 19-7 and Example 19-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.

  • The name of the I/O channel (e.g., file4) is the same in all interpreters. If a parent transfers a channel to a child, it can close the channel by evaluating a close command in the child. Although names are shared, an interpreter cannot attempt I/O on a channel to which it has not been given access.

  • The seek offset of the I/O channel is shared by all interpreters that share the I/O channel. An I/O operation on the channel updates the seek offset for all interpreters that share the channel. This means that if two interpreters share an I/O channel, their output will be cleanly interleaved in the channel. If they both read from the I/O channel, they will get different data. Seek offsets are explained in more detail on page 121.

  • A channel has a reference count of all interpreters that share the I/O channel. The channel remains open until all references are closed. When a parent transfers an I/O channel, the reference count stays the same. When a parent shares an I/O channel, the reference count increments by one. When an interpreter closes a channel with close, the reference count is decremented by one. When an interpreter is deleted, all of its references to I/O channels are removed.

The syntax of commands to share or transfer an I/O channel is:

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.

Example 19-7. 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, which becomes the return value from Tempfile. TempfileAlias uses interp transfer to pass the I/O channel to the child so that 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 19-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 19-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 which are described in Chapter 12. The safe base also defines an exit alias that terminates the slave like the one in Example 19-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 all right to source any Tcl program into an untrusted interpreter. However, untrusted scripts might learn things from the error messages they get by sourcing arbitrary files. The safe base also has versions of the package and unknown commands that support the library facility. Table 19-3 lists the Tcl procedures in the safe base:

Table 19-3. The safe base master interface

safe::interpCreate ?slave? ?options?

Creates a safe interpreter and initialize the security policy mechanism.

safe::interpInit slave ?options?

Initializes a safe interpreter so it can use security policies.

safe::interpConfigure slave ?options?

Options are -accessPath pathlist, -nostatics, -deleteHook script, -nestedLoadOk.

safe::interpDelete slave

Deletes a safe interpreter.

safe::interpAddToAccessPath slave directory

Adds a directory to the slave's access path.

safe::interpFindInAccessPath

Maps from a directory to the token visible in the slave for that directory.

safe::setLogCmd ?cmd arg ... ?

Sets or queries the logging command used by the safe base.

Table 19-4 lists the aliases defined in a safe interpreter by the safe base.

Table 19-4. The safe base slave aliases

source

Loads scripts from directories in the access path.

load

Loads binary extensions from the slaves access path.

file

Only the dirname, join, extension, root, tail, pathname, and split operations are allowed.

exit

Destroys 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:

  • Small policies are better than big, complex policies. If you do a lot of complex processing to allow or disallow access to resources, chances are there are holes in your policy. Keep it simple.

  • Never eval arguments to aliases. If an alias accepts arguments that are passed by the slave, you must avoid being tricked into executing arbitrary Tcl code. The primary way to avoid this is never to eval arguments that are passed into an alias. Watch your expressions, too. The expr command does an extra round of substitutions, so brace all your expressions so that an attacker cannot pass [exit] where you expect a number!

  • Security policies do not compose. Each time you add a new alias to a security policy, it changes the nature of the policy. Even if alias1 and alias2 are safe in isolation, there is no guarantee that they cannot be used together to mount an attack. Each addition to a security policy requires careful review.

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 connect only 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 19-8 shows a simplified version of the Safesock security policy that is distributed with Tcl 8.0.

Example 19-8. 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 nonblocking mode so that 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 that 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 version also lets a downloaded script connect to the Web server that it came from. The Web browser plug-in is described in Chapter 20.

Limited Temporary Files

Example 19-9 improves on Example 19-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 can invoke the hidden puts in the slave. This alternative is shown in Example 19-10.

Example 19-9. 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 24–4 on page 389
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]

   }
   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 19-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 specifies only the name and access mode (i.e., for reading or writing.) The Tempfile policy can 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 317 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-3 on page 136. 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 that 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 19-10 shows the difference when using hidden commands.

Example 19-10. 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]

   }
   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 19-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.

Example 19-11. 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
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.27.45