Abandon the use of Perl's Safe.pm to enforce restrictions in plperl, as it is
fundamentally insecure. Instead apply an opmask to the whole interpreter that imposes restrictions on unsafe operations. These restrictions are much harder to subvert than is Safe.pm, since there is no container to be broken out of. Backported to release 7.4. In releases 7.4, 8.0 and 8.1 this also includes the necessary backporting of the two interpreters model for plperl and plperlu adopted in release 8.2. In versions 8.0 and up, the use of Perl's POSIX module to undo its locale mangling on Windows has become insecure with these changes, so it is replaced by our own routine, which is also faster. Nice side effects of the changes include that it is now possible to use perl's "strict" pragma in a natural way in plperl, and that perl's $a and $b variables now work as expected in sort routines, and that function compilation is significantly faster. Tim Bunce and Andrew Dunstan, with reviews from Alex Hunsaker and Alexey Klyukin. Security: CVE-2010-1169
This commit is contained in:
parent
ffba89a9bb
commit
68e621bfa4
@ -1,5 +1,5 @@
|
||||
<!--
|
||||
$PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.49.2.1 2006/05/30 12:32:37 momjian Exp $
|
||||
$PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.49.2.2 2010/05/13 16:43:40 adunstan Exp $
|
||||
-->
|
||||
|
||||
<chapter id="plperl">
|
||||
@ -275,12 +275,7 @@ SELECT * FROM perl_set();
|
||||
<programlisting>
|
||||
use strict;
|
||||
</programlisting>
|
||||
in the function body. But this only works in <application>PL/PerlU</>
|
||||
functions, since <literal>use</> is not a trusted operation. In
|
||||
<application>PL/Perl</> functions you can instead do
|
||||
<programlisting>
|
||||
BEGIN { strict->import(); }
|
||||
</programlisting>
|
||||
in the function body.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
@ -596,6 +591,25 @@ $$ LANGUAGE plperl;
|
||||
If the above function was created by a superuser using the language
|
||||
<literal>plperlu</>, execution would succeed.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
For security reasons, to stop a leak of privileged operations from
|
||||
<application>PL/PerlU</> to <application>PL/Perl</>, these two languages
|
||||
have to run in separate instances of the Perl interpreter. If your
|
||||
Perl installation has been appropriately compiled, this is not a problem.
|
||||
However, not all installations are compiled with the requisite flags.
|
||||
If <productname>PostgreSQL</> detects that this is the case then it will
|
||||
not start a second interpreter, but instead create an error. In
|
||||
consequence, in such an installation, you cannot use both
|
||||
<application>PL/PerlU</> and <application>PL/Perl</> in the same backend
|
||||
process. The remedy for this is to obtain a Perl installation created
|
||||
with the appropriate flags, namely either <literal>usemultiplicity</> or
|
||||
both <literal>usethreads</> and <literal>useithreads</>.
|
||||
For more details,see the <literal>perlembed</> manual page.
|
||||
</para>
|
||||
</note>
|
||||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="plperl-triggers">
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Makefile for PL/Perl
|
||||
# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.25 2005/07/13 17:12:56 tgl Exp $
|
||||
# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.25.2.1 2010/05/13 16:43:40 adunstan Exp $
|
||||
|
||||
subdir = src/pl/plperl
|
||||
top_builddir = ../../..
|
||||
@ -23,7 +23,7 @@ perl_embed_ldflags := -L$(perl_archlibexp)/CORE -lperl58
|
||||
override CPPFLAGS += -DPLPERL_HAVE_UID_GID
|
||||
endif
|
||||
|
||||
override CPPFLAGS := -I$(srcdir) $(CPPFLAGS) -I$(perl_archlibexp)/CORE
|
||||
override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) -I$(perl_archlibexp)/CORE
|
||||
|
||||
rpathdir = $(perl_archlibexp)/CORE
|
||||
|
||||
@ -36,14 +36,27 @@ OBJS = plperl.o spi_internal.o SPI.o
|
||||
|
||||
SHLIB_LINK = $(perl_embed_ldflags) $(BE_DLLLIBS)
|
||||
|
||||
REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-language=plperl
|
||||
REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-language=plperl --load-language=plperlu
|
||||
REGRESS = plperl plperl_trigger plperl_shared plperl_elog
|
||||
# if Perl can support two interpreters in one backend,
|
||||
# test plperl-and-plperlu cases
|
||||
ifneq ($(PERL),)
|
||||
ifeq ($(shell $(PERL) -V:usemultiplicity), usemultiplicity='define';)
|
||||
REGRESS += plperlu_plperl
|
||||
endif
|
||||
endif
|
||||
|
||||
include $(top_srcdir)/src/Makefile.shlib
|
||||
|
||||
|
||||
all: all-lib
|
||||
|
||||
plperl.o: plperl_opmask.h
|
||||
|
||||
plperl_opmask.h: plperl_opmask.pl
|
||||
$(PERL) $< $@
|
||||
|
||||
|
||||
SPI.c: SPI.xs
|
||||
$(PERL) $(perl_privlibexp)/ExtUtils/xsubpp -typemap $(perl_privlibexp)/ExtUtils/typemap $< >$@
|
||||
|
||||
@ -91,7 +104,7 @@ submake:
|
||||
$(MAKE) -C $(top_builddir)/src/test/regress pg_regress
|
||||
|
||||
clean distclean maintainer-clean: clean-lib
|
||||
rm -f SPI.c $(OBJS)
|
||||
rm -f SPI.c $(OBJS) plperl_opmask.h
|
||||
rm -rf results
|
||||
rm -f regression.diffs regression.out
|
||||
|
||||
|
@ -420,3 +420,9 @@ SELECT array_of_text();
|
||||
{{"a\"b","c,d"},{"e\\f",g}}
|
||||
(1 row)
|
||||
|
||||
--
|
||||
-- Test detection of unsafe operations
|
||||
CREATE OR REPLACE FUNCTION perl_unsafe1() RETURNS void AS $$
|
||||
my $fd = fileno STDERR;
|
||||
$$ LANGUAGE plperl;
|
||||
ERROR: creation of Perl function failed: 'fileno' trapped by operation mask at line 2.
|
||||
|
76
src/pl/plperl/expected/plperlu_plperl.out
Normal file
76
src/pl/plperl/expected/plperlu_plperl.out
Normal file
@ -0,0 +1,76 @@
|
||||
--
|
||||
-- Test that recursing between plperl and plperlu doesn't allow plperl to perform unsafe ops
|
||||
--
|
||||
-- recurse between a plperl and plperlu function that are identical except that
|
||||
-- each calls the other. Each also checks if an unsafe opcode can be executed.
|
||||
CREATE OR REPLACE FUNCTION recurse_plperl(i int) RETURNS SETOF TEXT LANGUAGE plperl
|
||||
AS $$
|
||||
my $i = shift;
|
||||
return unless $i > 0;
|
||||
return_next "plperl $i entry: ".((eval "stat;1") ? "ok" : $@);
|
||||
return_next $_
|
||||
for map { $_->{recurse_plperlu} }
|
||||
@{spi_exec_query("select * from recurse_plperlu($i-1)")->{rows}};
|
||||
return;
|
||||
$$;
|
||||
CREATE OR REPLACE FUNCTION recurse_plperlu(i int) RETURNS SETOF TEXT LANGUAGE plperlu
|
||||
AS $$
|
||||
my $i = shift;
|
||||
return unless $i > 0;
|
||||
return_next "plperlu $i entry: ".((eval "stat;1") ? "ok" : $@);
|
||||
return_next $_
|
||||
for map { $_->{recurse_plperl} }
|
||||
@{spi_exec_query("select * from recurse_plperl($i-1)")->{rows}};
|
||||
return;
|
||||
$$;
|
||||
SELECT * FROM recurse_plperl(5);
|
||||
recurse_plperl
|
||||
---------------------------------------------------------------
|
||||
plperl 5 entry: 'stat' trapped by operation mask at line 1.
|
||||
|
||||
plperlu 4 entry: ok
|
||||
plperl 3 entry: 'stat' trapped by operation mask at line 1.
|
||||
|
||||
plperlu 2 entry: ok
|
||||
plperl 1 entry: 'stat' trapped by operation mask at line 1.
|
||||
|
||||
(5 rows)
|
||||
|
||||
SELECT * FROM recurse_plperlu(5);
|
||||
recurse_plperlu
|
||||
---------------------------------------------------------------
|
||||
plperlu 5 entry: ok
|
||||
plperl 4 entry: 'stat' trapped by operation mask at line 1.
|
||||
|
||||
plperlu 3 entry: ok
|
||||
plperl 2 entry: 'stat' trapped by operation mask at line 1.
|
||||
|
||||
plperlu 1 entry: ok
|
||||
(5 rows)
|
||||
|
||||
--
|
||||
-- Make sure we can't use/require things in plperl
|
||||
--
|
||||
CREATE OR REPLACE FUNCTION use_plperlu() RETURNS void LANGUAGE plperlu
|
||||
AS $$
|
||||
use Errno;
|
||||
$$;
|
||||
CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
|
||||
AS $$
|
||||
use Errno;
|
||||
$$;
|
||||
ERROR: creation of Perl function failed: Unable to load Errno.pm into plperl at line 2.
|
||||
BEGIN failed--compilation aborted at line 2.
|
||||
-- make sure our overloaded require op gets restored/set correctly
|
||||
select use_plperlu();
|
||||
use_plperlu
|
||||
-------------
|
||||
|
||||
(1 row)
|
||||
|
||||
CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
|
||||
AS $$
|
||||
use Errno;
|
||||
$$;
|
||||
ERROR: creation of Perl function failed: Unable to load Errno.pm into plperl at line 2.
|
||||
BEGIN failed--compilation aborted at line 2.
|
File diff suppressed because it is too large
Load Diff
62
src/pl/plperl/plperl_opmask.pl
Normal file
62
src/pl/plperl/plperl_opmask.pl
Normal file
@ -0,0 +1,62 @@
|
||||
#!perl -w
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Opcode qw(opset opset_to_ops opdesc full_opset);
|
||||
|
||||
my $plperl_opmask_h = shift
|
||||
or die "Usage: $0 <output_filename.h>\n";
|
||||
|
||||
my $plperl_opmask_tmp = $plperl_opmask_h."tmp";
|
||||
END { unlink $plperl_opmask_tmp }
|
||||
|
||||
open my $fh, ">", "$plperl_opmask_tmp"
|
||||
or die "Could not write to $plperl_opmask_tmp: $!";
|
||||
|
||||
printf $fh "#define PLPERL_SET_OPMASK(opmask) \\\n";
|
||||
printf $fh " memset(opmask, 1, MAXO);\t/* disable all */ \\\n";
|
||||
printf $fh " /* then allow some... */ \\\n";
|
||||
|
||||
my @allowed_ops = (
|
||||
# basic set of opcodes
|
||||
qw[:default :base_math !:base_io sort time],
|
||||
# require is safe because we redirect the opcode
|
||||
# entereval is safe as the opmask is now permanently set
|
||||
# caller is safe because the entire interpreter is locked down
|
||||
qw[require entereval caller],
|
||||
# These are needed for utf8_heavy.pl:
|
||||
# dofile is safe because we redirect the opcode like require above
|
||||
# print is safe because the only writable filehandles are STDOUT & STDERR
|
||||
# prtf (printf) is safe as it's the same as print + sprintf
|
||||
qw[dofile print prtf],
|
||||
# Disallow these opcodes that are in the :base_orig optag
|
||||
# (included in :default) but aren't considered sufficiently safe
|
||||
qw[!dbmopen !setpgrp !setpriority],
|
||||
);
|
||||
|
||||
if (grep { /^custom$/ } opset_to_ops(full_opset) ) {
|
||||
# custom is not deemed a likely security risk as it can't be generated from
|
||||
# perl so would only be seen if the DBA had chosen to load a module that
|
||||
# used it. Even then it's unlikely to be seen because it's typically
|
||||
# generated by compiler plugins that operate after PL_op_mask checks.
|
||||
# But we err on the side of caution and disable it, if it is actually
|
||||
# defined.
|
||||
push(@allowed_ops,qw[!custom]);
|
||||
}
|
||||
|
||||
printf $fh " /* ALLOWED: @allowed_ops */ \\\n";
|
||||
|
||||
foreach my $opname (opset_to_ops(opset(@allowed_ops))) {
|
||||
printf $fh qq{ opmask[OP_%-12s] = 0;\t/* %s */ \\\n},
|
||||
uc($opname), opdesc($opname);
|
||||
}
|
||||
printf $fh " /* end */ \n";
|
||||
|
||||
close $fh
|
||||
or die "Error closing $plperl_opmask_tmp: $!";
|
||||
|
||||
rename $plperl_opmask_tmp, $plperl_opmask_h
|
||||
or die "Error renaming $plperl_opmask_tmp to $plperl_opmask_h: $!";
|
||||
|
||||
exit 0;
|
@ -301,3 +301,9 @@ LANGUAGE plperl as $$
|
||||
$$;
|
||||
|
||||
SELECT array_of_text();
|
||||
|
||||
--
|
||||
-- Test detection of unsafe operations
|
||||
CREATE OR REPLACE FUNCTION perl_unsafe1() RETURNS void AS $$
|
||||
my $fd = fileno STDERR;
|
||||
$$ LANGUAGE plperl;
|
||||
|
53
src/pl/plperl/sql/plperlu_plperl.sql
Normal file
53
src/pl/plperl/sql/plperlu_plperl.sql
Normal file
@ -0,0 +1,53 @@
|
||||
--
|
||||
-- Test that recursing between plperl and plperlu doesn't allow plperl to perform unsafe ops
|
||||
--
|
||||
|
||||
-- recurse between a plperl and plperlu function that are identical except that
|
||||
-- each calls the other. Each also checks if an unsafe opcode can be executed.
|
||||
|
||||
CREATE OR REPLACE FUNCTION recurse_plperl(i int) RETURNS SETOF TEXT LANGUAGE plperl
|
||||
AS $$
|
||||
my $i = shift;
|
||||
return unless $i > 0;
|
||||
return_next "plperl $i entry: ".((eval "stat;1") ? "ok" : $@);
|
||||
return_next $_
|
||||
for map { $_->{recurse_plperlu} }
|
||||
@{spi_exec_query("select * from recurse_plperlu($i-1)")->{rows}};
|
||||
return;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION recurse_plperlu(i int) RETURNS SETOF TEXT LANGUAGE plperlu
|
||||
AS $$
|
||||
my $i = shift;
|
||||
return unless $i > 0;
|
||||
return_next "plperlu $i entry: ".((eval "stat;1") ? "ok" : $@);
|
||||
return_next $_
|
||||
for map { $_->{recurse_plperl} }
|
||||
@{spi_exec_query("select * from recurse_plperl($i-1)")->{rows}};
|
||||
return;
|
||||
$$;
|
||||
|
||||
SELECT * FROM recurse_plperl(5);
|
||||
SELECT * FROM recurse_plperlu(5);
|
||||
|
||||
--
|
||||
-- Make sure we can't use/require things in plperl
|
||||
--
|
||||
|
||||
CREATE OR REPLACE FUNCTION use_plperlu() RETURNS void LANGUAGE plperlu
|
||||
AS $$
|
||||
use Errno;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
|
||||
AS $$
|
||||
use Errno;
|
||||
$$;
|
||||
|
||||
-- make sure our overloaded require op gets restored/set correctly
|
||||
select use_plperlu();
|
||||
|
||||
CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
|
||||
AS $$
|
||||
use Errno;
|
||||
$$;
|
Loading…
x
Reference in New Issue
Block a user