]> granicus.if.org Git - postgresql/commitdiff
Abandon the use of Perl's Safe.pm to enforce restrictions in plperl, as it is
authorAndrew Dunstan <andrew@dunslane.net>
Thu, 13 May 2010 16:39:43 +0000 (16:39 +0000)
committerAndrew Dunstan <andrew@dunslane.net>
Thu, 13 May 2010 16:39:43 +0000 (16:39 +0000)
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

14 files changed:
doc/src/sgml/plperl.sgml
src/pl/plperl/GNUmakefile
src/pl/plperl/expected/plperl.out
src/pl/plperl/expected/plperl_init.out
src/pl/plperl/expected/plperl_plperlu.out
src/pl/plperl/plc_perlboot.pl
src/pl/plperl/plc_safe_bad.pl [deleted file]
src/pl/plperl/plc_safe_ok.pl [deleted file]
src/pl/plperl/plc_trusted.pl [new file with mode: 0644]
src/pl/plperl/plperl.c
src/pl/plperl/plperl_opmask.pl [new file with mode: 0644]
src/pl/plperl/sql/plperl.sql
src/pl/plperl/sql/plperl_init.sql
src/pl/plperl/sql/plperl_plperlu.sql

index c4129510fc1ca42c10d4b042a8a5e5d7bb2edce5..7d17002acfff6de6b3fcc15485d755b92d79aa6e 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.83 2010/04/03 07:22:55 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.84 2010/05/13 16:39:43 adunstan Exp $ -->
 
  <chapter id="plperl">
   <title>PL/Perl - Perl Procedural Language</title>
@@ -1154,8 +1154,16 @@ CREATE TRIGGER test_valid_id_trig
        into a module and loaded by the <literal>on_init</> string.
        Examples:
 <programlisting>
-plperl.on_init = '$ENV{NYTPROF}="start=no"; require Devel::NYTProf::PgPLPerl'
+plperl.on_init = 'require "plperlinit.pl"'
 plperl.on_init = 'use lib "/my/app"; use MyApp::PgInit;'
+</programlisting>
+       </para>
+       <para>
+       Any modules loaded by <literal>plperl.on_init</>, either directly or
+       indirectly, will be available for use by <literal>plperl</>.  This may
+       create a security risk. To see what modules have been loaded you can use:
+<programlisting>
+DO 'elog(WARNING, join ", ", sort keys %INC)' language plperl;
 </programlisting>
        </para>
        <para>
index e4fc226c336059f46d5a569e3cfdd9bc8d5459e8..6bbd1bfb239eccf51938bcda7ccba81d6b458d54 100644 (file)
@@ -1,5 +1,5 @@
 # Makefile for PL/Perl
-# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.43 2010/02/12 19:35:25 adunstan Exp $
+# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.44 2010/05/13 16:39:43 adunstan Exp $
 
 subdir = src/pl/plperl
 top_builddir = ../../..
@@ -36,7 +36,7 @@ NAME = plperl
 
 OBJS = plperl.o SPI.o Util.o
 
-PERLCHUNKS = plc_perlboot.pl plc_safe_bad.pl plc_safe_ok.pl
+PERLCHUNKS = plc_perlboot.pl plc_trusted.pl
 
 SHLIB_LINK = $(perl_embed_ldflags)
 
@@ -54,9 +54,12 @@ PSQLDIR = $(bindir)
 
 include $(top_srcdir)/src/Makefile.shlib
 
-plperl.o: perlchunks.h
+plperl.o: perlchunks.h plperl_opmask.h
 
-perlchunks.h: $(PERLCHUNKS)
+plperl_opmask.h: plperl_opmask.pl
+       $(PERL) $< $@
+
+perlchunks.h: $(PERLCHUNKS) 
        $(PERL) $(srcdir)/text2macro.pl --strip='^(\#.*|\s*)$$' $^ > $@
 
 all: all-lib
@@ -81,7 +84,7 @@ submake:
        $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
 
 clean distclean maintainer-clean: clean-lib
-       rm -f SPI.c Util.c $(OBJS) perlchunks.h
+       rm -f SPI.c Util.c $(OBJS) perlchunks.h plperl_opmask.h
        rm -rf results
        rm -f regression.diffs regression.out
 
index b3027f892684e9e3c258a141c308f4950eb564aa..e3e9ec7b6f82565f35c4c7e822ed3e1cf2533b94 100644 (file)
@@ -563,8 +563,23 @@ $$ LANGUAGE plperl;
 NOTICE:  This is a test
 CONTEXT:  PL/Perl anonymous code block
 -- check that restricted operations are rejected in a plperl DO block
