--- name: Perl security and taint mode --- # 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`](../../../guide/oneliner/switches) 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`](../../../guide/oneliner/switches) 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: | `${^TAINT}` | Meaning | |-------------|----------------------------------| | `1` | Taint mode on, fatal (`-T`) | | `-1` | Taint checks on, warn only (`-t`)| | `0` | 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`](perlvar/args-env-inc)); - all file input; - the results of [`readdir`](perlfunc/readdir) and `readlink`, the variable read by `shmread`, the messages from `msgrcv`, and the password, gcos, and shell fields returned by the `getpw*` calls; - locale data (see [`perllocale`](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 [`print`](perlfunc/print) and `syswrite` are **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. ```perl my $arg = shift; # tainted (from @ARGV) my $hid = $arg . 'bar'; # also tainted my $line = ; # 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 [`?:`](perlop) operator. Because ```perl 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: ```perl 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`](perllocale) 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`](../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: ```perl $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: ```perl 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`](perlfunc/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`](perlfunc/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`](perlfunc/keys), [`values`](perlfunc/values), and [`each`](perlfunc/each) per hash. Two environment variables override these defaults for debugging: `PERL_HASH_SEED` pins the seed (which controls storage, not the presented order) and `PERL_PERTURB_KEYS` controls traversal-order perturbation; the [`Hash::Util`](../Hash/Util) function [`hash_traversal_mask`](../Hash/Util/hash_traversal_mask) does 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.** [`sort`](perlfunc/sort) uses 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. - **`Safe` compartment not available yet.** The `Safe` module (and the `Opcode` module it builds on) is not available in PetaPerl yet; loading it today fails with `dynamic 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`](perlvar) - `${^TAINT}`, [`%ENV`](perlvar/args-env-inc), and the real/effective ID variables `$<` `$>` `$(` `$)` that decide whether taint mode auto-enables. - [`perllocale`](perllocale) - why locale data is untrusted and how `use locale` interacts with `\w` laundering. - [`perlop`](perlop) - the [`?:`](perlop) operator and its exception to whole-expression tainting. - [Command-line switches](../../../guide/oneliner/switches) - the `-T`, `-t`, and `-I` switches in context. - [Command-line options](../../../cli_options) - how pperl parses its switches, including the runtime selector. - [`Scalar::Util`](../Scalar/Util) - `tainted`, for probing the taint state of a value. - [`Hash::Util`](../Hash/Util) - [`hash_traversal_mask`](../Hash/Util/hash_traversal_mask), for per-hash control of traversal-order randomisation.