March 20, 2017·4 min read·engineeringperlsecuritymodules

Perl 5.25.11 stops searching the working directory by default

Removing dot from the default module search path changes how Perl treats its working directory. The development release also warns when a do statement depends on the old search behavior.

A working directory is often an input to a program, but Perl has also treated it as a place to find executable dependencies. The default . entry in @INC makes that connection implicit. Perl 5.25.11 removes the entry by default, according to its release notes. The release tag dates the snapshot to March 20, 2017. A program that needs code from its current directory must now express that dependency through its configuration or a path.

The change addresses a security problem in the relationship between code loading and ambient process state. A script may be installed in a controlled location yet run in a directory writable by someone else. Searching that directory gives its contents a role in selecting code. Removing the default entry narrows the locations searched without requiring every application to remember to remove it during startup.

I still choose Perl for short text transformations. That makes a safer loading default attractive, because a small script should not require extensive setup merely to avoid treating its input directory as a library directory. The compatibility effects are real, but they identify a dependency that the old default left unstated.

A build default changes the initial search list

The release turns on DEFAULT_INC_EXCLUDES_DOT. That configuration detail matters because the update concerns the default value of @INC. It does not establish a rule that Perl can never load a file from the working directory. Programs still need to distinguish between relying on a search list and supplying a filename that names the intended location.

This is a broader adjustment than removing . around one optional load. The initial search list itself changes. A script that previously found a private module only because it happened to start in the right directory may therefore behave differently without any change to its own source. Its installed dependencies can still be present while its unexpressed local dependency fails to resolve.

That failure is informative. The search path previously answered a question that the program had not made explicit, namely whether the launch directory should count as a source of code. A missing dependency now requires a decision about the intended source. Reintroducing the current directory recreates the same relationship with whoever can write there, so it is a policy choice with a security consequence.

A bare do filename also uses the search path

The versioned function documentation explains a less obvious compatibility effect. do 'stat.pl' searches the @INC directories. Historically, the presence of . often made that expression behave like an explicit relative filename. With the default entry removed, the two forms no longer imply the same lookup.

The explicit form documented for executing a file in the current directory is:

do './stat.pl';

The ./ identifies the location directly. It does not ask the loader to find stat.pl among module directories. This is appropriate only when the caller intends to execute that particular local file. Merely adding the prefix to make an old error disappear would preserve a trust decision that deserves to be understood.

The snapshot adds a warning for do failures associated with the removal of .. Its diagnostic says that the file failed and . is no longer in @INC. That explanation connects an apparent missing-file problem with the changed search policy. An administrator who can see a file in the directory otherwise has little reason to infer why the same bare name no longer reaches it.

Executing a file needs error handling

An explicit path does not change the rest of do's contract. The function documentation says that it executes a file as Perl code and reparses it on every call. Code evaluated this way cannot see lexical variables in the enclosing scope. These properties distinguish it from treating a configuration file as passive text or pasting its contents into the caller's lexical environment.

The return value also carries several meanings. A readable file that fails to compile makes do return undef and sets $@. A file that cannot be read makes it return undef and sets $!. Successful compilation returns the last expression's value, which can itself be false. The documentation says to check $@ first because a compilation failure can also set $!.

Those distinctions remain necessary after correcting the path. A diagnostic about the default search list explains one possible loading failure, but it cannot replace the application's handling of parse errors or the executed file's result. Existing scripts that use Perl files for configuration need both an intentional filename and an accurate interpretation of the result.

I regard the warning as a useful part of this security change. The new default reduces implicit code discovery, while the diagnostic explains the compatibility break at the point where it occurs. For a caller that deliberately executes ./stat.pl, the source of that code is then visible in the expression itself, and the documented do return and error rules still govern what happens next.