NAME

PostgreSQL::PLPerl::Injector - Inject subroutines into the PostgreSQL plperl language

VERSION

version 1.002

SYNOPSIS

use PostgreSQL::PLPerl::Injector;

inject_plperl_with_names(@names);

inject_plperl_with_names_from($module => @names);

DESCRIPTION

The PostgreSQL plperl language enables functions (stored procedures) to be defined using Perl and executed inside the PostgreSQL server. PostgreSQL uses the Safe module to restrict the operations that can be performed in plperl.

PostgreSQL doesn't provide any mechanism for plperl code to access external code, like CPAN modules. This greatly limits the utility of the plperl language.

This module lets you 'inject' subroutine names into the Safe compartment in which plperl code is executed. You can then call those subroutines in your plperl code.

WARNING

This module 'monkey patches' the code of the Safe module being used within PostgreSQL. This naturally carries at least some theoretical risk. Do not use this module unless you understand the risks.

You can't fully understand this module or begin to evaluate the risks unless you've also read and understood the documentation for the Safe module.

By calling a subroutine name injected into plperl, control flow 'escapes' from the Safe container in which plperl code normally executes. The implications of this are subtle and hard to predict - even by those very familar with the internal workings of perl and Safe.

If you don't trust your users you should not use this module.

ENABLING

In order to use this module you need to arrange for it to be loaded when PostgreSQL initializes a Perl interpreter.

PostgreSQL 8.x

Set the PERL5OPT before starting postgres, to something like this:

PERL5OPT='-e "require q{plperlinit.pl}"'

and create a plperlinit.pl file in the same directory as your postgres.conf file.

In the plperlinit.pl file write the code to load this module, plus any others you want to load and share subroutines from. (After reading and considering the risks outlined in "WARNINGS".)

The code in the plperlinit.pl should also include delete $ENV{PERL5OPT}; to avoid any problems with nested invocation of perl, perhaps via a plperlu function.

PostgreSQL 9.0

For PostgreSQL 9.0 you can still use the PERL5OPT method described above. Alternatively, and preferably, you can use the plperl.on_init configuration variable in the postgres.conf file.

plperl.on_init='require q{plperlinit.pl};'

SHARING SUBROUTINES

inject_plperl_with_names

inject_plperl_with_names(@names)

For example:

# import names into the main plperlu namespace
use MIME::Base64 qw(encode_base64 decode_base64);

# share the names with the main plperl namespace
inject_plperl_with_names(qw(encode_base64 decode_base64))

Perl subroutines and variables that exist in a package namespace, i.e., non not lexicals, can be 'shared' with plperl using "inject_plperl_with_names". Doing so creates an alias with that name inside the isolated plperl namespace that refers to the corresponding item on the outside.

inject_plperl_with_names_from

inject_plperl_with_names_from($module => @names)

For example:

inject_plperl_with_names(MIME::Base64 => qw(encode_base64 decode_base64))

This is a convenience function that loads the specified module, imports the names into the callers namespace (for the convenience of the plperlu language) and then calls "inject_plperl_with_names" with the same list of names.

Risks of Sharing

Injecting a variable name allows plperl code to access and modify the variable and, if the value is a reference, access and modify any data it references.

Injecting a subroutine name allows plperl code to call the subroutine, naturally. This means execution control flow can move into code that can perform unrestricted operations. This is potentially very dangerous if you don't completely trust all current and future users who may have permission to use the plperl language. Therefore you should only share subroutines that you have carefully vetted.

As an extreme example of what not to do:

sub dangerous { my $code = shift; eval $code } # DANGEROUS
inject_plperl_with_names('dangerous');         # DANGEROUS

Given the above, plperl code would be able to completely escape the restrictions of the plperl language by calling

dangerous('... unsafe perl code ...');

Okay, I'm sure that seems pretty obvious and you'd never do that. Fine, but there are undoubtedly many possible ways to subvert the system that would be much harder to anticipate.

For example, a subroutine you've shared may call other code that, somewhere sometime, does a require to load an extra module. Perhaps hostile plperl code could alter @INC before calling the subroutine in order to cause hostile code to be loaded instead of the expected code. I've not checked this possibility, it's simply a plausible example.

Perl caches some values and references to values internally so it can be hard to evaluate the true risks without studying the perl source code or trying it yourself. Are you sure you're smarter than any potential attacker? Do you want to take the risk?

Hopefully I've made it clear that sharing subroutines with plperl is not something to be done lightly. Evaluate the code and the risks in each case.

Limitations of Sharing

The plperl language executes code with the package namespace modified such that a specific non-main:: package appears to be main::. Code compiled outside plperl may not refer to the same packages or package variables as code compiled inside plperl. This may cause subtle bugs.

NOTES

Sort Bug

A perl bug affects calls to sort() in plperl code: RT#60374. The $a and $b variables don't work in sort blocks inside Safe if the perl being used was compiled with threads enabled. This module enables a partial workaround:

