Security and taint mode#
Security in a Perl program is mostly about not trusting data that came from outside the program: command-line arguments, the environment, file contents, network input. The dangerous step is not reading that data but acting on it. A filename that arrives from a web form is harmless until it reaches open; an environment variable is harmless until it steers a system call. This page describes the mechanism PetaPerl gives you to make that boundary explicit: taint mode. It explains what taint mode is, when it turns on, what it marks as untrusted, and how data is cleaned.
Taint mode#
PetaPerl runs the perl5 5.42 core, so taint mode is the real perl5 taint machinery, not an approximation. Every check perl5 performs, PetaPerl performs.
Taint mode turns on automatically when PetaPerl detects that it is running with differing real and effective user or group IDs, which is how a setuid (Unix mode 04000) or setgid (mode 02000) script is run. You can also enable it explicitly with the -T command-line switch. -T is strongly recommended for any program run on behalf of someone else, a CGI script being the classic case. Once taint mode is on, it stays on for the rest of the program; it cannot be turned off from inside.
The switch must appear early enough that the interpreter sees it before it starts parsing your program. On the #! line of a script this is automatic. On the command line, put it among the first switches. The related -t switch enables the same checks but downgrades the fatal errors to warnings, which is useful when retrofitting taint mode onto an existing program: you see every place that would fail without the program dying at the first one.
The read-only variable ${^TAINT} reports the current state:
| Meaning |
|---|---|
| Taint mode on, fatal ( |
| Taint checks on, warn only ( |
| Taint mode off |
What gets tainted#
When taint mode is on, any value that came from outside the program is marked tainted, and that mark propagates through every expression the value touches. The tainted sources are:
command-line arguments (
@ARGV) and the environment (%ENV);all file input;
the results of
readdirandreadlink, the variable read byshmread, the messages frommsgrcv, and the password, gcos, and shell fields returned by thegetpw*calls;locale data (see
perllocale).
Tainted data may not be used, directly or indirectly, in any operation that starts a sub-shell or that modifies a file, directory, or process. Using it in such an operation is fatal, with a message like Insecure dependency in %s or Insecure $ENV{PATH}. There are three exceptions:
arguments to
printandsyswriteare not checked;symbolic method calls (
$obj->$method(@args)) and symbolic subroutine references ($foo->(@args)) are not checked, so validate those names yourself if they can come from outside;hash keys are never tainted.
For efficiency, taint is tracked conservatively: if any part of an expression is tainted, the whole expression is treated as tainted, even where the tainted value does not actually influence the result. Taint attaches to individual scalar values, so some elements of an array or hash can be tainted while others are not.
my $arg = shift; # tainted (from @ARGV)
my $hid = $arg . 'bar'; # also tainted
my $line = <STDIN>; # tainted (file input)
my $data = 'abc'; # not tainted
system "echo $arg"; # dies: Insecure dependency
system "/bin/echo", $arg; # dies: tainted argument
system "echo $data"; # dies until PATH is cleaned (see below)
The one structural exception to whole-expression tainting is the ternary ?: operator. Because
my $result = $tainted ? "untainted" : "also untainted";
is just a conditional choice between two constants, $result is not tainted even though the condition is.
Laundering and detecting tainted data#
The only way to clear the taint on a value is to extract a substring of it through a regular-expression capture. PetaPerl assumes that if you bother to write a pattern and pull out $1, you have thought about what you are willing to accept. Untaint by validating against a whitelist of acceptable characters, never by trying to strip out the bad ones:
if ($data =~ /^([-\@\w.]+)$/) {
$data = $1; # $data is now untainted
} else {
die "rejected suspicious input";
}
This is reasonably safe because \w, a dot, a dash, and an at sign do not carry special meaning to a shell. A pattern like /(.+)/ would “launder” the value while accepting anything, which defeats the purpose. Be deliberate about the pattern.
There is a locale caveat. Under use locale the set of characters that \w matches is itself drawn from the locale, which is external data PetaPerl does not trust, so a \w match under use locale does not untaint. If you need to launder with \w in a locale-aware program, put no locale ahead of the match in the same block.
To test whether a value is tainted, use tainted from the Scalar::Util module. Under -T it correctly reports the taint state of its argument.
Cleaning up %ENV and $ENV{PATH}#
A tainted PATH is the most common surprise. Because PetaPerl cannot know that a program it runs will not itself consult PATH, it refuses to start a sub-process while $ENV{PATH} is tainted, with Insecure $ENV{PATH}. Every directory in the path must be absolute and writable only by its owner and group; a writable directory yields Insecure directory in %s. Note that an empty path component is treated as . (the current directory) and also triggers the message.
PATH is not the only offender. Because a shell may consult IFS, CDPATH, ENV, and BASH_ENV, PetaPerl requires those to be empty or untainted before it will start a sub-process, reporting Insecure $ENV{%s} otherwise. Set a known-good PATH and delete the shell variables early:
$ENV{PATH} = '/bin:/usr/bin';
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
When you must open a file or pipe from a privileged (setuid or setgid) program, the safe pattern is to fork a child, have the child drop its privileges back to the real user and group, and let the unprivileged child do the work and pass the result back over a pipe:
use English;
die "can't fork: $!" unless defined(my $pid = open(my $kid, "-|"));
if ($pid) { # parent
while (<$kid>) {
# consume the child's output
}
close $kid;
} else { # child
my @priv = ($EUID, $EGID);
my $real_uid = $UID;
my $real_gid = $GID;
$EUID = $UID;
$EGID = $GID;
$UID = $real_uid; # drop privileges
$GID = $real_gid;
($EUID, $EGID) = @priv; # make sure they are gone
die "can't drop privileges"
unless $UID == $EUID && $GID == $EGID;
$ENV{PATH} = '/bin:/usr/bin';
exec 'myprog', 'arg1', 'arg2'
or die "can't exec myprog: $!";
}
Note that exec is called with a list, never a single string, so no shell is involved to re-expand the arguments. This is the safest way to invoke an external command. The same fork-and-drop strategy works for glob and backticks, which (unlike system and exec) offer no list-form calling convention of their own.
Taint mode and @INC#
Under -T, PetaPerl ignores the PERL5LIB, PERLLIB, and PERL_USE_UNSAFE_INC environment variables. They are ignored because they are easy to set without a user noticing, which would let the environment inject library directories silently. You can still adjust the module search path with the visible -I switch or the lib pragma (-Mlib=/path), because those appear plainly on the command line.
The current directory . is not in the default @INC, taint mode or not. (PERL_USE_UNSAFE_INC=1 would re-add it, but that variable is itself ignored under -T, so . stays out of @INC for any tainted program.)
Set-id scripts and the shebang race#
PetaPerl detects the setuid/setgid condition (real user or group ID differing from the effective one) and enables taint mode accordingly; the “while running setuid” / “while running setgid” wording in the insecurity messages comes from that detection. PetaPerl does not, however, ship a setuid pperl install or any set-id helper. If you need a privileged Perl program, the supported approach on Linux is a small dedicated setuid wrapper that sanitises the environment and then execs the interpreter on a fixed, non-writable script path, rather than relying on a setuid script directly. The classic shebang race, where the kernel and the interpreter open the script file at two different moments and an attacker swaps the file in between, is a property of kernel set-id-script handling, not of the interpreter, and the wrapper approach sidesteps it.
Algorithmic complexity attacks#
Some internal algorithms can be driven to consume disproportionate time or memory by carefully chosen input, which is a denial-of-service vector independent of taint mode. The threat classes:
Hash collisions. An attacker who can predict the hash seed can craft keys that all land in one bucket, degrading hash operations. pperl randomises the hash seed at process start and randomises the visible order of
keys,values, andeachper hash. Two environment variables override these defaults for debugging:PERL_HASH_SEEDpins the seed (which controls storage, not the presented order) andPERL_PERTURB_KEYScontrols traversal-order perturbation; theHash::Utilfunctionhash_traversal_maskdoes the same per hash. These overrides weaken the protection and are not advised in production. PetaPerl has never guaranteed any hash key ordering; do not rely on it, and do not use the pseudo-random ordering as a shuffle.Catastrophic regex backtracking. The regular-expression engine is an NFA and can take time or space that grows steeply when a pattern can match an input in many ways. A hostile input matched against a vulnerable pattern can exhaust memory or CPU. Bound the input size and prefer patterns that cannot backtrack pathologically.
Sort.
sortuses mergesort, whose worst case is bounded, so it cannot be driven into quadratic behaviour by adversarial input.
Tied hashes may carry their own ordering and their own complexity characteristics.
Differences from upstream#
Linux only. PetaPerl runs only on Linux. The VMS, Windows, and other-platform set-id and security discussion that upstream Perl carries has no counterpart here and is simply absent.
No “protection” tooling. PetaPerl has no bytecode compiler and no source-filter obfuscation machinery, so the upstream advice about shipping compiled or filtered source does not apply. Obscuring source was never a security measure in any case.
Safecompartment not available yet. TheSafemodule (and theOpcodemodule it builds on) is not available in PetaPerl yet; loading it today fails withdynamic loading not available in this perl. Until it is, do not rely on the “run untrusted code in a restricted compartment” approach: treat untrusted code (as opposed to untrusted data) as something to run in a separate, OS-level sandbox or process with dropped privileges, not inside the interpreter.
See also#
perlvar-${^TAINT},%ENV, and the real/effective ID variables$<$>$($)that decide whether taint mode auto-enables.perllocale- why locale data is untrusted and howuse localeinteracts with\wlaundering.perlop- the?:operator and its exception to whole-expression tainting.Command-line switches - the
-T,-t, and-Iswitches in context.Command-line options - how pperl parses its switches, including the runtime selector.
Scalar::Util-tainted, for probing the taint state of a value.Hash::Util-hash_traversal_mask, for per-hash control of traversal-order randomisation.