-DO $$ eval "1+1"; $$ LANGUAGE plperl;
-ERROR:  'eval "string"' trapped by operation mask at line 1.
+DO $$ system("/nonesuch"); $$ LANGUAGE plperl;
+ERROR:  'system' trapped by operation mask at line 1.
+CONTEXT:  PL/Perl anonymous code block
+DO $$ qx("/nonesuch"); $$ LANGUAGE plperl;
+ERROR:  'quoted execution (``, qx)' trapped by operation mask at line 1.
+CONTEXT:  PL/Perl anonymous code block
+DO $$ open my $fh, "</nonesuch"; $$ LANGUAGE plperl;
+ERROR:  'open' trapped by operation mask at line 1.
+CONTEXT:  PL/Perl anonymous code block
+-- check that eval is allowed and eval'd restricted ops are caught
+DO $$ eval q{chdir '.'}; warn "Caught: $@"; $$ LANGUAGE plperl;
+WARNING:  Caught: 'chdir' trapped by operation mask at line 2.
+CONTEXT:  PL/Perl anonymous code block
+-- check that compiling do (dofile opcode) is allowed
+-- but that executing it for a file not already loaded (via require) dies
+DO $$ warn do "/dev/null"; $$ LANGUAGE plperl;
+ERROR:  Unable to load /dev/null into plperl at line 1.
 CONTEXT:  PL/Perl anonymous code block
 -- check that we can't "use" a module that's not been loaded already
 -- compile-time error: "Unable to load blib.pm into plperl"
index dca5d8f0ec660ca33ce73a67eedc969ec54df150..b335dcc6d30edfd35ff910396730669d4f3b6291 100644 (file)
@@ -1,14 +1,14 @@
 -- test plperl.on_plperl_init errors are fatal
 -- Avoid need for custom_variable_classes = 'plperl'
 LOAD 'plperl';
-SET SESSION plperl.on_plperl_init = ' eval "1+1" ';
+SET SESSION plperl.on_plperl_init = ' system("/nonesuch") ';
 SHOW plperl.on_plperl_init;
  plperl.on_plperl_init 
 -----------------------
-  eval "1+1" 
+  system("/nonesuch") 
 (1 row)
 
 DO $$ warn 42 $$ language plperl;
-ERROR:  'eval "string"' trapped by operation mask at line 2.
-CONTEXT:  while executing plperl.on_plperl_init
+ERROR:  'system' trapped by operation mask at line 2.
+CONTEXT:  While executing plperl.on_plperl_init.
 PL/Perl anonymous code block
index acc9dd4de3328a9023b3f2a6c5a3fbbbfa3883e2..479a902de438a8670de9021c2e1b813c96c19098 100644 (file)
@@ -63,3 +63,31 @@ select bar('hey');
  hey
 (1 row)
 
+--
+-- 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:  Unable to load Errno.pm into plperl at line 2.
+BEGIN failed--compilation aborted at line 2.
+CONTEXT:  compilation of PL/Perl function "use_plperl"
+-- 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:  Unable to load Errno.pm into plperl at line 2.
+BEGIN failed--compilation aborted at line 2.
+CONTEXT:  compilation of PL/Perl function "use_plperl"
index d3bb614a5d0a6783efa04d03395a9783e95a14f3..379d4bfa5b7038114593eaefb1ab848f666f4791 100644 (file)
@@ -1,5 +1,5 @@
 
-#  $PostgreSQL: pgsql/src/pl/plperl/plc_perlboot.pl,v 1.5 2010/02/16 21:39:52 adunstan Exp $
+#  $PostgreSQL: pgsql/src/pl/plperl/plc_perlboot.pl,v 1.6 2010/05/13 16:39:43 adunstan Exp $
 
 use 5.008001;
 
@@ -33,15 +33,12 @@ sub mkfuncsrc {
        } sort keys %$imports;
        $BEGIN &&= "BEGIN { $BEGIN }";
 
-       $name =~ s/\\/\\\\/g;
-       $name =~ s/::|'/_/g; # avoid package delimiters
-
-       return qq[ package main; undef *{'$name'}; *{'$name'} = sub { $BEGIN $prolog $src } ];
+       return qq[ package main; sub { $BEGIN $prolog $src } ];
 }
 
