Home / Alt manpages / perlsec(1)

  • perlsec(1)
  • User command
  • linux

Use Perl Taint Mode to Fence Untrusted Input

You will finish with a small Perl program that enables taint mode, validates input before using it, and launches a fixed command without handing data to a shell. The examples target Perl 5.38.2 from Ubuntu's perl and perl-doc packages. Allow about fifteen minutes if Perl is already installed.

This is a defensive programming guide, not a recipe for making an arbitrary setuid script safe. Taint checks reduce accidental trust in input. They do not replace privilege separation, careful file permissions, service isolation or review of the whole program.

1. Check the Perl version and enable taint mode

Run these ordinary, read-only commands:

$ perl -v
This is perl 5, version 38, subversion 2 (v5.38.2) built for x86_64-linux-gnu-thread-multi
$ perl -T -e 'print "taint mode enabled\n"'
taint mode enabled

The -T switch turns taint mode on for the rest of that process. Perl also enables it automatically when it detects differing real and effective user or group IDs. Use -T for a server or a program run on somebody else's behalf, even when it is not currently set-id.

Checkpoint

The first command identifies the interpreter you are testing. Do not assume that a different Perl installation has identical defaults.

2. See what tainted input looks like

Command-line arguments, environment variables and file input are tainted. Scalar::Util::tainted is included with Perl from 5.8.0, so this check needs no CPAN install:

$ perl -T -MScalar::Util=tainted -e 'print tainted($ARGV[0]) ? "tainted\n" : "clean\n"' example
tainted
$ perl -T -MScalar::Util=tainted -e '$x = "fixed"; print tainted($x) ? "tainted\n" : "clean\n"'
clean

Taint is attached to scalar values. Combining a clean value with tainted data can make the resulting expression tainted, and Perl takes a conservative view of expressions that have accessed tainted data. Hash keys are not tainted, but relying on that as a way to clean input hides the policy in a surprising place.

3. Validate, then launder a narrow value

Use a positive allow-list for the exact value your program expects. A capture from a successful regular expression match is Perl's documented laundering mechanism. This example accepts a simple identifier and rejects spaces, slashes and shell punctuation:

use strict;
use warnings;
use Scalar::Util qw(tainted);

my $name = shift @ARGV // die "missing name\n";
die "bad name\n" unless $name =~ /^([A-Za-z0-9_-]+)$/;
$name = $1;

die "validation failed\n" if tainted($name);
print "accepted: $name\n";

Run it from a temporary file such as /tmp/perl-name-check.pl, passing report_2026 as the argument. Do not copy this pattern unchanged for a path, a shell command, a URL or a username without defining that value's real grammar. The broad pattern /.+/ accepts almost anything and is not a security boundary. If use locale is active, locale definitions affect \w; this example uses explicit ASCII ranges instead.

Checkpoint

A rejected value should stop before any file, process or network operation. Log a safe reason, not secrets or the complete untrusted value.

4. Make subprocess execution explicit

Set a known absolute PATH before starting a subprocess. Perl checks PATH even when the executable name is fully qualified, because that executable may launch another program using PATH. The shell variables IFS, CDPATH, ENV and BASH_ENV can also affect shell behaviour:

$ENV{PATH} = '/usr/bin:/bin';
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};

my $exit = system('/usr/bin/logger', '--tag', 'perl-check', '--', $name);
die "logger failed\n" if $exit == -1 || $exit & 127 || $exit >> 8 != 0;

The argument list makes the program and its arguments separate values. Perl does not call a shell for system or exec when you pass an explicit parameter list. Do not switch to system("logger $name"), exec "sh", "-c", $name or a backtick expression. Those forms create a different problem: shell interpretation of data.

For a dry verification that does not write a system log, replace system temporarily with print join("\n", '/usr/bin/logger', '--tag', 'perl-check', '--', $name), "\n". If you do run the logger example, it changes system logging state by adding one message. Remove that message according to your normal logging retention process; do not delete a whole log file to undo a test.

5. Treat files, modules and set-id scripts separately

Taint mode allows some read-only operations that would be unsafe for writes, so it is not permission checking. Drop special user or group privileges before opening user-supplied files whenever the design permits. Use fixed directories, ownership checks and least privilege as separate controls.

With -T, Perl ignores PERL5LIB, PERLLIB and PERL_USE_UNSAFE_INC. You can still make a visible module path choice with -I/path or -Mlib=/path. Since Perl 5.26, the current directory is not in the default @INC, so do not add . to make an old script work around a missing dependency. Use an explicit, trusted directory instead.

Warning

Do not set the setuid or setgid bit on a Perl script as a quick test. The manual describes a kernel shebang race on systems without safe set-id script handling. Perl may refuse to run such a script, but that refusal does not repair the underlying exposure. Remove the special bit with an authorised administrator, for example chmod u-s /path/to/script, or use a carefully reviewed compiled wrapper and a separately reviewed privilege model.

6. Verify the boundary and remember the limits

Run the final test with an input that contains shell punctuation:

$ perl -T /tmp/perl-name-check.pl 'report; touch /tmp/should-not-exist'
bad name
$ test ! -e /tmp/should-not-exist && echo 'no side effect'
no side effect

That result demonstrates validation, not a complete security proof. Perl's standard Safe module can restrict selected operations for foreign code, but the perlsec manual warns that it does not stop infinite loops, excessive memory use or interpreter bugs. Do not use it as a sandbox for hostile code without an outer resource and process boundary.

Also avoid depending on hash key order. Perl 5.38 includes the hardened, randomised hash behaviour described by perlsec, but hash traversal order is not an API contract. If an output format needs stable ordering, sort the keys explicitly. Never weaken hash randomisation in production merely to reproduce an old order.

Done means

  • perl -T is present on the entry point that handles untrusted input.
  • Input is checked against a narrow positive grammar before it is reused.
  • PATH is absolute and trusted, shell-related environment variables are removed, and subprocess arguments are passed as a list.
  • Module search paths, file privileges and set-id behaviour have been considered separately from taint mode.
  • The punctuation test is rejected without creating the marker file.