Arjen Markus (12 november 2003) In response to a recent discussion on the c.l.t. about a problem that arose in the context of regular expressions, I have started this page. Its sole purpose:
document dangerous constructs in Tcl
Using the subst command on arbitrary data edit
set a "Hello,"
set b "world!"
set string "\$a \$b"
puts [subst $string]
gives:
Hello, world!
but:
set string "\[exit\]"
puts [subst $string]
stops your program!
The
subst command allows you to suppress the execution of commands:
puts [subst -nocommands $string]
gives:
[exit]
DKF: The way to protect against such things is to use
[safe interpreters
]. That doesn't stop you from making programming mistakes, but it does mean the leaks occur in a sandbox.
DKF: Also, [
subst -nocommands] doesn't protect against:
set string "foo,\$a(\[exit\]),$b,bar"
since that's principally a variable substitution.
Unexpected 0ctals edit
Remember that if Tcl is expecting an integer, and the value begins with a 0 (zero), it will interpret it as an octal number. This bug is triggered most often when trying to do arithmetic on date and time values obtained from [clock format]. This is especially dangerous since you can (e.g.) write a program in January that works just fine for months, then crashes in August and September.
Solution : The
scan page recommends:
scan $possiblyZeroedDecimal %d cleanInteger
Anything goes (in a switch) edit
RS: A simple error that will appear only at runtime is
not protecting a switch command with --:
switch $input {…}
The error will occur if $input starts with a minus (-) sign. So best always use
switch -- $input {...}
LV There are a number of other tcl commands which also support
-- ; if the command supports it, and you are using
random input from users or input files, you probably should use it.
DKF: The exact semantics of
switch were changed (in 8.6?) to ease this. Now, if called with only two arguments,
switch assumes the first is a value to switch on and the second is a collection of patterns and bodies. (The compiler then issues a jump table to implement it, which is rather fast.)
Expecting that only one other party can predict a socket edit
TV opening any server socket, expecting a certain other party to connect. For instance a file transfer à la ftp where a control connection triggers a file transfer over a separate socket pair.
DKF: Fixing this requires that you do an identification exchange after opening the socket. And possibly that you use
[tls::socket
] to open the socket in the first place.
FTP is unfixable without going to major effort (FTPS is rarely deployed, and sftp works in a totally different way under the covers).
Unbraced expressions edit
What's
dangerous about
unbraced expr?
Short answer:
# Uh-oh; what if it's "exec rm -rf ..." rather than "exec touch ..."?
set a {[exec touch /tmp/77]}
set b {[exec touch /tmp/78]}
catch {expr $a + 4}
catch {expr {$b + 4}}
DKF: Protection is two-fold. Brace those expressions unless you're really certain you know better, and use
[safe interpreters
] (see
subst discussion above).
Verify that something in a variable is an integer by using:
incr theVar 0
Verify that the value in a variable is numeric by using:
expr {$theVar + 0.0}
(Note: the expression is
braced so it is safe.)
Verify that something is a
list or
dictionary by using:
list $thePossibleList
dict size $thePossibleDict
Defaults for list searching edit
AM Here is another one:
set list {A B C D E}
set elem B
set a [lsearch $list $elem]
This will work nicely, unless you have a list like this:
set list {A B ? D E}
and you look for "?". This will in fact return 0, not 2!
You should use:
set a [lsearch -exact $list $elem]
as
lsearch uses glob patterns by default
DKF: Sometimes in 8.5, it is better to say:
if {$elem in $list} {
...
}
Comparisons also handle strings edit
RS once had to debug this:
proc foo max {
for {set i 0} {$i<=$max} {incr i} {
#do something with $i
}
}
Harmless enough. But if called with a nonnumeric argument
max, say
foo bar, it will never terminate, because any integer value of
i, in string comparison that is forced in this case, is less than
bar. Simple remedy: add
incr max 0
before the
for loop - that will throw an error
expected integer but got bar which is better to understand and fix than an endless loop somewhere deep in the code....
See also edit