-# see also mksafefunc() in plc_safe_ok.pl
-sub mkunsafefunc {
-       no strict; # default to no strict for the eval
+sub mkfunc {
+       no strict;   # default to no strict for the eval
+       no warnings; # default to no warnings for the eval
        my $ret = eval(mkfuncsrc(@_));
        $@ =~ s/\(eval \d+\) //g if $@;
        return $ret;
diff --git a/src/pl/plperl/plc_safe_bad.pl b/src/pl/plperl/plc_safe_bad.pl
deleted file mode 100644 (file)
index 89eb11b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-
-#  $PostgreSQL: pgsql/src/pl/plperl/plc_safe_bad.pl,v 1.3 2010/01/26 23:11:56 adunstan Exp $
-
-# Minimal version of plc_safe_ok.pl
-# that's used if Safe is too old or doesn't load for any reason
-
-my $msg = 'trusted Perl functions disabled - please upgrade Perl Safe module';
-
-sub mksafefunc {
-       my ($name, $pragma, $prolog, $src) = @_;
-       # replace $src with code to generate an error
-       $src = qq{ ::elog(::ERROR,"$msg\n") };
-       my $ret = eval(::mkfuncsrc($name, $pragma, '', $src));
-       $@ =~ s/\(eval \d+\) //g if $@;
-       return $ret;
-}
diff --git a/src/pl/plperl/plc_safe_ok.pl b/src/pl/plperl/plc_safe_ok.pl
deleted file mode 100644 (file)
index b76900d..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-#  $PostgreSQL: pgsql/src/pl/plperl/plc_safe_ok.pl,v 1.5 2010/02/16 21:39:52 adunstan Exp $
-
-package PostgreSQL::InServer::safe;
-use strict;
-use warnings;
-use Safe;
-
-# @EvalInSafe    = ( [ "string to eval", "extra,opcodes,to,allow" ], ...)
-# @ShareIntoSafe = ( [ from_class => \@symbols ], ...)
-
-# these are currently declared "my" so they can't be monkeyed with using init
-# code. If we later decide to change that policy, we could change one or more
-# to make them visible by using "use vars".
-my($PLContainer,$SafeClass,@EvalInSafe,@ShareIntoSafe);
-  
-# --- configuration ---
-
-# ensure we only alter the configuration variables once to avoid any
-# problems if this code is run multiple times due to an exception generated
-# from plperl.on_trusted_init code leaving the interp_state unchanged.
-
-if (not our $_init++) {
-
-  # Load widely useful pragmas into the container to make them available.
-  # These must be trusted to not expose a way to execute a string eval
-  # or any kind of unsafe action that the untrusted code could exploit.
-  # If in ANY doubt about a module then DO NOT add it to this list.
-
-  unshift @EvalInSafe,
-      [ 'require strict',   'caller' ],
-      [ 'require Carp',     'caller,entertry'  ], # load Carp before warnings
-      [ 'require warnings', 'caller'  ];
-  push @EvalInSafe,
-      [ 'require feature' ] if $] >= 5.010000;
-
-  push @ShareIntoSafe, [
-      main => [ qw(
-          &elog &DEBUG &LOG &INFO &NOTICE &WARNING &ERROR
-          &spi_query &spi_fetchrow &spi_cursor_close &spi_exec_query
-          &spi_prepare &spi_exec_prepared &spi_query_prepared &spi_freeplan
-          &return_next &_SHARED
-          &quote_literal &quote_nullable &quote_ident
-          &encode_bytea &decode_bytea &looks_like_number
-          &encode_array_literal &encode_array_constructor
-      ) ],
-  ];
-}
-
-# --- create and initialize a new container ---
-
-$SafeClass ||= 'Safe';
-$PLContainer = $SafeClass->new('PostgreSQL::InServer::safe_container');
-
-$PLContainer->permit_only(':default');
-$PLContainer->permit(qw[:base_math !:base_io sort time require]);
-
-for my $do (@EvalInSafe) {
-  my $perform = sub { # private closure
-      my ($container, $src, $ops) = @_;
-      my $mask = $container->mask;
-      $container->permit(split /\s*,\s*/, $ops);
-      my $ok = safe_eval("$src; 1");
-      $container->mask($mask);
-      main::elog(main::ERROR(), "$src failed: $@") unless $ok;
-  };
-  
-  my $ops = $do->[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.
-  $ops = "entereval,$ops"
-      if $] < 5.011004 and $ops =~ /\bentertry\b/;
-
-  $perform->($PLContainer, $do->[0], $ops);
-}
-
-$PLContainer->share_from(@$_) for @ShareIntoSafe;
-
-
-# --- runtime interface ---
-
-# called directly for plperl.on_trusted_init and @EvalInSafe
-sub safe_eval {
-       my $ret = $PLContainer->reval(shift);
-       $@ =~ s/\(eval \d+\) //g if $@;
-       return $ret;
-}
-
-sub mksafefunc {
-!   return safe_eval(PostgreSQL::InServer::mkfuncsrc(@_));
-}
diff --git a/src/pl/plperl/plc_trusted.pl b/src/pl/plperl/plc_trusted.pl
new file mode 100644 (file)
index 0000000..a76cc2f
--- /dev/null
@@ -0,0 +1,29 @@
+
+
+#  $PostgreSQL: pgsql/src/pl/plperl/plc_trusted.pl,v 1.1 2010/05/13 16:39:43 adunstan Exp $
+
+package PostgreSQL::InServer::safe;
+# Load widely useful pragmas into plperl to make them available.
+#
+# SECURITY RISKS:
+#
+# Since these modules are free to compile unsafe opcodes they must
+# be trusted to now allow any code containing unsafe opcodes to be abused.
+# That's much harder than it sounds.
+#
+# Be aware that perl provides a wide variety of ways to subvert
+# pre-compiled code. For some examples, see this presentation:
+# http://www.slideshare.net/cdman83/barely-legal-xxx-perl-presentation
+#
+# If in ANY doubt about a module, or ANY of the modules down the chain of
+# dependencies it loads, then DO NOT add it to this list.
+#
+# To check if any of these modules use "unsafe" opcodes you can compile
+# plperl with the PLPERL_ENABLE_OPMASK_EARLY macro defined. See plperl.c
+
+require strict;
+require Carp;
+require Carp::Heavy;
+require warnings;
+require feature if $] >= 5.010000;
index 9ad2d40d114778ae27918e27e5c5f9cbb20413c4..de6ddb288fd053afa9546110fbabda894514f9a1 100644 (file)
@@ -1,7 +1,7 @@
 /**********************************************************************
  * plperl.c - perl as a procedural language for PostgreSQL
  *
- *       $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.174 2010/04/18 19:16:06 tgl Exp $
+ *       $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.175 2010/05/13 16:39:43 adunstan Exp $
  *
  **********************************************************************/
 
