1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-30 11:03:19 +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:43:41 +00:00
parent ffba89a9bb
commit 68e621bfa4
8 changed files with 713 additions and 218 deletions

View File

@ -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">

View File

@ -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

View File

@ -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.

View 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

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

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

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