1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-09 17:03:00 +03:00

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:
Andrew Dunstan
2010-05-13 16:44:03 +00:00
parent 2824dd4f4f
commit e089e04d3e
10 changed files with 790 additions and 192 deletions

View File

@@ -1,5 +1,5 @@
<!--
$PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.37 2005/01/17 17:29:49 momjian Exp $
$PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.37.4.1 2010/05/13 16:44:03 adunstan Exp $
-->
<chapter id="plperl">
@@ -458,7 +458,26 @@ $$ LANGUAGE plperl;
If the above function was created by a superuser using the language
<literal>plperlu</>, execution would succeed.
</para>
</sect1>
<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">
<title>PL/Perl Triggers</title>

View File

@@ -1,5 +1,5 @@
# Makefile for PL/Perl
# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.18.4.1 2005/07/17 04:05:49 tgl Exp $
# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.18.4.2 2010/05/13 16:44:03 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
@@ -41,6 +41,13 @@ 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 $< >$@
@@ -60,7 +67,7 @@ uninstall:
rm -f $(DESTDIR)$(pkglibdir)/plperl$(DLSUFFIX)
clean distclean maintainer-clean: clean-lib
rm -f SPI.c $(OBJS)
rm -f SPI.c $(OBJS) plperl_opmask.h
else # can't build

File diff suppressed because it is too large Load Diff

View 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;

View File

@@ -0,0 +1,71 @@
CREATE OR REPLACE FUNCTION recurse_plperl(i int) RETURNS SETOF TEXT LANGUAGE plperl
AS $$
my $i = shift;
my $res = [];
return $res unless $i > 0;
push @$res, "plperl $i entry: ".((eval "stat;1") ? "ok" : $@);
push @$res, $_
for map { $_->{recurse_plperlu} }
@{spi_exec_query("select * from recurse_plperlu($i-1)")->{rows}};
return $res;
$$;
CREATE OR REPLACE FUNCTION recurse_plperlu(i int) RETURNS SETOF TEXT LANGUAGE plperlu
AS $$
my $i = shift;
my $res = [];
return $res unless $i > 0;
push @$res, "plperlu $i entry: ".((eval "stat;1") ? "ok" : $@);
push @$res, $_
for map { $_->{recurse_plperl} }
@{spi_exec_query("select * from recurse_plperl($i-1)")->{rows}};
return $res;
$$;
SELECT * FROM recurse_plperl(5);
recurse_plperl
------------------------------------------------------------------------
plperl 5 entry: 'stat' trapped by operation mask at (eval 4) line 1.
plperlu 4 entry: ok
plperl 3 entry: 'stat' trapped by operation mask at (eval 5) line 1.
plperlu 2 entry: ok
plperl 1 entry: 'stat' trapped by operation mask at (eval 6) line 1.
(5 rows)
SELECT * FROM recurse_plperlu(5);
recurse_plperlu
------------------------------------------------------------------------
plperlu 5 entry: ok
plperl 4 entry: 'stat' trapped by operation mask at (eval 7) line 1.
plperlu 3 entry: ok
plperl 2 entry: 'stat' trapped by operation mask at (eval 8) line 1.
plperlu 1 entry: ok
(5 rows)
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;
$$;
select use_plperl();
ERROR: creation of Perl function failed: Unable to load Errno.pm into plperl at (eval 9) line 2.
BEGIN failed--compilation aborted at (eval 9) line 2.
select use_plperlu();
use_plperlu
-------------
(1 row)
CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
AS $$
use Errno;
$$;
select use_plperl();
ERROR: creation of Perl function failed: Unable to load Errno.pm into plperl at (eval 10) line 2.
BEGIN failed--compilation aborted at (eval 10) line 2.

View File

@@ -0,0 +1,59 @@
--
-- 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;
my $res = [];
return $res unless $i > 0;
push @$res, "plperl $i entry: ".((eval "stat;1") ? "ok" : $@);
push @$res, $_
for map { $_->{recurse_plperlu} }
@{spi_exec_query("select * from recurse_plperlu($i-1)")->{rows}};
return $res;
$$;
CREATE OR REPLACE FUNCTION recurse_plperlu(i int) RETURNS SETOF TEXT LANGUAGE plperlu
AS $$
my $i = shift;
my $res = [];
return $res unless $i > 0;
push @$res, "plperlu $i entry: ".((eval "stat;1") ? "ok" : $@);
push @$res, $_
for map { $_->{recurse_plperl} }
@{spi_exec_query("select * from recurse_plperl($i-1)")->{rows}};
return $res;
$$;
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;
$$;
select use_plperl();
-- 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;
$$;
select use_plperl();

View File

@@ -14,6 +14,10 @@ createdb $DBNAME
echo "**** Create procedural language plperl ****"
createlang plperl $DBNAME
echo "**** Create procedural language plperlu ****"
createlang plperlu $DBNAME
echo "**** Running test queries ****"
psql -q -n -e $DBNAME <test_queries.sql > test.out 2>&1
@@ -24,3 +28,17 @@ else
echo " Tests failed - look at diffs between"
echo " test.expected and test.out"
fi
echo "**** Running plperlu_plperl tests ****"
psql -q -n -e $DBNAME <plperlu_plperl.sql > plperlu_plperl.out 2>&1
if diff plperlu_plperl.expected plperlu_plperl.out >/dev/null 2>&1 || \
diff plperlu_plperl.expected_alt plperlu_plperl.out >/dev/null 2>&1
then
echo " Tests passed O.K."
rm plperlu_plperl.out
else
echo " Tests failed - look at diffs between"
echo " plperlu_plperl.expected{_alt} and plperlu_plperl.out"
fi

View File

@@ -0,0 +1,31 @@
#!/bin/sh
DBNAME=plperl_test
export DBNAME
echo "**** Destroy old database $DBNAME ****"
dropdb $DBNAME
sleep 1
echo "**** Create test database $DBNAME ****"
createdb $DBNAME
echo "**** Create procedural language plperl ****"
createlang plperl $DBNAME
echo "**** Create procedural language plperlu ****"
createlang plperlu $DBNAME
echo "**** Running test queries ****"
psql -q -n -e $DBNAME <test_queries.sql > test.out 2>&1
if diff test.expected test.out >/dev/null 2>&1 ; then
echo " Tests passed O.K."
rm test.out
else
echo " Tests failed - look at diffs between"
echo " test.expected and test.out"
fi

View File

@@ -300,3 +300,8 @@ SELECT perl_get_field((11,12), 'z');
(1 row)
CREATE OR REPLACE FUNCTION perl_unsafe1() RETURNS void AS $$
my $fd = fileno STDERR;
$$ LANGUAGE plperl;
select perl_unsafe1();
ERROR: creation of Perl function failed: 'fileno' trapped by operation mask at (eval 26) line 2.

View File

@@ -211,3 +211,11 @@ $$ LANGUAGE plperl;
SELECT perl_get_field((11,12), 'x');
SELECT perl_get_field((11,12), 'y');
SELECT perl_get_field((11,12), 'z');
--
-- Test detection of unsafe operations
CREATE OR REPLACE FUNCTION perl_unsafe1() RETURNS void AS $$
my $fd = fileno STDERR;
$$ LANGUAGE plperl;
select perl_unsafe1();