@@ -46,6 +46,8 @@
 
 /* string literal macros defining chunks of perl code */
 #include "perlchunks.h"
+/* defines PLPERL_SET_OPMASK */
+#include "plperl_opmask.h"
 
 PG_MODULE_MAGIC;
 
@@ -134,6 +136,7 @@ static PerlInterpreter *plperl_trusted_interp = NULL;
 static PerlInterpreter *plperl_untrusted_interp = NULL;
 static PerlInterpreter *plperl_held_interp = NULL;
 static OP  *(*pp_require_orig) (pTHX) = NULL;
+static OP  *pp_require_safe(pTHX);
 static bool trusted_context;
 static HTAB *plperl_proc_hash = NULL;
 static HTAB *plperl_query_hash = NULL;
@@ -143,6 +146,8 @@ static char *plperl_on_init = NULL;
 static char *plperl_on_plperl_init = NULL;
 static char *plperl_on_plperlu_init = NULL;
 static bool plperl_ending = false;
+static char plperl_opmask[MAXO];
+static void set_interp_require(void);
 
 /* this is saved and restored by plperl_call_handler */
 static plperl_call_data *current_call_data = NULL;
@@ -180,6 +185,9 @@ static void plperl_inline_callback(void *arg);
 static char *strip_trailing_ws(const char *msg);
 static OP  *pp_require_safe(pTHX);
 static int     restore_context(bool);
+#ifdef WIN32
+static char *setlocale_perl(int category, char *locale);
+#endif
 
 /*
  * Convert an SV to char * and verify the encoding via pg_verifymbstr()
@@ -228,7 +236,13 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
 void
 _PG_init(void)
 {
-       /* Be sure we do initialization only once (should be redundant now) */
+       /*
+        * Be sure we do initialization only once.
+        *
+        * If initialization fails due to, e.g., plperl_init_interp() throwing an
+        * exception, then we'll return here on the next usage and the user will
+        * get a rather cryptic: ERROR:  attempt to redefine parameter "plperl.use_strict"
+        */
        static bool inited = false;
        HASHCTL         hash_ctl;
 
@@ -296,6 +310,8 @@ _PG_init(void)
                                                                        &hash_ctl,
                                                                        HASH_ELEM);
 
+       PLPERL_SET_OPMASK(plperl_opmask);
+
        plperl_held_interp = plperl_init_interp();
        interp_state = INTERP_HELD;
 
@@ -303,6 +319,21 @@ _PG_init(void)
 }
 
 
