Perl v5.26 removes . from @INC, but don’t think you’re safe!

Perl’s @INC might find code that you don’t want. That array is list of directories that use, require, and do search to find modules and libraries. By default, the last entry has been ., which represents the current working directory. That’s not a real directory; it’s a pointer to a directory you’ll discover later. There’s a fix for one consequence of this problem, but there are still issues of trust. That’s security—there are always more problems.

Locks

“Locks”, by Chilanga Cement on Flickr.

J.D. Lightsey and Todd Rinaldo from cPanel (a top-shelf Perl Foundation Core Maintenance Fund sponsor) first reported the issue as CVE-2016-1238. If someone puts a module in the current working directory, they don’t have to modify @INC to load it. However, this also means that someone else might put a module in the current working directory, and, in the case that it’s not already installed somewhere else in @INC, the application loads that one. If you’re running from a shared directory, this might affect you. You don’t even need to use the module in some cases as other modules might helpfully try to use it for you, as in a plugin or Storable’s object inflation bug.

This could be a problem; It could also not be a problem. However, Perls v5.22.3 and v5.24.1 (not yet released as I write this), modify several modules and programs in the perl source tree to remove the dot from @INC. The . will be gone in v5.26.

These patches don’t actually solve the big issue. They solve a very small corner issue, but the problem of trusting directories is much bigger. Consider the patch to my module App::Cpan. I loaded some modules conditionally:

my $log4perl_loaded = eval "require Log::Log4perl; 1";

A patch to fix this @INC problem creates a subroutine that “safely” loads a module:

sub _safe_load_module {
	my $name = shift;

	local @INC = @INC;
	pop @INC if $INC[-1] eq '.';
	eval "require $name; 1";
}

This wraps my previous calls:

_safe_load_module("Log::Log4perl");

But, there are many problems with this way of doing things. If you are worried about loading modules from directories writeable by other users, why would you limit yourself to looking at a single entry in @INC and only removing it if it has a hard-coded entry in only the last position? And, doing this despite the actual permissions on that directory? Not only that, how does this know that I didn’t purposely add that to @INC myself? But this is shim code until v5.26 removes the . and I don’t have to inspect the module search path.

This is a risk of using a multi-user computer with every utility, application, or programming language. It’s a consequence of the way things work in these systems. This means the system provides ways, by design, to circumvent this very limited protection. This is something that lives at the system level rather than the application level.

Perl could examine every entry in @INC to check their permissions. Most of the entries are likely to be writeable by other users since they are likely owned by a superuser (or other elevated-priveleges account). But, some of those users I have to trust. If I can’t trust root, I have a much bigger problem.

Should perl track a list of trusted users? Some of the directories might be group writeable. I maintain some systems where many people are able to affect system directories. I have a perl group for those people. Am I going to trust all of those? I think I trust them, but what if someone changed the group to add a user that I don’t trust?

What if one of the entries is a symlink? Maybe someone futzed with the filesytem so that one of the system directories points to a directory that we shouldn’t trust? If you’re using perlbrew, this is the problem you have. You install some stuff in your own directory then perlbrew manages symlinks to which ever version you want to use.

Now, suppose I have a foolproof way to check that everything about a directory is acceptable. Between the time I do that and the time I try to load the module, does that situation change? Do I need to lock everything? You know what a headache locking is.

So, there’s all of that. Some filesystems make it so that I can’t trust anything in @INC. That doesn’t mean I should try to fix something that we can, but it’s the sort of thing I should think about rather than feel safe.

But, let’s look at the fix a bit more. There are several statements in _safe_load_module. If I can load a perl debugger here (and there are many ways to do that), I get a chance to run arbitrary code after each statement. After you change @INC I can change it right back. But, I don’t need to change it back. I can append an entry so . is not the last one. Or, what if I had already appended to @INC in such a way that there were two entries for the current directory? Did you know that you can have a code reference in @INC?

I don’t even have to trick you into loading a module you don’t know about. If you’re using CPAN, you’re taking far more risk than loading modules from a writeable directory. How many of you actually review the code in the modules you use? I like Mojolicious, I trust it, and I’ve read a lot of the code, but I haven’t read all of it. When I upgrade, I don’t review the code changes.

What if they decide to play around with @INC? Every time I install a module from CPAN I take the slight risk that someone has tricked me into installing malicious code, the slightly greater risk of installing exploitable code, and the certainty of installing buggy code. Again, this doesn’t mean we shouldn’t fix the . case.

Or, using the techniques in Mastering Perl, I might simply redefine the _safe_load_module subroutine that makes people merely feel safe without doing the “safe” load.

So, that. If you’re in a situation where you are worrying about this sort of thing, you have other things to think about. Some of that might be system security. Some of that might include stringent scrubbing of @INC. Whatever that might be, security is a neverending examination of what’s going on.