Secure Programming for Linux and Unix HOWTO
Prev | Chapter 10. Language-Specific Issues | Next

# 10.7. Tcl

Tcl stands for ``tool command language'' and is pronounced ``tickle.'' Tcl is divided into two parts: a language and a library. The language is a simple language, originally intended for issuing commands to interactive programs and including basic programming capabilities. The library can be embedded in application programs. You can find more information about Tcl at sites such as the Tcl.tk and the Tcl WWW Info web page and the comp.lang.tcl FAQ launch page at http://www.tclfaq.wservice.com/tcl-faq. My thanks go to Wojciech Kocjan for providing some of this detailed information on using Tcl in secure applications.

For some security applications, especially interesting components of Tcl are Safe-Tcl (which creates a sandbox in Tcl) and Safe-TK (which implements a sandboxed portable GUI for Safe Tcl), as well as the WebWiseTclTk Toolkit which permits Tcl packages to be automatically located and loaded from anywhere on the World Wide Web. You can find more about the latter from http://www.cbl.ncsu.edu/software/WebWiseTclTk. It's not clear to me how much code review this has received.

Tcl's original design goal to be a small, simple language resulted in a language that was originally somewhat limiting and slow. For an example of the limiting weaknesses in the original language, see Richard Stallman's ``Why You Should Not Use Tcl''. For example, Tcl was originally designed to really support only one data type (string). Thankfully, these issues have been addressed over time. In particular, version 8.0 added support for more data types (integers are stored internally as integers, lists as lists and so on). This improves its capabilities, and in particular improves its speed.

As with essentially all scripting languages, Tcl has an "eval" command that parses and executes arbitrary Tcl commands. And like all such scripting languages, this eval command needs to be used especially carefully, or an attacker could insert characters in the input to cause malicious things to occur. For example, an attackers may be able insert characters with special meaning to Tcl such as embedded whitespace (including space and newline), double-quote, curly braces, square brackets, dollar signs, backslash, semicolon, or pound sign (or create input to cause these characters to be created during processing). This also applies to any function that passes data to eval as well (depending on how eval is called).

Here is a small example that may make this concept clearer; first, let's define a small function and then interactively invoke it directly - note that these uses are fine:

     proc something {a b c d e} {
           puts "A='$a'"
           puts "B='$b'"
           puts "C='$c'"
           puts "D='$d'"
           puts "E='$e'"
     }

     % # This works normally:
     % something "test 1" "test2" "t3" "t4" "t5"
     A='test 1'
     B='test2'
     C='t3'
     D='t4'
     E='t5'

     % # Imagine that str1 is set by an attacker:
     % set str1 {test 1 [puts HELLOWORLD]}

     % # This works as well
     % something $str1 t2 t3 t4 t5
     A='test 1 [puts HELLOWORLD]'
     B='t2'
     C='t3'
     D='t4'
     E='t5'

However, continuing the example, let's see how "eval" can be incorrectly and correctly called. If you call eval in an incorrect (dangerous) way, it allows attackers to misuse it. However, by using commands like list or lrange to correctly group the input, you can avoid this problem:

     % # This is the WRONG way - str1 is interpreted.
     % eval something $str1 t2 t3
     HELLOWORLD
     A='test'
     B='1'
     C=''
     D='t2'
     E='t3'

     % # Here's one solution, using "list".
     % eval something [list $str1 t2 t3 t4 t5]
     A='test 1 [puts HELLOWORLD]'
     B='t2'
     C='t3'
     D='t4'
     E='t5'

     % # Here's another solution, using lrange:
     % eval something [lrange $str1 0 end] t2
     A='test'
     B='1'
     C='[puts'
     D='HELLOWORLD]'
     E='t2'

Using lrange is useful when concatenating arguments to a called function, e.g., with more complex libraries using callbacks. In Tcl, eval is often used to create a one-argument version of a function that takes a variable number of arguments, and you need to be careful when using it this way. Here's another example (presuming that you've defined a "printf" function):

     proc vprintf {str arglist} {
          eval printf [list $str] [lrange $arglist 0 end]
     }

     % printf "1+1=%d  2+2=%d" 2 4
     % vprintf "1+1=%d  2+2=%d" {2 4}

Fundamentally, when passing a command that will be eventually evaluated, you must pass Tcl commands as a properly built list, and not as a (possibly concatentated) string. For example, the "after" command runs a Tcl command after a given number of milliseconds; if the data in $param1 can be controlled by an attacker, this Tcl code is dangerously wrong:

      # DON'T DO THIS if param1 can be controlled by an attacker
      after 1000 "someCommand someparam $param1"

This is wrong, because if an attacker can control the value of $param1, the attacker can control the program. For example, if the attacker can cause $param1 to have '[exit]', then the program will exit. Also, if $param1 would be '; exit', it would also exit.

Thus, the proper alternative would be:

     after 1000 [list someCommand someparam $param1]

Even better would be something like the following:

     set cmd [list someCommand someparam]
     after 1000 [concat $cmd $param1]

Here's another example showing what you shouldn't do, pretending that $params is data controlled by possibly malicious user:

     set params "%-20s TESTSTRING"
     puts "'[eval format $params]'"

will result in:

     'TESTSTRING       '

But, when if the untrusted user sends data with an embedded newline, like this:

     set params "%-20s TESTSTRING\nputs HELLOWORLD"
     puts "'[eval format $params]'"

The result will be this (notice that the attacker's code was executed!):

     HELLOWORLD
     'TESTINGSTRING       '

Wojciech Kocjan suggests that the simplest solution in this case is to convert this to a list using lrange, doing this:

     set params "%-20s TESTINGSTRING\nputs HELLOWORLD"
     puts "'[eval format [lrange $params 0 end]]'"

The result would be:

     'TESTINGSTRING       '

Note that this solution presumes that the potentially malicious text is concatenated to the end of the text; as with all languages, make sure the attacker cannot control the format text.

As a matter of style always use curly braces when using if, while, for, expr, and any other command which parses an argument using expr/eval/subst. Doing this will avoid a common error when using Tcl called unintended double substitution (aka double substitution). This is best explained by example; the following code is incorrect:

     while ![eof $file] {
         set line [gets $file]
     }

The code is incorrect because the "![eof $file]" text will be evaluated by the Tcl parser when the while command is executed the first time, and not re-evaluated in every iteration as it should be. Instead, do this:

     while {![eof $file]} {
          set line [gets $file]
     }

Note that both the condition, and the action to be performed, are surrounded by curly braces. Although there are cases where the braces are redundant, they never hurt, and when you fail to include the curly braces where they're needed (say, when making a minor change) subtle and hard-to-find errors often result.

More information on good Tcl style can be found in documents such as Ray Johnson's Tcl Style Guide.

In the past, I have stated that I don't recommend Tcl for writing programs which must mediate a security boundary. Tcl seems to have improved since that time, so while I cannot guarantee Tcl will work for your needs, I can't guarantee that any other language will work for you either. Again, my thanks to Wojciech Kocjan who provided some of these suggestions on how to write Tcl code for secure applications.

Prev | Home | Next
Java | Up | PHP