+static void
+set_interp_require(void)
+{
+       if (trusted_context)
+       {
+               PL_ppaddr[OP_REQUIRE] = pp_require_safe;
+               PL_ppaddr[OP_DOFILE] = pp_require_safe;
+       }
+       else
+       {
+               PL_ppaddr[OP_REQUIRE] = pp_require_orig;
+               PL_ppaddr[OP_DOFILE] = pp_require_orig;
+       }
+}
+
 /*
  * Cleanup perl interpreters, including running END blocks.
  * Does not fully undo the actions of _PG_init() nor make it callable again.
@@ -335,9 +366,6 @@ plperl_fini(int code, Datum arg)
 }
 
 
-#define SAFE_MODULE \
-       "require Safe; $Safe::VERSION"
-
 /********************************************************************
  *
  * We start out by creating a "held" interpreter that we can use in
@@ -406,6 +434,7 @@ select_perl_context(bool trusted)
        }
        plperl_held_interp = NULL;
        trusted_context = trusted;
+       set_interp_require();
 
        /*
         * Since the timing of first use of PL/Perl can't be predicted, any
@@ -438,16 +467,12 @@ restore_context(bool trusted)
                if (trusted_context != trusted)
                {
                        if (trusted)
-                       {
                                PERL_SET_CONTEXT(plperl_trusted_interp);
-                               PL_ppaddr[OP_REQUIRE] = pp_require_safe;
-                       }
                        else
-                       {
                                PERL_SET_CONTEXT(plperl_untrusted_interp);
-                               PL_ppaddr[OP_REQUIRE] = pp_require_orig;
-                       }
+
                        trusted_context = trusted;
+                       set_interp_require();
                }
                return 1;                               /* context restored */
        }
@@ -484,7 +509,7 @@ plperl_init_interp(void)
         * subsequent calls to the interpreter don't mess with the locale
         * settings.
         *
-        * We restore them using Perl's POSIX::setlocale() function so that Perl
+        * We restore them using setlocale_perl(), defined below, so that Perl
         * doesn't have a different idea of the locale from Postgres.
         *
         */
@@ -495,7 +520,6 @@ plperl_init_interp(void)
                           *save_monetary,
                           *save_numeric,
                           *save_time;
-       char            buf[1024];
 
        loc = setlocale(LC_COLLATE, NULL);
        save_collate = loc ? pstrdup(loc) : NULL;
@@ -507,6 +531,12 @@ plperl_init_interp(void)
        save_numeric = loc ? pstrdup(loc) : NULL;
        loc = setlocale(LC_TIME, NULL);
        save_time = loc ? pstrdup(loc) : NULL;
+
+#define PLPERL_RESTORE_LOCALE(name, saved) \
+       STMT_START { \
+               if (saved != NULL) { setlocale_perl(name, saved); pfree(saved); } \
+       } STMT_END
+
 #endif
 
        if (plperl_on_init)
@@ -548,13 +578,26 @@ plperl_init_interp(void)
        PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
 
        /*
-        * Record the original function for the 'require' opcode. Ensure it's used
-        * for new interpreters.
+        * Record the original function for the 'require' and 'dofile' opcodes.
+        * (They share the same implementation.) Ensure it's used for new interpreters.
         */
        if (!pp_require_orig)
                pp_require_orig = PL_ppaddr[OP_REQUIRE];
-       else
+       else 
+       {
                PL_ppaddr[OP_REQUIRE] = pp_require_orig;
+               PL_ppaddr[OP_DOFILE]  = pp_require_orig;
+       }
+
+#ifdef PLPERL_ENABLE_OPMASK_EARLY
+       /*
+        * For regression testing to prove that the PLC_PERLBOOT and PLC_TRUSTED
+        * code doesn't even compile any unsafe ops. In future there may be a
+        * valid need for them to do so, in which case this could be softened
+        * (perhaps moved to plperl_trusted_init()) or removed.
+        */
+       PL_op_mask = plperl_opmask;
+#endif
 
        if (perl_parse(plperl, plperl_init_shared_libs,
                                   nargs, embedding, NULL) != 0)
@@ -567,45 +610,12 @@ plperl_init_interp(void)
                                (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))),
                                 errcontext("while running Perl initialization")));
 
