January 14, 2017·4 min read·engineeringperlsecuritymodules

Perl 5.24.1 restricts optional module loading

Perl 5.24.1 changes how bundled tools find optional modules and requires explicit activation of PerlIO debugging. Both fixes narrow the effects of ambient process state.

An optional module lookup can execute code even when the application works perfectly without that module. The risk depends on where the loader searches. Perl 5.24.1 changes bundled tools and many core modules so they no longer search the default current-directory entry for optional dependencies. Its release notes describe the attack directly. Another user can place an optional module in an attacker-writable working directory, such as /tmp, and cause a process to load it. The release history dates this maintenance release to January 14, 2017.

This is a useful example of a maintenance change that alters behavior for a security reason. The release promises no intentional incompatibilities beyond its security changes, but those changes affect assumptions about module discovery. I still value Perl for short text work. That makes the circumstances under which a small script can discover and execute extra code particularly relevant to how I judge its defaults.

Optional does not mean inert

The release notes use Storable as their example. Before attempting to load Log::Agent, it removes the final . entry from @INC. The optional dependency remains optional, but its absence from the installed module directories no longer leads that lookup into the default current directory. The change restricts where the interpreter may satisfy that particular request.

The distinction between a module being necessary and a module being executable is central here. Wrapping require in eval allows a program to handle a failed load. It does not make a successful load harmless. If the search finds an attacker's module, handling the missing-module case never solves the actual problem. The search policy must exclude the unwanted location before the load happens.

For application code, the release notes supply an early removal of the default trailing entry:

BEGIN { pop @INC if $INC[-1] eq '.' }
use strict;

The condition matters. It removes the last entry only when that entry is exactly .. It does not remove the last installed module directory merely because the program wants to tighten its policy. Placing the operation in BEGIN also makes its timing explicit in the supplied example, before the subsequent use statement. This is a targeted adjustment to the default search list, not a claim that every possible module source has become trusted.

Libraries can limit the change to one lookup

A reusable module has another concern. Mutating a process-wide search list can affect its caller. The release notes therefore give a second form that localizes @INC around the optional lookup:

my $can_foo = eval {
    local @INC = @INC;
    pop @INC if $INC[-1] eq '.';
    require Foo;
};

This code copies the current search list into a localized value, removes its default trailing current-directory entry when present, and performs the load within that scope. The example separates the library's immediate loading decision from the application's broader policy. It is appropriate to describe exactly that scope, because the release does not remove . from every lookup everywhere.

The release also documents an unresolved exception. The base module treats every supplied module name as optional, and attempts to apply the restriction there caused difficulties. Consequently, this release leaves base unchanged. That qualification prevents a misleading description of 5.24.1 as a universal removal of current-directory loading. The actual change covers bundled tools and many core modules, with a named gap.

I regard that explicit exception as necessary release information. A maintenance update can improve the default behavior while preserving a compatibility problem in one component. Applications that rely on base still need an accurate account of the shipped behavior, rather than an assurance inferred from the broader security heading.

Debugging output had a timing problem

The other principal security change concerns PERLIO_DEBUG. Previously, the environment variable could direct PerlIO debugging output to a file when the interpreter was not running setuid and had not yet parsed -T or -t. Output produced early enough could therefore create or overwrite the named file even when the command line included -T. The intended command-line protection had not yet taken effect at the moment the environment influenced output.

Perl 5.24.1 requires the -Di switch to enable that debugging output. By default, the output goes to standard error. The environment variable may redirect it to a file, but Perl ignores that redirection when running setuid or when -T was supplied. In those cases, debugging output goes to standard error as other -D output does.

The mechanism addresses both activation and destination. An environment variable alone no longer activates the debugging output described in the advisory. Once debugging is explicitly requested, the rules still constrain where it goes under the specified execution modes. That is more precise than describing the fix as merely a different default filename.

A maintenance release can restore compatibility

The update also reverts an interpreter-redirection change introduced in 5.24.0. A hashbang path containing perl followed by 6 could trigger redirection to another interpreter. The release notes give #!/opt/perl64/bin/perl as a path that the rule broke. Its 6 belongs to a directory name, so the text match inferred an interpreter choice that the path did not establish.

That correction belongs beside the loading fixes because both affect what code an invocation selects before the script's intended work is complete. XSLoader also receives a security fix for loading binary files from outside @INC, according to the same release notes. Together, these changes give 5.24.1 a concrete purpose for existing installations. It tightens several code-loading and output decisions while retaining the maintenance branch's stated compatibility policy for other behavior.