# workaround http://rt.perl.org/rt3//Public/Bug/Display.html?id=60374
inject_plperl_with_names(qw(*a *b));

It's partial because it doesn't fix calls to sort() compiled in other packages.

Untested conjecture: it might be possible to extend this workaround on a package-by-package basis, something like this:

# make sort work in package Foo
*Foo::a = *a;
*Foo::b = *b;
inject_plperl_with_names(qw(*Foo::a *Foo::b));

If so it might be worth adding a new function: inject_plperl_sort_fix('Foo');

A better fix is to compile postgres to use a perl that was configured with multiplicity but not threads (Configure -Uusethreads -Dusemultiplicity). That'll not only fix the sort bug but also give you a significant boost in the performance of your perl code.

AUTHOR

Tim Bunce http://www.tim.bunce.name

Copyright (c) Tim Bunce, Ireland, 2010. All rights reserved. You may use and distribute on the same terms as Perl 5.10.1.

With thanks to http://www.TigerLead.com for sponsoring development.

REMINDER

You did read and understand the "WARNING" didn't you?

This code is incomplete and disabled. It's difficult to implement in a reasonably secure manner.

inject_plperl_with_code

inject_plperl_with_code($perl_code, $allowed_opcodes, $load_dependencies);

This function provides an alternative approach for making external code available to plperl. Instead of loading code outside of plperl and then sharing individual subroutines via "inject_plperl_with_names", using "inject_plperl_with_code" you can load entire modules into plperl or just execute arbitrary fragments of perl code:

inject_plperl_with_code('sub foo { ... }');

Opcodes

The plperl language uses Safe to restrict what operations (perl opcodes) can be compiled. For example, some introspection operators like caller() and all file operations like open() are not allowed. inject_plperl_with_code() honours those restrictions by default.

In order to execute $perl_code it's likely that you'll need to relax the restrictions to allow specific opcodes (or named groups of opcodes called optags). You can do that by listing them in $allowed_opcodes, separated by commas. Remember that any opcodes you allow create a potential risk if hostile plperl could execute the subs that use those opcodes.

but allows you to relax the them

inject_plperl_with_code(
    q{ use MIME::Base64 qw(encode_base64 decode_base64) },
    'require,caller,tied',
    allow_nested_load => 1
);


# For old perls we add entereval if entertry is listed
# due to http://rt.perl.org/rt3/Ticket/Display.html?id=70970
# Testing with a recent perl (>=5.11.4) ensures this doesn't
# allow any use of actual entereval (eval "...") opcodes.

Nested use/require

XXX

Other issues

Refer to "Sort Bug" affecting code loaded inside the compartment.

my @requested_module; sub inject_plperl_with_module { my ($module, $imports, $ops) = @_; # XXX sanity check push @requested_module, [ $module, $imports, $ops ]; }

my @requested_code; sub inject_plperl_with_code { my ($code, $ops) = @_; # XXX sanity check ops push @requested_code, [ $code, $ops ]; }

sub _inject_module { my ($safe, $module, $imports, $ops) = @_;

$ops .= ',require';

require DynaLoader;
require XSLoader;
my $xsl_entry = 'XSLoader::load';

# share dynaloader and xsloader
# load the module
# unshare them
$safe->share_from('main', [ $xsl_entry ]);

$safe->reval(q{ $INC{'XSLoader.pm'} = 'injected' });

my $use = sprintf "use %s %s", $module,
    ($imports && @$imports) ? "qw(@$imports)" : "";
_warn "inject module: $use";
eval { _inject_code($safe, $use, $ops) };
die $@ if $@;

*{ $safe->varglob($xsl_entry) } = sub {
    die "$xsl_entry not available within plperl";
};
}

sub _inject_code { my ($safe, $code, $ops, $allow_use) = @_; $ops ||= '';

# For old perls we add entereval if entertry is listed
# due to http://rt.perl.org/rt3/Ticket/Display.html?id=70970
# Testing with a recent perl (>=5.11.4) ensures this doesn't
# allow any use of actual entereval (eval "...") opcodes.
$ops = "entereval,$ops"
    if $] < 5.011004 and $ops =~ /\bentertry\b/;

_warn(sprintf "Executing in plperl: %s%s%s",
    $code,
    $ops ? ". Extra ops '$ops'" : "",
    $allow_use ? '. Nested use allowed' : "");

# XXX disallow use unless $allow_use

my $mask = $safe->mask;

# relax, eval, restrict, propagate
$safe->permit(split /\s*,\s*/, $ops) if $ops;

my $ok = $safe->reval("$code; 1");
$safe->mask($mask);
if (not $ok) {
    chomp $@;
    $@ =~ s/\.$//;
    die "$@ (while executing $code)\n";
}
_warn "Done executing in plperl.\n";
}