-#ifdef WIN32
-
-       eval_pv("use POSIX qw(locale_h);", TRUE);       /* croak on failure */
-
-       if (save_collate != NULL)
-       {
-               snprintf(buf, sizeof(buf), "setlocale(%s,'%s');",
-                                "LC_COLLATE", save_collate);
-               eval_pv(buf, TRUE);
-               pfree(save_collate);
-       }
-       if (save_ctype != NULL)
-       {
-               snprintf(buf, sizeof(buf), "setlocale(%s,'%s');",
-                                "LC_CTYPE", save_ctype);
-               eval_pv(buf, TRUE);
-               pfree(save_ctype);
-       }
-       if (save_monetary != NULL)
-       {
-               snprintf(buf, sizeof(buf), "setlocale(%s,'%s');",
-                                "LC_MONETARY", save_monetary);
-               eval_pv(buf, TRUE);
-               pfree(save_monetary);
-       }
-       if (save_numeric != NULL)
-       {
-               snprintf(buf, sizeof(buf), "setlocale(%s,'%s');",
-                                "LC_NUMERIC", save_numeric);
-               eval_pv(buf, TRUE);
-               pfree(save_numeric);
-       }
-       if (save_time != NULL)
-       {
-               snprintf(buf, sizeof(buf), "setlocale(%s,'%s');",
-                                "LC_TIME", save_time);
-               eval_pv(buf, TRUE);
-               pfree(save_time);
-       }
+#ifdef PLPERL_RESTORE_LOCALE
+       PLPERL_RESTORE_LOCALE(LC_COLLATE,  save_collate);
+       PLPERL_RESTORE_LOCALE(LC_CTYPE,    save_ctype);
+       PLPERL_RESTORE_LOCALE(LC_MONETARY, save_monetary);
+       PLPERL_RESTORE_LOCALE(LC_NUMERIC,  save_numeric);
+       PLPERL_RESTORE_LOCALE(LC_TIME,     save_time);
 #endif
 
        return plperl;
@@ -683,70 +693,76 @@ plperl_destroy_interp(PerlInterpreter **interp)
 static void
 plperl_trusted_init(void)
 {
-       SV                 *safe_version_sv;
-       IV                      safe_version_x100;
-
-       safe_version_sv = eval_pv(SAFE_MODULE, FALSE);          /* TRUE = croak if
-                                                                                                                * failure */
-       safe_version_x100 = (int) (SvNV(safe_version_sv) * 100);
-
-       /*
-        * Reject too-old versions of Safe and some others: 2.20:
-        * http://rt.perl.org/rt3/Ticket/Display.html?id=72068 2.21:
-        * http://rt.perl.org/rt3/Ticket/Display.html?id=72700
-        */
-       if (safe_version_x100 < 209 || safe_version_x100 == 220 ||
-               safe_version_x100 == 221)
+       HV         *stash;
+       SV         *sv;
+       char       *key;
+       I32         klen;
+       
+       /* use original require while we set up */
+       PL_ppaddr[OP_REQUIRE] = pp_require_orig;
+       PL_ppaddr[OP_DOFILE] = pp_require_orig;
+       eval_pv(PLC_TRUSTED, FALSE);
+       if (SvTRUE(ERRSV))
+               ereport(ERROR,
+                               (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))),
+                                errcontext("While executing PLC_TRUSTED.")));
+       
+       if (GetDatabaseEncoding() == PG_UTF8)
        {
-               /* not safe, so disallow all trusted funcs */
-               eval_pv(PLC_SAFE_BAD, FALSE);
+               /*
+                * Force loading of utf8 module now to prevent errors that can
+                * arise from the regex code later trying to load utf8 modules.
+                * See http://rt.perl.org/rt3/Ticket/Display.html?id=47576
+                */
+               eval_pv("my $a=chr(0x100); return $a =~ /\\xa9/i", FALSE);
                if (SvTRUE(ERRSV))
                        ereport(ERROR,
                                        (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))),
-                                        errcontext("while executing PLC_SAFE_BAD")));
+                                        errcontext("While executing utf8fix.")));
        }
-       else
+       
+       /*
+        * Lock down the interpreter
+        */
+       
+       /* switch to the safe require/dofile opcode for future code */
+       PL_ppaddr[OP_REQUIRE] = pp_require_safe;
+       PL_ppaddr[OP_DOFILE]  = pp_require_safe;
+       
+       /* 
+        * prevent (any more) unsafe opcodes being compiled 
+        * PL_op_mask is per interpreter, so this only needs to be set once 
+        */
+       PL_op_mask = plperl_opmask;
+       
+       /* delete the DynaLoader:: namespace so extensions can't be loaded */
+       stash = gv_stashpv("DynaLoader", GV_ADDWARN);
+       hv_iterinit(stash);
+       while ((sv = hv_iternextsv(stash, &key, &klen))) 
        {
-               eval_pv(PLC_SAFE_OK, FALSE);
+               if (!isGV_with_GP(sv) || !GvCV(sv))
+                       continue;
+               SvREFCNT_dec(GvCV(sv)); /* free the CV */
+               GvCV(sv) = NULL;        /* prevent call via GV */
+       }
+       hv_clear(stash);
+       
+       /* invalidate assorted caches */
+       ++PL_sub_generation;
+       hv_clear(PL_stashcache);
+       
+       /*
+        * Execute plperl.on_plperl_init in the locked-down interpreter
+        */
+       if (plperl_on_plperl_init && *plperl_on_plperl_init)
+       {
+               eval_pv(plperl_on_plperl_init, FALSE);
                if (SvTRUE(ERRSV))
                        ereport(ERROR,
                                        (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))),
-                                        errcontext("while executing PLC_SAFE_OK")));
-
-               if (GetDatabaseEncoding() == PG_UTF8)
-               {
-                       /*
-                        * Force loading of utf8 module now to prevent errors that can
-                        * arise from the regex code later trying to load utf8 modules.
-                        * See http://rt.perl.org/rt3/Ticket/Display.html?id=47576
-                        */
-                       eval_pv("my $a=chr(0x100); return $a =~ /\\xa9/i", FALSE);
-                       if (SvTRUE(ERRSV))
-                               ereport(ERROR,
-                                               (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))),
-                                                errcontext("while executing utf8fix")));
-               }
-
-               /* switch to the safe require opcode */
-               PL_ppaddr[OP_REQUIRE] = pp_require_safe;
-
-               if (plperl_on_plperl_init && *plperl_on_plperl_init)
-               {
-                       dSP;
-
-                       PUSHMARK(SP);
-                       XPUSHs(sv_2mortal(newSVstring(plperl_on_plperl_init)));
-                       PUTBACK;
-
-                       call_pv("PostgreSQL::InServer::safe::safe_eval", G_VOID);
-                       SPAGAIN;
-
-                       if (SvTRUE(ERRSV))
-                               ereport(ERROR,
-                                               (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))),
-                                         errcontext("while executing plperl.on_plperl_init")));
-               }
-
+                                        errcontext("While executing plperl.on_plperl_init.")));
+               
        }
 }
 
@@ -1250,12 +1266,10 @@ static void
 plperl_create_sub(plperl_proc_desc *prodesc, char *s, Oid fn_oid)
 {
        dSP;
-       bool            trusted = prodesc->lanpltrusted;
        char            subname[NAMEDATALEN + 40];
        HV                 *pragma_hv = newHV();
        SV                 *subref = NULL;
        int                     count;
-       char       *compile_sub;
 
        sprintf(subname, "%s__%u", prodesc->proname, fn_oid);
 
@@ -1277,22 +1291,17 @@ plperl_create_sub(plperl_proc_desc *prodesc, char *s, Oid fn_oid)
         * errors properly.  Perhaps it's because there's another level of eval
         * inside mksafefunc?
         */
-       compile_sub = (trusted)
-               ? "PostgreSQL::InServer::safe::mksafefunc"
-               : "PostgreSQL::InServer::mkunsafefunc";
-       count = perl_call_pv(compile_sub, G_SCALAR | G_EVAL | G_KEEPERR);
+       count = perl_call_pv("PostgreSQL::InServer::mkfunc",
+                                                G_SCALAR | G_EVAL | G_KEEPERR);
        SPAGAIN;
 
        if (count == 1)
        {
-               GV                 *sub_glob = (GV *) POPs;
+               SV                 *sub_rv = (SV *) POPs;
 
-               if (sub_glob && SvTYPE(sub_glob) == SVt_PVGV)
+               if (sub_rv && SvROK(sub_rv) && SvTYPE(SvRV(sub_rv)) == SVt_PVCV)
                {
-                       SV                 *sv = (SV *) GvCVu((GV *) sub_glob);
-
-                       if (sv)
-                               subref = newRV_inc(sv);
+                       subref = newRV_inc(SvRV(sub_rv));
                }
        }
 
@@ -1307,22 +1316,21 @@ plperl_create_sub(plperl_proc_desc *prodesc, char *s, Oid fn_oid)
 
        if (!subref)
                ereport(ERROR,
-                               (errmsg("did not get a GLOB from compiling function \"%s\" via %s",
-                                               prodesc->proname, compile_sub)));
-
-       prodesc->reference = newSVsv(subref);
-
+                               (errmsg("didn't get a CODE ref from compiling %s",
+                                               prodesc->proname)));
+       
+       /* give the subroutine a proper name in the main:: symbol table */
+       CvGV(SvRV(subref)) = (GV *) newSV(0);
+       gv_init(CvGV(SvRV(subref)), PL_defstash, subname, strlen(subname), TRUE);
+       
+       prodesc->reference = subref;
+       
        return;
 }
 
 
 /**********************************************************************
  * plperl_init_shared_libs()           -
- *
- * We cannot use the DynaLoader directly to get at the Opcode
- * module (used by Safe.pm). So, we link Opcode into ourselves
- * and do the initialization behind perl's back.
- *
  **********************************************************************/
 
 static void
@@ -3041,3 +3049,72 @@ plperl_inline_callback(void *arg)
 {
        errcontext("PL/Perl anonymous code block");
 }
+
+
+/*
+ * Perl's own setlocal() copied from POSIX.xs
+ * (needed because of the calls to new_*())
+ */
+#ifdef WIN32
+static char *
+setlocale_perl(int category, char *locale)
+{
+    char *RETVAL = setlocale(category, locale);
+    if (RETVAL) {
+#ifdef USE_LOCALE_CTYPE
+        if (category == LC_CTYPE
+#ifdef LC_ALL
+            || category == LC_ALL
+#endif
+            )
+        {
+            char *newctype;
+#ifdef LC_ALL
+            if (category == LC_ALL)
+                newctype = setlocale(LC_CTYPE, NULL);
+            else
+#endif
+                newctype = RETVAL;
+            new_ctype(newctype);
+        }
+#endif /* USE_LOCALE_CTYPE */
+#ifdef USE_LOCALE_COLLATE
+        if (category == LC_COLLATE
+#ifdef LC_ALL
+            || category == LC_ALL
+#endif
+            )
+        {
+            char *newcoll;
+#ifdef LC_ALL
+            if (category == LC_ALL)
+                newcoll = setlocale(LC_COLLATE, NULL);
+            else
+#endif
+                newcoll = RETVAL;
+            new_collate(newcoll);
+        }
+#endif /* USE_LOCALE_COLLATE */
+
+#ifdef USE_LOCALE_NUMERIC
+        if (category == LC_NUMERIC
+#ifdef LC_ALL
+            || category == LC_ALL
+#endif
+            )
+        {
+            char *newnum;
+#ifdef LC_ALL
+            if (category == LC_ALL)
+                newnum = setlocale(LC_NUMERIC, NULL);
+            else
+#endif
+                newnum = RETVAL;
+            new_numeric(newnum);
+        }
+#endif /* USE_LOCALE_NUMERIC */
+    }
+
+    return RETVAL;
+}
+#endif
diff --git a/src/pl/plperl/plperl_opmask.pl b/src/pl/plperl/plperl_opmask.pl
new file mode 100644 (file)
index 0000000..3e9ecaa
--- /dev/null
@@ -0,0 +1,58 @@
+#!perl -w
+
+use strict;
+use warnings;
+
+use Opcode qw(opset opset_to_ops opdesc);
+
+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],
+       # 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
+       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;
index 6d4c5c2a85448d10cd2c570d2092e052b046d93a..651d5ee2b413689f5ffdd93a78a158c5efa5bfae 100644 (file)
@@ -368,7 +368,16 @@ DO $$
 $$ LANGUAGE plperl;
 
 -- check that restricted operations are rejected in a plperl DO block
-DO $$ eval "1+1"; $$ LANGUAGE plperl;
+DO $$ system("/nonesuch"); $$ LANGUAGE plperl;
+DO $$ qx("/nonesuch"); $$ LANGUAGE plperl;
+DO $$ open my $fh, "</nonesuch"; $$ LANGUAGE plperl;
+
+-- check that eval is allowed and eval'd restricted ops are caught
+DO $$ eval q{chdir '.'}; warn "Caught: $@"; $$ LANGUAGE plperl;
+
+-- check that compiling do (dofile opcode) is allowed
+-- but that executing it for a file not already loaded (via require) dies
+DO $$ warn do "/dev/null"; $$ LANGUAGE plperl;
 
 -- check that we can't "use" a module that's not been loaded already
 -- compile-time error: "Unable to load blib.pm into plperl"
index 69b12e9d25f6a8862435ec844ff8e19489036faf..f6a32b9bae4792ab449ef62cdb717384adca6bd8 100644 (file)
@@ -3,7 +3,7 @@
 -- Avoid need for custom_variable_classes = 'plperl'
 LOAD 'plperl';
 
-SET SESSION plperl.on_plperl_init = ' eval "1+1" ';
+SET SESSION plperl.on_plperl_init = ' system("/nonesuch") ';
 
 SHOW plperl.on_plperl_init;
 
index cbc5080fa63d3074c2e8ad79390449dd8a83b537..65281c2df91bdd93295235334643117ccf824932 100644 (file)
@@ -35,3 +35,24 @@ select bar('hey');
 create or replace function bar(text) returns text language plperlu as 'shift';
 select bar('hey');
 
+--
+-- 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;
+$$;