Commit 50595b5f authored by Tom Lane's avatar Tom Lane

Use a separate interpreter for each calling SQL userid in plperl and pltcl.

There are numerous methods by which a Perl or Tcl function can subvert
the behavior of another such function executed later; for example, by
redefining standard functions or operators called by the target function.
If the target function is SECURITY DEFINER, or is called by such a
function, this means that any ordinary SQL user with Perl or Tcl language
usage rights can do essentially anything with the privileges of the target
function's owner.

To close this security hole, create a separate Perl or Tcl interpreter for
each SQL userid under which plperl or pltcl functions are executed within
a session.  However, all plperlu or pltclu functions run within a session
still share a single interpreter, since they all execute at the trust
level of a database superuser anyway.

Note: this change results in a functionality loss when libperl has been
built without the "multiplicity" option: it's no longer possible to call
plperl functions under different userids in one session, since such a
libperl can't support multiple interpreters in one process.  However, such
a libperl already failed to support concurrent use of plperl and plperlu,
so it's likely that few people use such versions with Postgres.

Security: CVE-2010-3433
parent 1f0eb5de
...@@ -167,6 +167,11 @@ su - postgres ...@@ -167,6 +167,11 @@ su - postgres
recent <productname>Perl</productname> versions, but it was not recent <productname>Perl</productname> versions, but it was not
in earlier versions, and in any case it is the choice of whomever in earlier versions, and in any case it is the choice of whomever
installed Perl at your site. installed Perl at your site.
If you intend to make more than incidental use of
<application>PL/Perl</application>, you should ensure that the
<productname>Perl</productname> installation was built with the
<literal>usemultiplicity</> option enabled (<literal>perl -V</>
will show whether this is the case).
</para> </para>
<para> <para>
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<para> <para>
Users of source packages must specially enable the build of Users of source packages must specially enable the build of
PL/Perl during the installation process. (Refer to <xref PL/Perl during the installation process. (Refer to <xref
linkend="install-short"> for more information.) Users of linkend="installation"> for more information.) Users of
binary packages might find PL/Perl in a separate subpackage. binary packages might find PL/Perl in a separate subpackage.
</para> </para>
</note> </note>
...@@ -101,7 +101,7 @@ $$ LANGUAGE plperl; ...@@ -101,7 +101,7 @@ $$ LANGUAGE plperl;
most convenient to use dollar quoting (see <xref most convenient to use dollar quoting (see <xref
linkend="sql-syntax-dollar-quoting">) for the string constant. linkend="sql-syntax-dollar-quoting">) for the string constant.
If you choose to use escape string syntax <literal>E''</>, If you choose to use escape string syntax <literal>E''</>,
you must double the single quote marks (<literal>'</>) and backslashes you must double any single quote marks (<literal>'</>) and backslashes
(<literal>\</>) used in the body of the function (<literal>\</>) used in the body of the function
(see <xref linkend="sql-syntax-strings">). (see <xref linkend="sql-syntax-strings">).
</para> </para>
...@@ -829,10 +829,20 @@ $$ LANGUAGE plperl; ...@@ -829,10 +829,20 @@ $$ LANGUAGE plperl;
</para> </para>
<para> <para>
The <varname>%_SHARED</varname> variable and other global state within For security reasons, PL/Perl executes functions called by any one SQL role
the language are public data, available to all PL/Perl functions within a in a separate Perl interpreter for that role. This prevents accidental or
session. Use with care, especially in situations that involve use of malicious interference by one user with the behavior of another user's
multiple roles or <literal>SECURITY DEFINER</> functions. PL/Perl functions. Each such interpreter has its own value of the
<varname>%_SHARED</varname> variable and other global state. Thus, two
PL/Perl functions will share the same value of <varname>%_SHARED</varname>
if and only if they are executed by the same SQL role. In an application
wherein a single session executes code under multiple SQL roles (via
<literal>SECURITY DEFINER</> functions, use of <command>SET ROLE</>, etc)
you may need to take explicit steps to ensure that PL/Perl functions can
share data via <varname>%_SHARED</varname>. To do that, make sure that
functions that should communicate are owned by the same user, and mark
them <literal>SECURITY DEFINER</>. You must of course take care that
such functions can't be used to do anything unintended.
</para> </para>
</sect1> </sect1>
...@@ -909,20 +919,29 @@ $$ LANGUAGE plperl; ...@@ -909,20 +919,29 @@ $$ LANGUAGE plperl;
<note> <note>
<para> <para>
For security reasons, to stop a leak of privileged operations from While <application>PL/Perl</> functions run in a separate Perl
<application>PL/PerlU</> to <application>PL/Perl</>, these two languages interpreter for each SQL role, all <application>PL/PerlU</> functions
have to run in separate instances of the Perl interpreter. If your executed in a given session run in a single Perl interpreter (which is
Perl installation has been appropriately compiled, this is not a problem. not any of the ones used for <application>PL/Perl</> functions).
However, not all installations are compiled with the requisite flags. This allows <application>PL/PerlU</> functions to share data freely,
If <productname>PostgreSQL</> detects that this is the case then it will but no communication can occur between <application>PL/Perl</> and
not start a second interpreter, but instead create an error. In <application>PL/PerlU</> functions.
consequence, in such an installation, you cannot use both </para>
<application>PL/PerlU</> and <application>PL/Perl</> in the same backend </note>
process. The remedy for this is to obtain a Perl installation configured
with the appropriate flags, namely either <literal>usemultiplicity</> <note>
or <literal>useithreads</>. <literal>usemultiplicity</> is preferred <para>
unless you actually need to use threads. For more details, see the Perl cannot support multiple interpreters within one process unless
<citerefentry><refentrytitle>perlembed</></citerefentry> man page. it was built with the appropriate flags, namely either
<literal>usemultiplicity</> or <literal>useithreads</>.
(<literal>usemultiplicity</> is preferred unless you actually need
to use threads. For more details, see the
<citerefentry><refentrytitle>perlembed</></citerefentry> man page.)
If <application>PL/Perl</> is used with a copy of Perl that was not built
this way, then it is only possible to have one Perl interpreter per
session, and so any one session can only execute either
<application>PL/PerlU</> functions, or <application>PL/Perl</> functions
that are all called by the same SQL role.
</para> </para>
</note> </note>
...@@ -1137,12 +1156,13 @@ CREATE TRIGGER test_valid_id_trig ...@@ -1137,12 +1156,13 @@ CREATE TRIGGER test_valid_id_trig
</indexterm> </indexterm>
<listitem> <listitem>
<para> <para>
Specifies Perl code to be executed when a Perl interpreter is first initialized Specifies Perl code to be executed when a Perl interpreter is first
and before it is specialized for use by <literal>plperl</> or <literal>plperlu</>. initialized, before it is specialized for use by <literal>plperl</> or
<literal>plperlu</>.
The SPI functions are not available when this code is executed. The SPI functions are not available when this code is executed.
If the code fails with an error it will abort the initialization of the interpreter If the code fails with an error it will abort the initialization of
and propagate out to the calling query, causing the current transaction the interpreter and propagate out to the calling query, causing the
or subtransaction to be aborted. current transaction or subtransaction to be aborted.
</para> </para>
<para> <para>
The Perl code is limited to a single string. Longer code can be placed The Perl code is limited to a single string. Longer code can be placed
...@@ -1162,9 +1182,21 @@ DO 'elog(WARNING, join ", ", sort keys %INC)' language plperl; ...@@ -1162,9 +1182,21 @@ DO 'elog(WARNING, join ", ", sort keys %INC)' language plperl;
</programlisting> </programlisting>
</para> </para>
<para> <para>
Initialization will happen in the postmaster if the plperl library is included Initialization will happen in the postmaster if the plperl library is
in <literal>shared_preload_libraries</> (see <xref linkend="guc-shared-preload-libraries">), included in <xref linkend="guc-shared-preload-libraries">, in which
in which case extra consideration should be given to the risk of destabilizing the postmaster. case extra consideration should be given to the risk of destabilizing
the postmaster. The principal reason for making use of this feature
is that Perl modules loaded by <literal>plperl.on_init</> need be
loaded only at postmaster start, and will be instantly available
without loading overhead in individual database sessions. However,
keep in mind that the overhead is avoided only for the first Perl
interpreter used by a database session &mdash; either PL/PerlU, or
PL/Perl for the first SQL role that calls a PL/Perl function. Any
additional Perl interpreters created in a database session will have
to execute <literal>plperl.on_init</> afresh. Also, on Windows there
will be no savings whatsoever from preloading, since the Perl
interpreter created in the postmaster process does not propagate to
child processes.
</para> </para>
<para> <para>
This parameter can only be set in the postgresql.conf file or on the server command line. This parameter can only be set in the postgresql.conf file or on the server command line.
...@@ -1183,41 +1215,30 @@ DO 'elog(WARNING, join ", ", sort keys %INC)' language plperl; ...@@ -1183,41 +1215,30 @@ DO 'elog(WARNING, join ", ", sort keys %INC)' language plperl;
</indexterm> </indexterm>
<listitem> <listitem>
<para> <para>
These parameters specify Perl code to be executed when the These parameters specify Perl code to be executed when a Perl
<literal>plperl</>, or <literal>plperlu</> language is first used in a interpreter is specialized for <literal>plperl</> or
session. Changes to these parameters after the corresponding language <literal>plperlu</> respectively. This will happen when a PL/Perl or
has been used will have no effect. PL/PerlU function is first executed in a database session, or when
an additional interpreter has to be created because the other language
is called or a PL/Perl function is called by a new SQL role. This
follows any initialization done by <literal>plperl.on_init</>.
The SPI functions are not available when this code is executed. The SPI functions are not available when this code is executed.
Only superusers can change these settings. The Perl code in <literal>plperl.on_plperl_init</> is executed after
The Perl code in <literal>plperl.on_plperl_init</> can only perform trusted operations. <quote>locking down</> the interpreter, and thus it can only perform
</para> trusted operations.
<para>
The effect of setting these parameters is very similar to executing a
<literal>DO</> command with the Perl code before any other use of the
language. The parameters are useful when you want to execute the Perl
code automatically on every connection, or when a connection is not
interactive. The parameters can be used by non-superusers by having a
superuser execute an <literal>ALTER USER ... SET ...</> command.
For example:
<programlisting>
ALTER USER joe SET plperl.on_plperl_init = '$_SHARED{debug} = 1';
</programlisting>
</para> </para>
<para> <para>
If the code fails with an error it will abort the initialization and If the code fails with an error it will abort the initialization and
propagate out to the calling query, causing the current transaction or propagate out to the calling query, causing the current transaction or
subtransaction to be aborted. Any changes within Perl won't be undone. subtransaction to be aborted. Any actions already done within Perl
If the language is used again the initialization will be repeated. won't be undone; however, that interpreter won't be used again.
If the language is used again the initialization will be attempted
again within a fresh Perl interpreter.
</para> </para>
<para> <para>
The difference between these two settings and the Only superusers can change these settings. Although these settings
<literal>plperl.on_init</> setting is that these can be used for can be changed within a session, such changes will not affect Perl
settings specific to the trusted or untrusted language variant, such interpreters that have already been used to execute functions.
as setting values in the <varname>%_SHARED</> variable. By contrast,
<literal>plperl.on_init</> is more useful for doing things like
setting the library search path for <productname>Perl</> or
loading Perl modules that don't interact directly with
<productname>PostgreSQL</>.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
...@@ -1229,8 +1250,9 @@ ALTER USER joe SET plperl.on_plperl_init = '$_SHARED{debug} = 1'; ...@@ -1229,8 +1250,9 @@ ALTER USER joe SET plperl.on_plperl_init = '$_SHARED{debug} = 1';
</indexterm> </indexterm>
<listitem> <listitem>
<para> <para>
When set true subsequent compilations of PL/Perl functions have the <literal>strict</> pragma enabled. When set true subsequent compilations of PL/Perl functions will have
This parameter does not affect functions already compiled in the current session. the <literal>strict</> pragma enabled. This parameter does not affect
functions already compiled in the current session.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -214,14 +214,36 @@ $$ LANGUAGE pltcl; ...@@ -214,14 +214,36 @@ $$ LANGUAGE pltcl;
Sometimes it Sometimes it
is useful to have some global data that is held between two is useful to have some global data that is held between two
calls to a function or is shared between different functions. calls to a function or is shared between different functions.
This is easily done since This is easily done in PL/Tcl, but there are some restrictions that
all PL/Tcl functions executed in one session share the same must be understood.
safe Tcl interpreter. So, any global Tcl variable is accessible to
all PL/Tcl function calls and will persist for the duration of the
SQL session. (Note that <application>PL/TclU</> functions likewise share
global data, but they are in a different Tcl interpreter and cannot
communicate with PL/Tcl functions.)
</para> </para>
<para>
For security reasons, PL/Tcl executes functions called by any one SQL
role in a separate Tcl interpreter for that role. This prevents
accidental or malicious interference by one user with the behavior of
another user's PL/Tcl functions. Each such interpreter will have its own
values for any <quote>global</> Tcl variables. Thus, two PL/Tcl
functions will share the same global variables if and only if they are
executed by the same SQL role. In an application wherein a single
session executes code under multiple SQL roles (via <literal>SECURITY
DEFINER</> functions, use of <command>SET ROLE</>, etc) you may need to
take explicit steps to ensure that PL/Tcl functions can share data. To
do that, make sure that functions that should communicate are owned by
the same user, and mark them <literal>SECURITY DEFINER</>. You must of
course take care that such functions can't be used to do anything
unintended.
</para>
<para>
All PL/TclU functions used in a session execute in the same Tcl
interpreter, which of course is distinct from the interpreter(s)
used for PL/Tcl functions. So global data is automatically shared
between PL/TclU functions. This is not considered a security risk
because all PL/TclU functions execute at the same trust level,
namely that of a database superuser.
</para>
<para> <para>
To help protect PL/Tcl functions from unintentionally interfering To help protect PL/Tcl functions from unintentionally interfering
with each other, a global with each other, a global
...@@ -231,7 +253,9 @@ $$ LANGUAGE pltcl; ...@@ -231,7 +253,9 @@ $$ LANGUAGE pltcl;
<literal>GD</> be used <literal>GD</> be used
for persistent private data of a function. Use regular Tcl global for persistent private data of a function. Use regular Tcl global
variables only for values that you specifically intend to be shared among variables only for values that you specifically intend to be shared among
multiple functions. multiple functions. (Note that the <literal>GD</> arrays are only
global within a particular interpreter, so they do not bypass the
security restrictions mentioned above.)
</para> </para>
<para> <para>
...@@ -691,8 +715,8 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab ...@@ -691,8 +715,8 @@ CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
exists, the module <literal>unknown</> is fetched from the table exists, the module <literal>unknown</> is fetched from the table
and loaded into the Tcl interpreter immediately before the first and loaded into the Tcl interpreter immediately before the first
execution of a PL/Tcl function in a database session. (This execution of a PL/Tcl function in a database session. (This
happens separately for PL/Tcl and PL/TclU, if both are used, happens separately for each Tcl interpreter, if more than one is
because separate interpreters are used for the two languages.) used in a session; see <xref linkend="pltcl-global">.)
</para> </para>
<para> <para>
While the <literal>unknown</> module could actually contain any While the <literal>unknown</> module could actually contain any
......
...@@ -37,6 +37,43 @@ ...@@ -37,6 +37,43 @@
<itemizedlist> <itemizedlist>
<listitem>
<para>
Use a separate interpreter for each calling SQL userid in PL/Perl and
PL/Tcl (Tom Lane)
</para>
<para>
This change prevents security problems that can be caused by subverting
Perl or Tcl code that will be executed later in the same session under
another SQL user identity (for example, within a <literal>SECURITY
DEFINER</> function). Most scripting languages offer numerous ways that
that might be done, such as redefining standard functions or operators
called by the target function. Without this change, any SQL user with
Perl or Tcl language usage rights can do essentially anything with the
SQL privileges of the target function's owner.
</para>
<para>
The cost of this change is that intentional communication among Perl
and Tcl functions becomes more difficult. To provide an escape hatch,
PL/PerlU and PL/TclU functions continue to use only one interpreter
per session. This is not considered a security issue since all such
functions execute at the trust level of a database superuser already.
</para>
<para>
It is likely that third-party procedural languages that claim to offer
trusted execution have similar security issues. We advise contacting
the authors of any PL you are depending on for security-critical
purposes.
</para>
<para>
Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433).
</para>
</listitem>
<listitem> <listitem>
<para> <para>
Prevent possible crashes in <function>pg_get_expr()</> by disallowing Prevent possible crashes in <function>pg_get_expr()</> by disallowing
......
...@@ -37,6 +37,43 @@ ...@@ -37,6 +37,43 @@
<itemizedlist> <itemizedlist>
<listitem>
<para>
Use a separate interpreter for each calling SQL userid in PL/Perl and
PL/Tcl (Tom Lane)
</para>
<para>
This change prevents security problems that can be caused by subverting
Perl or Tcl code that will be executed later in the same session under
another SQL user identity (for example, within a <literal>SECURITY
DEFINER</> function). Most scripting languages offer numerous ways that
that might be done, such as redefining standard functions or operators
called by the target function. Without this change, any SQL user with
Perl or Tcl language usage rights can do essentially anything with the
SQL privileges of the target function's owner.
</para>
<para>
The cost of this change is that intentional communication among Perl
and Tcl functions becomes more difficult. To provide an escape hatch,
PL/PerlU and PL/TclU functions continue to use only one interpreter
per session. This is not considered a security issue since all such
functions execute at the trust level of a database superuser already.
</para>
<para>
It is likely that third-party procedural languages that claim to offer
trusted execution have similar security issues. We advise contacting
the authors of any PL you are depending on for security-critical
purposes.
</para>
<para>
Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433).
</para>
</listitem>
<listitem> <listitem>
<para> <para>
Prevent possible crashes in <function>pg_get_expr()</> by disallowing Prevent possible crashes in <function>pg_get_expr()</> by disallowing
......
...@@ -37,6 +37,43 @@ ...@@ -37,6 +37,43 @@
<itemizedlist> <itemizedlist>
<listitem>
<para>
Use a separate interpreter for each calling SQL userid in PL/Perl and
PL/Tcl (Tom Lane)
</para>
<para>
This change prevents security problems that can be caused by subverting
Perl or Tcl code that will be executed later in the same session under
another SQL user identity (for example, within a <literal>SECURITY
DEFINER</> function). Most scripting languages offer numerous ways that
that might be done, such as redefining standard functions or operators
called by the target function. Without this change, any SQL user with
Perl or Tcl language usage rights can do essentially anything with the
SQL privileges of the target function's owner.
</para>
<para>
The cost of this change is that intentional communication among Perl
and Tcl functions becomes more difficult. To provide an escape hatch,
PL/PerlU and PL/TclU functions continue to use only one interpreter
per session. This is not considered a security issue since all such
functions execute at the trust level of a database superuser already.
</para>
<para>
It is likely that third-party procedural languages that claim to offer
trusted execution have similar security issues. We advise contacting
the authors of any PL you are depending on for security-critical
purposes.
</para>
<para>
Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433).
</para>
</listitem>
<listitem> <listitem>
<para> <para>
Prevent possible crashes in <function>pg_get_expr()</> by disallowing Prevent possible crashes in <function>pg_get_expr()</> by disallowing
......
...@@ -31,6 +31,43 @@ ...@@ -31,6 +31,43 @@
<itemizedlist> <itemizedlist>
<listitem>
<para>
Use a separate interpreter for each calling SQL userid in PL/Perl and
PL/Tcl (Tom Lane)
</para>
<para>
This change prevents security problems that can be caused by subverting
Perl or Tcl code that will be executed later in the same session under
another SQL user identity (for example, within a <literal>SECURITY
DEFINER</> function). Most scripting languages offer numerous ways that
that might be done, such as redefining standard functions or operators
called by the target function. Without this change, any SQL user with
Perl or Tcl language usage rights can do essentially anything with the
SQL privileges of the target function's owner.
</para>
<para>
The cost of this change is that intentional communication among Perl
and Tcl functions becomes more difficult. To provide an escape hatch,
PL/PerlU and PL/TclU functions continue to use only one interpreter
per session. This is not considered a security issue since all such
functions execute at the trust level of a database superuser already.
</para>
<para>
It is likely that third-party procedural languages that claim to offer
trusted execution have similar security issues. We advise contacting
the authors of any PL you are depending on for security-critical
purposes.
</para>
<para>
Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433).
</para>
</listitem>
<listitem> <listitem>
<para> <para>
Prevent possible crashes in <function>pg_get_expr()</> by disallowing Prevent possible crashes in <function>pg_get_expr()</> by disallowing
......
...@@ -31,6 +31,43 @@ ...@@ -31,6 +31,43 @@
<itemizedlist> <itemizedlist>
<listitem>
<para>
Use a separate interpreter for each calling SQL userid in PL/Perl and
PL/Tcl (Tom Lane)
</para>
<para>
This change prevents security problems that can be caused by subverting
Perl or Tcl code that will be executed later in the same session under
another SQL user identity (for example, within a <literal>SECURITY
DEFINER</> function). Most scripting languages offer numerous ways that
that might be done, such as redefining standard functions or operators
called by the target function. Without this change, any SQL user with
Perl or Tcl language usage rights can do essentially anything with the
SQL privileges of the target function's owner.
</para>
<para>
The cost of this change is that intentional communication among Perl
and Tcl functions becomes more difficult. To provide an escape hatch,
PL/PerlU and PL/TclU functions continue to use only one interpreter
per session. This is not considered a security issue since all such
functions execute at the trust level of a database superuser already.
</para>
<para>
It is likely that third-party procedural languages that claim to offer
trusted execution have similar security issues. We advise contacting
the authors of any PL you are depending on for security-critical
purposes.
</para>
<para>
Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433).
</para>
</listitem>
<listitem> <listitem>
<para> <para>
Prevent possible crashes in <function>pg_get_expr()</> by disallowing Prevent possible crashes in <function>pg_get_expr()</> by disallowing
......
...@@ -31,6 +31,43 @@ ...@@ -31,6 +31,43 @@
<itemizedlist> <itemizedlist>
<listitem>
<para>
Use a separate interpreter for each calling SQL userid in PL/Perl and
PL/Tcl (Tom Lane)
</para>
<para>
This change prevents security problems that can be caused by subverting
Perl or Tcl code that will be executed later in the same session under
another SQL user identity (for example, within a <literal>SECURITY
DEFINER</> function). Most scripting languages offer numerous ways that
that might be done, such as redefining standard functions or operators
called by the target function. Without this change, any SQL user with
Perl or Tcl language usage rights can do essentially anything with the
SQL privileges of the target function's owner.
</para>
<para>
The cost of this change is that intentional communication among Perl
and Tcl functions becomes more difficult. To provide an escape hatch,
PL/PerlU and PL/TclU functions continue to use only one interpreter
per session. This is not considered a security issue since all such
functions execute at the trust level of a database superuser already.
</para>
<para>
It is likely that third-party procedural languages that claim to offer
trusted execution have similar security issues. We advise contacting
the authors of any PL you are depending on for security-critical
purposes.
</para>
<para>
Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433).
</para>
</listitem>
<listitem> <listitem>
<para> <para>
Prevent possible crashes in <function>pg_get_expr()</> by disallowing Prevent possible crashes in <function>pg_get_expr()</> by disallowing
......
...@@ -29,6 +29,43 @@ ...@@ -29,6 +29,43 @@
<itemizedlist> <itemizedlist>
<listitem>
<para>
Use a separate interpreter for each calling SQL userid in PL/Perl and
PL/Tcl (Tom Lane)
</para>
<para>
This change prevents security problems that can be caused by subverting
Perl or Tcl code that will be executed later in the same session under
another SQL user identity (for example, within a <literal>SECURITY
DEFINER</> function). Most scripting languages offer numerous ways that
that might be done, such as redefining standard functions or operators
called by the target function. Without this change, any SQL user with
Perl or Tcl language usage rights can do essentially anything with the
SQL privileges of the target function's owner.
</para>
<para>
The cost of this change is that intentional communication among Perl
and Tcl functions becomes more difficult. To provide an escape hatch,
PL/PerlU and PL/TclU functions continue to use only one interpreter
per session. This is not considered a security issue since all such
functions execute at the trust level of a database superuser already.
</para>
<para>
It is likely that third-party procedural languages that claim to offer
trusted execution have similar security issues. We advise contacting
the authors of any PL you are depending on for security-critical
purposes.
</para>
<para>
Our thanks to Tim Bunce for pointing out this issue (CVE-2010-3433).
</para>
</listitem>
<listitem> <listitem>
<para> <para>
Improve <function>pg_get_expr()</> security fix so that the function Improve <function>pg_get_expr()</> security fix so that the function
......
...@@ -49,8 +49,45 @@ ...@@ -49,8 +49,45 @@
/* defines PLPERL_SET_OPMASK */ /* defines PLPERL_SET_OPMASK */
#include "plperl_opmask.h" #include "plperl_opmask.h"
EXTERN_C void boot_DynaLoader(pTHX_ CV *cv);
EXTERN_C void boot_PostgreSQL__InServer__Util(pTHX_ CV *cv);
EXTERN_C void boot_PostgreSQL__InServer__SPI(pTHX_ CV *cv);
PG_MODULE_MAGIC; PG_MODULE_MAGIC;
/**********************************************************************
* Information associated with a Perl interpreter. We have one interpreter
* that is used for all plperlu (untrusted) functions. For plperl (trusted)
* functions, there is a separate interpreter for each effective SQL userid.
* (This is needed to ensure that an unprivileged user can't inject Perl code
* that'll be executed with the privileges of some other SQL user.)
*
* The plperl_interp_desc structs are kept in a Postgres hash table indexed
* by userid OID, with OID 0 used for the single untrusted interpreter.
*
* We start out by creating a "held" interpreter, which we initialize
* only as far as we can do without deciding if it will be trusted or
* untrusted. Later, when we first need to run a plperl or plperlu
* function, we complete the initialization appropriately and move the
* PerlInterpreter pointer into the plperl_interp_hash hashtable. If after
* that we need more interpreters, we create them as needed if we can, or
* fail if the Perl build doesn't support multiple interpreters.
*
* The reason for all the dancing about with a held interpreter is to make
* it possible for people to preload a lot of Perl code at postmaster startup
* (using plperl.on_init) and then use that code in backends. Of course this
* will only work for the first interpreter created in any backend, but it's
* still useful with that restriction.
**********************************************************************/
typedef struct plperl_interp_desc
{
Oid user_id; /* Hash key (must be first!) */
PerlInterpreter *interp; /* The interpreter */
HTAB *query_hash; /* plperl_query_entry structs */
} plperl_interp_desc;
/********************************************************************** /**********************************************************************
* The information we cache about loaded procedures * The information we cache about loaded procedures
**********************************************************************/ **********************************************************************/
...@@ -59,6 +96,7 @@ typedef struct plperl_proc_desc ...@@ -59,6 +96,7 @@ typedef struct plperl_proc_desc
char *proname; /* user name of procedure */ char *proname; /* user name of procedure */
TransactionId fn_xmin; TransactionId fn_xmin;
ItemPointerData fn_tid; ItemPointerData fn_tid;
plperl_interp_desc *interp; /* interpreter it's created in */
bool fn_readonly; bool fn_readonly;
bool lanpltrusted; bool lanpltrusted;
bool fn_retistuple; /* true, if function returns tuple */ bool fn_retistuple; /* true, if function returns tuple */
...@@ -73,14 +111,35 @@ typedef struct plperl_proc_desc ...@@ -73,14 +111,35 @@ typedef struct plperl_proc_desc
SV *reference; SV *reference;
} plperl_proc_desc; } plperl_proc_desc;
/* hash table entry for proc desc */ /**********************************************************************
* For speedy lookup, we maintain a hash table mapping from
* function OID + trigger flag + user OID to plperl_proc_desc pointers.
* The reason the plperl_proc_desc struct isn't directly part of the hash
* entry is to simplify recovery from errors during compile_plperl_function.
*
* Note: if the same function is called by multiple userIDs within a session,
* there will be a separate plperl_proc_desc entry for each userID in the case
* of plperl functions, but only one entry for plperlu functions, because we
* set user_id = 0 for that case. If the user redeclares the same function
* from plperl to plperlu or vice versa, there might be multiple
* plperl_proc_ptr entries in the hashtable, but only one is valid.
**********************************************************************/
typedef struct plperl_proc_key
{
Oid proc_id; /* Function OID */
/*
* is_trigger is really a bool, but declare as Oid to ensure this struct
* contains no padding
*/
Oid is_trigger; /* is it a trigger function? */
Oid user_id; /* User calling the function, or 0 */
} plperl_proc_key;
typedef struct plperl_proc_entry typedef struct plperl_proc_ptr
{ {
char proc_name[NAMEDATALEN]; /* internal name, eg plperl_proc_key proc_key; /* Hash key (must be first!) */
* __PLPerl_proc_39987 */ plperl_proc_desc *proc_ptr;
plperl_proc_desc *proc_data; } plperl_proc_ptr;
} plperl_proc_entry;
/* /*
* The information we cache for the duration of a single call to a * The information we cache for the duration of a single call to a
...@@ -101,7 +160,7 @@ typedef struct plperl_call_data ...@@ -101,7 +160,7 @@ typedef struct plperl_call_data
**********************************************************************/ **********************************************************************/
typedef struct plperl_query_desc typedef struct plperl_query_desc
{ {
char qname[20]; char qname[24];
void *plan; void *plan;
int nargs; int nargs;
Oid *argtypes; Oid *argtypes;
...@@ -121,33 +180,21 @@ typedef struct plperl_query_entry ...@@ -121,33 +180,21 @@ typedef struct plperl_query_entry
* Global data * Global data
**********************************************************************/ **********************************************************************/
typedef enum static HTAB *plperl_interp_hash = NULL;
{
INTERP_NONE,
INTERP_HELD,
INTERP_TRUSTED,
INTERP_UNTRUSTED,
INTERP_BOTH
} InterpState;
static InterpState interp_state = INTERP_NONE;
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_proc_hash = NULL;
static HTAB *plperl_query_hash = NULL; static plperl_interp_desc *plperl_active_interp = NULL;
/* If we have an unassigned "held" interpreter, it's stored here */
static PerlInterpreter *plperl_held_interp = NULL;
/* GUC variables */
static bool plperl_use_strict = false; static bool plperl_use_strict = false;
static char *plperl_on_init = NULL; static char *plperl_on_init = NULL;
static char *plperl_on_plperl_init = NULL; static char *plperl_on_plperl_init = NULL;
static char *plperl_on_plperlu_init = NULL; static char *plperl_on_plperlu_init = NULL;
static bool plperl_ending = false; static bool plperl_ending = false;
static OP *(*pp_require_orig) (pTHX) = NULL;
static char plperl_opmask[MAXO]; static char plperl_opmask[MAXO];
static void set_interp_require(void);
/* this is saved and restored by plperl_call_handler */ /* this is saved and restored by plperl_call_handler */
static plperl_call_data *current_call_data = NULL; static plperl_call_data *current_call_data = NULL;
...@@ -163,6 +210,7 @@ void _PG_init(void); ...@@ -163,6 +210,7 @@ void _PG_init(void);
static PerlInterpreter *plperl_init_interp(void); static PerlInterpreter *plperl_init_interp(void);
static void plperl_destroy_interp(PerlInterpreter **); static void plperl_destroy_interp(PerlInterpreter **);
static void plperl_fini(int code, Datum arg); static void plperl_fini(int code, Datum arg);
static void set_interp_require(bool trusted);
static Datum plperl_func_handler(PG_FUNCTION_ARGS); static Datum plperl_func_handler(PG_FUNCTION_ARGS);
static Datum plperl_trigger_handler(PG_FUNCTION_ARGS); static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
...@@ -184,7 +232,7 @@ static void plperl_exec_callback(void *arg); ...@@ -184,7 +232,7 @@ static void plperl_exec_callback(void *arg);
static void plperl_inline_callback(void *arg); static void plperl_inline_callback(void *arg);
static char *strip_trailing_ws(const char *msg); static char *strip_trailing_ws(const char *msg);
static OP *pp_require_safe(pTHX); static OP *pp_require_safe(pTHX);
static int restore_context(bool); static void activate_interpreter(plperl_interp_desc *interp_desc);
#ifdef WIN32 #ifdef WIN32
static char *setlocale_perl(int category, char *locale); static char *setlocale_perl(int category, char *locale);
...@@ -251,8 +299,14 @@ _PG_init(void) ...@@ -251,8 +299,14 @@ _PG_init(void)
if (inited) if (inited)
return; return;
/*
* Support localized messages.
*/
pg_bindtextdomain(TEXTDOMAIN); pg_bindtextdomain(TEXTDOMAIN);
/*
* Initialize plperl's GUCs.
*/
DefineCustomBoolVariable("plperl.use_strict", DefineCustomBoolVariable("plperl.use_strict",
gettext_noop("If true, trusted and untrusted Perl code will be compiled in strict mode."), gettext_noop("If true, trusted and untrusted Perl code will be compiled in strict mode."),
NULL, NULL,
...@@ -261,6 +315,12 @@ _PG_init(void) ...@@ -261,6 +315,12 @@ _PG_init(void)
PGC_USERSET, 0, PGC_USERSET, 0,
NULL, NULL); NULL, NULL);
/*
* plperl.on_init is marked PGC_SIGHUP to support the idea that it might
* be executed in the postmaster (if plperl is loaded into the postmaster
* via shared_preload_libraries). This isn't really right either way,
* though.
*/
DefineCustomStringVariable("plperl.on_init", DefineCustomStringVariable("plperl.on_init",
gettext_noop("Perl initialization code to execute when a Perl interpreter is initialized."), gettext_noop("Perl initialization code to execute when a Perl interpreter is initialized."),
NULL, NULL,
...@@ -270,13 +330,18 @@ _PG_init(void) ...@@ -270,13 +330,18 @@ _PG_init(void)
NULL, NULL); NULL, NULL);
/* /*
* plperl.on_plperl_init is currently PGC_SUSET to avoid issues whereby a * plperl.on_plperl_init is marked PGC_SUSET to avoid issues whereby a
* user who doesn't have USAGE privileges on the plperl language could * user who might not even have USAGE privilege on the plperl language
* possibly use SET plperl.on_plperl_init='...' to influence the behaviour * could nonetheless use SET plperl.on_plperl_init='...' to influence the
* of any existing plperl function that they can EXECUTE (which may be * behaviour of any existing plperl function that they can execute (which
* security definer). Set * might be SECURITY DEFINER, leading to a privilege escalation). See
* http://archives.postgresql.org/pgsql-hackers/2010-02/msg00281.php and * http://archives.postgresql.org/pgsql-hackers/2010-02/msg00281.php and
* the overall thread. * the overall thread.
*
* Note that because plperl.use_strict is USERSET, a nefarious user could
* set it to be applied against other people's functions. This is judged
* OK since the worst result would be an error. Your code oughta pass
* use_strict anyway ;-)
*/ */
DefineCustomStringVariable("plperl.on_plperl_init", DefineCustomStringVariable("plperl.on_plperl_init",
gettext_noop("Perl initialization code to execute once when plperl is first used."), gettext_noop("Perl initialization code to execute once when plperl is first used."),
...@@ -296,35 +361,45 @@ _PG_init(void) ...@@ -296,35 +361,45 @@ _PG_init(void)
EmitWarningsOnPlaceholders("plperl"); EmitWarningsOnPlaceholders("plperl");
MemSet(&hash_ctl, 0, sizeof(hash_ctl)); /*
* Create hash tables.
hash_ctl.keysize = NAMEDATALEN; */
hash_ctl.entrysize = sizeof(plperl_proc_entry); memset(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = sizeof(Oid);
plperl_proc_hash = hash_create("PLPerl Procedures", hash_ctl.entrysize = sizeof(plperl_interp_desc);
32, hash_ctl.hash = oid_hash;
plperl_interp_hash = hash_create("PL/Perl interpreters",
8,
&hash_ctl, &hash_ctl,
HASH_ELEM); HASH_ELEM | HASH_FUNCTION);
hash_ctl.entrysize = sizeof(plperl_query_entry); memset(&hash_ctl, 0, sizeof(hash_ctl));
plperl_query_hash = hash_create("PLPerl Queries", hash_ctl.keysize = sizeof(plperl_proc_key);
hash_ctl.entrysize = sizeof(plperl_proc_ptr);
hash_ctl.hash = tag_hash;
plperl_proc_hash = hash_create("PL/Perl procedures",
32, 32,
&hash_ctl, &hash_ctl,
HASH_ELEM); HASH_ELEM | HASH_FUNCTION);
/*
* Save the default opmask.
*/
PLPERL_SET_OPMASK(plperl_opmask); PLPERL_SET_OPMASK(plperl_opmask);
/*
* Create the first Perl interpreter, but only partially initialize it.
*/
plperl_held_interp = plperl_init_interp(); plperl_held_interp = plperl_init_interp();
interp_state = INTERP_HELD;
inited = true; inited = true;
} }
static void static void
set_interp_require(void) set_interp_require(bool trusted)
{ {
if (trusted_context) if (trusted)
{ {
PL_ppaddr[OP_REQUIRE] = pp_require_safe; PL_ppaddr[OP_REQUIRE] = pp_require_safe;
PL_ppaddr[OP_DOFILE] = pp_require_safe; PL_ppaddr[OP_DOFILE] = pp_require_safe;
...@@ -343,6 +418,9 @@ set_interp_require(void) ...@@ -343,6 +418,9 @@ set_interp_require(void)
static void static void
plperl_fini(int code, Datum arg) plperl_fini(int code, Datum arg)
{ {
HASH_SEQ_STATUS hash_seq;
plperl_interp_desc *interp_desc;
elog(DEBUG3, "plperl_fini"); elog(DEBUG3, "plperl_fini");
/* /*
...@@ -360,91 +438,129 @@ plperl_fini(int code, Datum arg) ...@@ -360,91 +438,129 @@ plperl_fini(int code, Datum arg)
return; return;
} }
plperl_destroy_interp(&plperl_trusted_interp); /* Zap the "held" interpreter, if we still have it */
plperl_destroy_interp(&plperl_untrusted_interp);
plperl_destroy_interp(&plperl_held_interp); plperl_destroy_interp(&plperl_held_interp);
/* Zap any fully-initialized interpreters */
hash_seq_init(&hash_seq, plperl_interp_hash);
while ((interp_desc = hash_seq_search(&hash_seq)) != NULL)
{
if (interp_desc->interp)
{
activate_interpreter(interp_desc);
plperl_destroy_interp(&interp_desc->interp);
}
}
elog(DEBUG3, "plperl_fini: done"); elog(DEBUG3, "plperl_fini: done");
} }
/******************************************************************** /*
* * Select and activate an appropriate Perl interpreter.
* We start out by creating a "held" interpreter that we can use in
* trusted or untrusted mode (but not both) as the need arises. Later, we
* assign that interpreter if it is available to either the trusted or
* untrusted interpreter. If it has already been assigned, and we need to
* create the other interpreter, we do that if we can, or error out.
*/ */
static void static void
select_perl_context(bool trusted) select_perl_context(bool trusted)
{ {
EXTERN_C void boot_PostgreSQL__InServer__SPI(pTHX_ CV *cv); Oid user_id;
plperl_interp_desc *interp_desc;
bool found;
PerlInterpreter *interp = NULL;
/* Find or create the interpreter hashtable entry for this userid */
if (trusted)
user_id = GetUserId();
else
user_id = InvalidOid;
interp_desc = hash_search(plperl_interp_hash, &user_id,
HASH_ENTER,
&found);
if (!found)
{
/* Initialize newly-created hashtable entry */
interp_desc->interp = NULL;
interp_desc->query_hash = NULL;
}
/* Make sure we have a query_hash for this interpreter */
if (interp_desc->query_hash == NULL)
{
HASHCTL hash_ctl;
memset(&hash_ctl, 0, sizeof(hash_ctl));
hash_ctl.keysize = NAMEDATALEN;
hash_ctl.entrysize = sizeof(plperl_query_entry);
interp_desc->query_hash = hash_create("PL/Perl queries",
32,
&hash_ctl,
HASH_ELEM);
}
/* /*
* handle simple cases * Quick exit if already have an interpreter
*/ */
if (restore_context(trusted)) if (interp_desc->interp)
{
activate_interpreter(interp_desc);
return; return;
}
/* /*
* adopt held interp if free, else create new one if possible * adopt held interp if free, else create new one if possible
*/ */
if (interp_state == INTERP_HELD) if (plperl_held_interp != NULL)
{ {
/* first actual use of a perl interpreter */ /* first actual use of a perl interpreter */
interp = plperl_held_interp;
/*
* Reset the plperl_held_interp pointer first; if we fail during init
* we don't want to try again with the partially-initialized interp.
*/
plperl_held_interp = NULL;
if (trusted) if (trusted)
{
plperl_trusted_init(); plperl_trusted_init();
plperl_trusted_interp = plperl_held_interp;
interp_state = INTERP_TRUSTED;
}
else else
{
plperl_untrusted_init(); plperl_untrusted_init();
plperl_untrusted_interp = plperl_held_interp;
interp_state = INTERP_UNTRUSTED;
}
/* successfully initialized, so arrange for cleanup */ /* successfully initialized, so arrange for cleanup */
on_proc_exit(plperl_fini, 0); on_proc_exit(plperl_fini, 0);
} }
else else
{ {
#ifdef MULTIPLICITY #ifdef MULTIPLICITY
PerlInterpreter *plperl = plperl_init_interp(); /*
* plperl_init_interp will change Perl's idea of the active
* interpreter. Reset plperl_active_interp temporarily, so that if we
* hit an error partway through here, we'll make sure to switch back
* to a non-broken interpreter before running any other Perl
* functions.
*/
plperl_active_interp = NULL;
/* Now build the new interpreter */
interp = plperl_init_interp();
if (trusted) if (trusted)
{
plperl_trusted_init(); plperl_trusted_init();
plperl_trusted_interp = plperl;
}
else else
{
plperl_untrusted_init(); plperl_untrusted_init();
plperl_untrusted_interp = plperl;
}
interp_state = INTERP_BOTH;
#else #else
elog(ERROR, elog(ERROR,
"cannot allocate second Perl interpreter on this platform"); "cannot allocate multiple Perl interpreters on this platform");
#endif #endif
} }
plperl_held_interp = NULL;
trusted_context = trusted; set_interp_require(trusted);
set_interp_require();
/* /*
* Since the timing of first use of PL/Perl can't be predicted, any * Since the timing of first use of PL/Perl can't be predicted, any
* database interaction during initialization is problematic. Including, * database interaction during initialization is problematic. Including,
* but not limited to, security definer issues. So we only enable access * but not limited to, security definer issues. So we only enable access
* to the database AFTER on_*_init code has run. See * to the database AFTER on_*_init code has run. See
* http://archives.postgresql.org/message-id/20100127143318.GE713@timac.loc * http://archives.postgresql.org/pgsql-hackers/2010-01/msg02669.php
* al
*/ */
newXS("PostgreSQL::InServer::SPI::bootstrap", newXS("PostgreSQL::InServer::SPI::bootstrap",
boot_PostgreSQL__InServer__SPI, __FILE__); boot_PostgreSQL__InServer__SPI, __FILE__);
...@@ -454,35 +570,41 @@ select_perl_context(bool trusted) ...@@ -454,35 +570,41 @@ select_perl_context(bool trusted)
ereport(ERROR, ereport(ERROR,
(errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))), (errmsg("%s", strip_trailing_ws(SvPV_nolen(ERRSV))),
errcontext("while executing PostgreSQL::InServer::SPI::bootstrap"))); errcontext("while executing PostgreSQL::InServer::SPI::bootstrap")));
/* Fully initialized, so mark the hashtable entry valid */
interp_desc->interp = interp;
/* And mark this as the active interpreter */
plperl_active_interp = interp_desc;
} }
/* /*
* Restore previous interpreter selection, if two are active * Make the specified interpreter the active one
*
* A call with NULL does nothing. This is so that "restoring" to a previously
* null state of plperl_active_interp doesn't result in useless thrashing.
*/ */
static int static void
restore_context(bool trusted) activate_interpreter(plperl_interp_desc *interp_desc)
{ {
if (interp_state == INTERP_BOTH || if (interp_desc && plperl_active_interp != interp_desc)
(trusted && interp_state == INTERP_TRUSTED) ||
(!trusted && interp_state == INTERP_UNTRUSTED))
{
if (trusted_context != trusted)
{ {
if (trusted) Assert(interp_desc->interp);
PERL_SET_CONTEXT(plperl_trusted_interp); PERL_SET_CONTEXT(interp_desc->interp);
else /* trusted iff user_id isn't InvalidOid */
PERL_SET_CONTEXT(plperl_untrusted_interp); set_interp_require(OidIsValid(interp_desc->user_id));
plperl_active_interp = interp_desc;
trusted_context = trusted;
set_interp_require();
}
return 1; /* context restored */
} }
return 0; /* unable - appropriate interpreter not
* available */
} }
/*
* Create a new Perl interpreter.
*
* We initialize the interpreter as far as we can without knowing whether
* it will become a trusted or untrusted interpreter; in particular, the
* plperl.on_init code will get executed. Later, either plperl_trusted_init
* or plperl_untrusted_init must be called to complete the initialization.
*/
static PerlInterpreter * static PerlInterpreter *
plperl_init_interp(void) plperl_init_interp(void)
{ {
...@@ -538,17 +660,17 @@ plperl_init_interp(void) ...@@ -538,17 +660,17 @@ plperl_init_interp(void)
STMT_START { \ STMT_START { \
if (saved != NULL) { setlocale_perl(name, saved); pfree(saved); } \ if (saved != NULL) { setlocale_perl(name, saved); pfree(saved); } \
} STMT_END } STMT_END
#endif #endif /* WIN32 */
if (plperl_on_init) if (plperl_on_init && *plperl_on_init)
{ {
embedding[nargs++] = "-e"; embedding[nargs++] = "-e";
embedding[nargs++] = plperl_on_init; embedding[nargs++] = plperl_on_init;
} }
/**** /*
* The perl API docs state that PERL_SYS_INIT3 should be called before * The perl API docs state that PERL_SYS_INIT3 should be called before
* allocating interprters. Unfortunately, on some platforms this fails * allocating interpreters. Unfortunately, on some platforms this fails
* in the Perl_do_taint() routine, which is called when the platform is * in the Perl_do_taint() routine, which is called when the platform is
* using the system's malloc() instead of perl's own. Other platforms, * using the system's malloc() instead of perl's own. Other platforms,
* notably Windows, fail if PERL_SYS_INIT3 is not called. So we call it * notably Windows, fail if PERL_SYS_INIT3 is not called. So we call it
...@@ -655,6 +777,11 @@ pp_require_safe(pTHX) ...@@ -655,6 +777,11 @@ pp_require_safe(pTHX)
} }
/*
* Destroy one Perl interpreter ... actually we just run END blocks.
*
* Caller must have ensured this interpreter is the active one.
*/
static void static void
plperl_destroy_interp(PerlInterpreter **interp) plperl_destroy_interp(PerlInterpreter **interp)
{ {
...@@ -671,8 +798,6 @@ plperl_destroy_interp(PerlInterpreter **interp) ...@@ -671,8 +798,6 @@ plperl_destroy_interp(PerlInterpreter **interp)
* be used to perform manual cleanup. * be used to perform manual cleanup.
*/ */
PERL_SET_CONTEXT(*interp);
/* Run END blocks - based on perl's perl_destruct() */ /* Run END blocks - based on perl's perl_destruct() */
if (PL_exit_flags & PERL_EXIT_DESTRUCT_END) if (PL_exit_flags & PERL_EXIT_DESTRUCT_END)
{ {
...@@ -692,7 +817,9 @@ plperl_destroy_interp(PerlInterpreter **interp) ...@@ -692,7 +817,9 @@ plperl_destroy_interp(PerlInterpreter **interp)
} }
} }
/*
* Initialize the current Perl interpreter as a trusted interp
*/
static void static void
plperl_trusted_init(void) plperl_trusted_init(void)
{ {
...@@ -770,9 +897,15 @@ plperl_trusted_init(void) ...@@ -770,9 +897,15 @@ plperl_trusted_init(void)
} }
/*
* Initialize the current Perl interpreter as an untrusted interp
*/
static void static void
plperl_untrusted_init(void) plperl_untrusted_init(void)
{ {
/*
* Nothing to do except execute plperl.on_plperlu_init
*/
if (plperl_on_plperlu_init && *plperl_on_plperlu_init) if (plperl_on_plperlu_init && *plperl_on_plperlu_init)
{ {
eval_pv(plperl_on_plperlu_init, FALSE); eval_pv(plperl_on_plperlu_init, FALSE);
...@@ -1077,7 +1210,7 @@ plperl_call_handler(PG_FUNCTION_ARGS) ...@@ -1077,7 +1210,7 @@ plperl_call_handler(PG_FUNCTION_ARGS)
{ {
Datum retval; Datum retval;
plperl_call_data *save_call_data = current_call_data; plperl_call_data *save_call_data = current_call_data;
bool oldcontext = trusted_context; plperl_interp_desc *oldinterp = plperl_active_interp;
PG_TRY(); PG_TRY();
{ {
...@@ -1089,13 +1222,13 @@ plperl_call_handler(PG_FUNCTION_ARGS) ...@@ -1089,13 +1222,13 @@ plperl_call_handler(PG_FUNCTION_ARGS)
PG_CATCH(); PG_CATCH();
{ {
current_call_data = save_call_data; current_call_data = save_call_data;
restore_context(oldcontext); activate_interpreter(oldinterp);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
current_call_data = save_call_data; current_call_data = save_call_data;
restore_context(oldcontext); activate_interpreter(oldinterp);
return retval; return retval;
} }
...@@ -1112,7 +1245,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS) ...@@ -1112,7 +1245,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
FmgrInfo flinfo; FmgrInfo flinfo;
plperl_proc_desc desc; plperl_proc_desc desc;
plperl_call_data *save_call_data = current_call_data; plperl_call_data *save_call_data = current_call_data;
bool oldcontext = trusted_context; plperl_interp_desc *oldinterp = plperl_active_interp;
ErrorContextCallback pl_error_context; ErrorContextCallback pl_error_context;
/* Set up a callback for error reporting */ /* Set up a callback for error reporting */
...@@ -1175,7 +1308,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS) ...@@ -1175,7 +1308,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
if (desc.reference) if (desc.reference)
SvREFCNT_dec(desc.reference); SvREFCNT_dec(desc.reference);
current_call_data = save_call_data; current_call_data = save_call_data;
restore_context(oldcontext); activate_interpreter(oldinterp);
PG_RE_THROW(); PG_RE_THROW();
} }
PG_END_TRY(); PG_END_TRY();
...@@ -1184,7 +1317,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS) ...@@ -1184,7 +1317,7 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
SvREFCNT_dec(desc.reference); SvREFCNT_dec(desc.reference);
current_call_data = save_call_data; current_call_data = save_call_data;
restore_context(oldcontext); activate_interpreter(oldinterp);
error_context_stack = pl_error_context.previous; error_context_stack = pl_error_context.previous;
...@@ -1336,8 +1469,6 @@ static void ...@@ -1336,8 +1469,6 @@ static void
plperl_init_shared_libs(pTHX) plperl_init_shared_libs(pTHX)
{ {
char *file = __FILE__; char *file = __FILE__;
EXTERN_C void boot_DynaLoader(pTHX_ CV *cv);
EXTERN_C void boot_PostgreSQL__InServer__Util(pTHX_ CV *cv);
newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file); newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
newXS("PostgreSQL::InServer::Util::bootstrap", newXS("PostgreSQL::InServer::Util::bootstrap",
...@@ -1535,7 +1666,7 @@ plperl_func_handler(PG_FUNCTION_ARGS) ...@@ -1535,7 +1666,7 @@ plperl_func_handler(PG_FUNCTION_ARGS)
"cannot accept a set"))); "cannot accept a set")));
} }
select_perl_context(prodesc->lanpltrusted); activate_interpreter(prodesc->interp);
perlret = plperl_call_perl_func(prodesc, fcinfo); perlret = plperl_call_perl_func(prodesc, fcinfo);
...@@ -1682,7 +1813,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -1682,7 +1813,7 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
pl_error_context.arg = prodesc->proname; pl_error_context.arg = prodesc->proname;
error_context_stack = &pl_error_context; error_context_stack = &pl_error_context;
select_perl_context(prodesc->lanpltrusted); activate_interpreter(prodesc->interp);
svTD = plperl_trigger_build_args(fcinfo); svTD = plperl_trigger_build_args(fcinfo);
perlret = plperl_call_perl_trigger_func(prodesc, fcinfo, svTD); perlret = plperl_call_perl_trigger_func(prodesc, fcinfo, svTD);
...@@ -1762,17 +1893,54 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -1762,17 +1893,54 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
} }
static bool
validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
{
if (proc_ptr && proc_ptr->proc_ptr)
{
plperl_proc_desc *prodesc = proc_ptr->proc_ptr;
bool uptodate;
/************************************************************
* If it's present, must check whether it's still up to date.
* This is needed because CREATE OR REPLACE FUNCTION can modify the
* function's pg_proc entry without changing its OID.
************************************************************/
uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
if (uptodate)
return true;
/* Otherwise, unlink the obsoleted entry from the hashtable ... */
proc_ptr->proc_ptr = NULL;
/* ... and throw it away */
if (prodesc->reference)
{
plperl_interp_desc *oldinterp = plperl_active_interp;
activate_interpreter(prodesc->interp);
SvREFCNT_dec(prodesc->reference);
activate_interpreter(oldinterp);
}
free(prodesc->proname);
free(prodesc);
}
return false;
}
static plperl_proc_desc * static plperl_proc_desc *
compile_plperl_function(Oid fn_oid, bool is_trigger) compile_plperl_function(Oid fn_oid, bool is_trigger)
{ {
HeapTuple procTup; HeapTuple procTup;
Form_pg_proc procStruct; Form_pg_proc procStruct;
char internal_proname[NAMEDATALEN]; plperl_proc_key proc_key;
plperl_proc_ptr *proc_ptr;
plperl_proc_desc *prodesc = NULL; plperl_proc_desc *prodesc = NULL;
int i; int i;
plperl_proc_entry *hash_entry; plperl_interp_desc *oldinterp = plperl_active_interp;
bool found;
bool oldcontext = trusted_context;
ErrorContextCallback plperl_error_context; ErrorContextCallback plperl_error_context;
/* We'll need the pg_proc tuple in any case... */ /* We'll need the pg_proc tuple in any case... */
...@@ -1787,48 +1955,24 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ...@@ -1787,48 +1955,24 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
plperl_error_context.arg = NameStr(procStruct->proname); plperl_error_context.arg = NameStr(procStruct->proname);
error_context_stack = &plperl_error_context; error_context_stack = &plperl_error_context;
/************************************************************ /* Try to find function in plperl_proc_hash */
* Build our internal proc name from the function's Oid proc_key.proc_id = fn_oid;
************************************************************/ proc_key.is_trigger = is_trigger;
if (!is_trigger) proc_key.user_id = GetUserId();
sprintf(internal_proname, "__PLPerl_proc_%u", fn_oid);
else
sprintf(internal_proname, "__PLPerl_proc_%u_trigger", fn_oid);
/************************************************************ proc_ptr = hash_search(plperl_proc_hash, &proc_key,
* Lookup the internal proc name in the hashtable
************************************************************/
hash_entry = hash_search(plperl_proc_hash, internal_proname,
HASH_FIND, NULL); HASH_FIND, NULL);
if (hash_entry) if (validate_plperl_function(proc_ptr, procTup))
{ prodesc = proc_ptr->proc_ptr;
bool uptodate; else
prodesc = hash_entry->proc_data;
/************************************************************
* If it's present, must check whether it's still up to date.
* This is needed because CREATE OR REPLACE FUNCTION can modify the
* function's pg_proc entry without changing its OID.
************************************************************/
uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
if (!uptodate)
{
hash_search(plperl_proc_hash, internal_proname,
HASH_REMOVE, NULL);
if (prodesc->reference)
{ {
select_perl_context(prodesc->lanpltrusted); /* If not found or obsolete, maybe it's plperlu */
SvREFCNT_dec(prodesc->reference); proc_key.user_id = InvalidOid;
restore_context(oldcontext); proc_ptr = hash_search(plperl_proc_hash, &proc_key,
} HASH_FIND, NULL);
free(prodesc->proname); if (validate_plperl_function(proc_ptr, procTup))
free(prodesc); prodesc = proc_ptr->proc_ptr;
prodesc = NULL;
}
} }
/************************************************************ /************************************************************
...@@ -1859,6 +2003,10 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ...@@ -1859,6 +2003,10 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
errmsg("out of memory"))); errmsg("out of memory")));
MemSet(prodesc, 0, sizeof(plperl_proc_desc)); MemSet(prodesc, 0, sizeof(plperl_proc_desc));
prodesc->proname = strdup(NameStr(procStruct->proname)); prodesc->proname = strdup(NameStr(procStruct->proname));
if (prodesc->proname == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_tid = procTup->t_self; prodesc->fn_tid = procTup->t_self;
...@@ -1996,27 +2144,33 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) ...@@ -1996,27 +2144,33 @@ compile_plperl_function(Oid fn_oid, bool is_trigger)
proc_source = TextDatumGetCString(prosrcdatum); proc_source = TextDatumGetCString(prosrcdatum);
/************************************************************ /************************************************************
* Create the procedure in the interpreter * Create the procedure in the appropriate interpreter
************************************************************/ ************************************************************/
select_perl_context(prodesc->lanpltrusted); select_perl_context(prodesc->lanpltrusted);
prodesc->interp = plperl_active_interp;
plperl_create_sub(prodesc, proc_source, fn_oid); plperl_create_sub(prodesc, proc_source, fn_oid);
restore_context(oldcontext); activate_interpreter(oldinterp);
pfree(proc_source); pfree(proc_source);
if (!prodesc->reference) /* can this happen? */ if (!prodesc->reference) /* can this happen? */
{ {
free(prodesc->proname); free(prodesc->proname);
free(prodesc); free(prodesc);
elog(ERROR, "could not create internal procedure \"%s\"", elog(ERROR, "could not create PL/Perl internal procedure");
internal_proname);
} }
hash_entry = hash_search(plperl_proc_hash, internal_proname, /************************************************************
HASH_ENTER, &found); * OK, link the procedure into the correct hashtable entry
hash_entry->proc_data = prodesc; ************************************************************/
proc_key.user_id = prodesc->lanpltrusted ? GetUserId() : InvalidOid;
proc_ptr = hash_search(plperl_proc_hash, &proc_key,
HASH_ENTER, NULL);
proc_ptr->proc_ptr = prodesc;
} }
/* restore previous error callback */ /* restore previous error callback */
...@@ -2636,7 +2790,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv) ...@@ -2636,7 +2790,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
* the key to the caller. * the key to the caller.
************************************************************/ ************************************************************/
hash_entry = hash_search(plperl_query_hash, qdesc->qname, hash_entry = hash_search(plperl_active_interp->query_hash, qdesc->qname,
HASH_ENTER, &found); HASH_ENTER, &found);
hash_entry->query_data = qdesc; hash_entry->query_data = qdesc;
...@@ -2675,7 +2829,7 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv) ...@@ -2675,7 +2829,7 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv)
* Fetch the saved plan descriptor, see if it's o.k. * Fetch the saved plan descriptor, see if it's o.k.
************************************************************/ ************************************************************/
hash_entry = hash_search(plperl_query_hash, query, hash_entry = hash_search(plperl_active_interp->query_hash, query,
HASH_FIND, NULL); HASH_FIND, NULL);
if (hash_entry == NULL) if (hash_entry == NULL)
elog(ERROR, "spi_exec_prepared: Invalid prepared query passed"); elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
...@@ -2683,7 +2837,7 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv) ...@@ -2683,7 +2837,7 @@ plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv)
qdesc = hash_entry->query_data; qdesc = hash_entry->query_data;
if (qdesc == NULL) if (qdesc == NULL)
elog(ERROR, "spi_exec_prepared: panic - plperl_query_hash value vanished"); elog(ERROR, "spi_exec_prepared: panic - plperl query_hash value vanished");
if (qdesc->nargs != argc) if (qdesc->nargs != argc)
elog(ERROR, "spi_exec_prepared: expected %d argument(s), %d passed", elog(ERROR, "spi_exec_prepared: expected %d argument(s), %d passed",
...@@ -2818,7 +2972,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv) ...@@ -2818,7 +2972,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
/************************************************************ /************************************************************
* Fetch the saved plan descriptor, see if it's o.k. * Fetch the saved plan descriptor, see if it's o.k.
************************************************************/ ************************************************************/
hash_entry = hash_search(plperl_query_hash, query, hash_entry = hash_search(plperl_active_interp->query_hash, query,
HASH_FIND, NULL); HASH_FIND, NULL);
if (hash_entry == NULL) if (hash_entry == NULL)
elog(ERROR, "spi_exec_prepared: Invalid prepared query passed"); elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
...@@ -2826,7 +2980,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv) ...@@ -2826,7 +2980,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
qdesc = hash_entry->query_data; qdesc = hash_entry->query_data;
if (qdesc == NULL) if (qdesc == NULL)
elog(ERROR, "spi_query_prepared: panic - plperl_query_hash value vanished"); elog(ERROR, "spi_query_prepared: panic - plperl query_hash value vanished");
if (qdesc->nargs != argc) if (qdesc->nargs != argc)
elog(ERROR, "spi_query_prepared: expected %d argument(s), %d passed", elog(ERROR, "spi_query_prepared: expected %d argument(s), %d passed",
...@@ -2934,7 +3088,7 @@ plperl_spi_freeplan(char *query) ...@@ -2934,7 +3088,7 @@ plperl_spi_freeplan(char *query)
check_spi_usage_allowed(); check_spi_usage_allowed();
hash_entry = hash_search(plperl_query_hash, query, hash_entry = hash_search(plperl_active_interp->query_hash, query,
HASH_FIND, NULL); HASH_FIND, NULL);
if (hash_entry == NULL) if (hash_entry == NULL)
elog(ERROR, "spi_exec_prepared: Invalid prepared query passed"); elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
...@@ -2942,13 +3096,13 @@ plperl_spi_freeplan(char *query) ...@@ -2942,13 +3096,13 @@ plperl_spi_freeplan(char *query)
qdesc = hash_entry->query_data; qdesc = hash_entry->query_data;
if (qdesc == NULL) if (qdesc == NULL)
elog(ERROR, "spi_exec_freeplan: panic - plperl_query_hash value vanished"); elog(ERROR, "spi_exec_freeplan: panic - plperl query_hash value vanished");
/* /*
* free all memory before SPI_freeplan, so if it dies, nothing will be * free all memory before SPI_freeplan, so if it dies, nothing will be
* left over * left over
*/ */
hash_search(plperl_query_hash, query, hash_search(plperl_active_interp->query_hash, query,
HASH_REMOVE, NULL); HASH_REMOVE, NULL);
plan = qdesc->plan; plan = qdesc->plan;
......
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
#endif #endif
#include "access/xact.h" #include "access/xact.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h" #include "catalog/pg_proc.h"
#include "catalog/pg_type.h" #include "catalog/pg_type.h"
#include "commands/trigger.h" #include "commands/trigger.h"
...@@ -83,6 +82,25 @@ utf_e2u(unsigned char *src) ...@@ -83,6 +82,25 @@ utf_e2u(unsigned char *src)
PG_MODULE_MAGIC; PG_MODULE_MAGIC;
/**********************************************************************
* Information associated with a Tcl interpreter. We have one interpreter
* that is used for all pltclu (untrusted) functions. For pltcl (trusted)
* functions, there is a separate interpreter for each effective SQL userid.
* (This is needed to ensure that an unprivileged user can't inject Tcl code
* that'll be executed with the privileges of some other SQL user.)
*
* The pltcl_interp_desc structs are kept in a Postgres hash table indexed
* by userid OID, with OID 0 used for the single untrusted interpreter.
**********************************************************************/
typedef struct pltcl_interp_desc
{
Oid user_id; /* Hash key (must be first!) */
Tcl_Interp *interp; /* The interpreter */
Tcl_HashTable query_hash; /* pltcl_query_desc structs */
} pltcl_interp_desc;
/********************************************************************** /**********************************************************************
* The information we cache about loaded procedures * The information we cache about loaded procedures
**********************************************************************/ **********************************************************************/
...@@ -94,6 +112,7 @@ typedef struct pltcl_proc_desc ...@@ -94,6 +112,7 @@ typedef struct pltcl_proc_desc
ItemPointerData fn_tid; ItemPointerData fn_tid;
bool fn_readonly; bool fn_readonly;
bool lanpltrusted; bool lanpltrusted;
pltcl_interp_desc *interp_desc;
FmgrInfo result_in_func; FmgrInfo result_in_func;
Oid result_typioparam; Oid result_typioparam;
int nargs; int nargs;
...@@ -116,20 +135,40 @@ typedef struct pltcl_query_desc ...@@ -116,20 +135,40 @@ typedef struct pltcl_query_desc
} pltcl_query_desc; } pltcl_query_desc;
/**********************************************************************
* For speedy lookup, we maintain a hash table mapping from
* function OID + trigger OID + user OID to pltcl_proc_desc pointers.
* The reason the pltcl_proc_desc struct isn't directly part of the hash
* entry is to simplify recovery from errors during compile_pltcl_function.
*
* Note: if the same function is called by multiple userIDs within a session,
* there will be a separate pltcl_proc_desc entry for each userID in the case
* of pltcl functions, but only one entry for pltclu functions, because we
* set user_id = 0 for that case.
**********************************************************************/
typedef struct pltcl_proc_key
{
Oid proc_id; /* Function OID */
Oid trig_id; /* Trigger OID, or 0 if not trigger */
Oid user_id; /* User calling the function, or 0 */
} pltcl_proc_key;
typedef struct pltcl_proc_ptr
{
pltcl_proc_key proc_key; /* Hash key (must be first!) */
pltcl_proc_desc *proc_ptr;
} pltcl_proc_ptr;
/********************************************************************** /**********************************************************************
* Global data * Global data
**********************************************************************/ **********************************************************************/
static bool pltcl_pm_init_done = false; static bool pltcl_pm_init_done = false;
static bool pltcl_be_norm_init_done = false;
static bool pltcl_be_safe_init_done = false;
static Tcl_Interp *pltcl_hold_interp = NULL; static Tcl_Interp *pltcl_hold_interp = NULL;
static Tcl_Interp *pltcl_norm_interp = NULL; static HTAB *pltcl_interp_htab = NULL;
static Tcl_Interp *pltcl_safe_interp = NULL; static HTAB *pltcl_proc_htab = NULL;
static Tcl_HashTable *pltcl_proc_hash = NULL;
static Tcl_HashTable *pltcl_norm_query_hash = NULL;
static Tcl_HashTable *pltcl_safe_query_hash = NULL;
/* these are saved and restored by pltcl_call_handler */ /* these are saved and restored by pltcl_handler */
static FunctionCallInfo pltcl_current_fcinfo = NULL; static FunctionCallInfo pltcl_current_fcinfo = NULL;
static pltcl_proc_desc *pltcl_current_prodesc = NULL; static pltcl_proc_desc *pltcl_current_prodesc = NULL;
...@@ -140,17 +179,20 @@ Datum pltcl_call_handler(PG_FUNCTION_ARGS); ...@@ -140,17 +179,20 @@ Datum pltcl_call_handler(PG_FUNCTION_ARGS);
Datum pltclu_call_handler(PG_FUNCTION_ARGS); Datum pltclu_call_handler(PG_FUNCTION_ARGS);
void _PG_init(void); void _PG_init(void);
static void pltcl_init_interp(Tcl_Interp *interp); static void pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted);
static Tcl_Interp *pltcl_fetch_interp(bool pltrusted); static pltcl_interp_desc *pltcl_fetch_interp(bool pltrusted);
static void pltcl_init_load_unknown(Tcl_Interp *interp); static void pltcl_init_load_unknown(Tcl_Interp *interp);
static Datum pltcl_func_handler(PG_FUNCTION_ARGS); static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);
static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS); static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted);
static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted);
static void throw_tcl_error(Tcl_Interp *interp, const char *proname); static void throw_tcl_error(Tcl_Interp *interp, const char *proname);
static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid); static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
bool pltrusted);
static int pltcl_elog(ClientData cdata, Tcl_Interp *interp, static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]); int argc, CONST84 char *argv[]);
...@@ -264,10 +306,15 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo) ...@@ -264,10 +306,15 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
* _PG_init() - library load-time initialization * _PG_init() - library load-time initialization
* *
* DO NOT make this static nor change its name! * DO NOT make this static nor change its name!
*
* The work done here must be safe to do in the postmaster process,
* in case the pltcl library is preloaded in the postmaster.
*/ */
void void
_PG_init(void) _PG_init(void)
{ {
HASHCTL hash_ctl;
/* Be sure we do initialization only once (should be redundant now) */ /* Be sure we do initialization only once (should be redundant now) */
if (pltcl_pm_init_done) if (pltcl_pm_init_done)
return; return;
...@@ -304,47 +351,62 @@ _PG_init(void) ...@@ -304,47 +351,62 @@ _PG_init(void)
* stdout and stderr on DeleteInterp * stdout and stderr on DeleteInterp
************************************************************/ ************************************************************/
if ((pltcl_hold_interp = Tcl_CreateInterp()) == NULL) if ((pltcl_hold_interp = Tcl_CreateInterp()) == NULL)
elog(ERROR, "could not create \"hold\" interpreter"); elog(ERROR, "could not create master Tcl interpreter");
if (Tcl_Init(pltcl_hold_interp) == TCL_ERROR) if (Tcl_Init(pltcl_hold_interp) == TCL_ERROR)
elog(ERROR, "could not initialize \"hold\" interpreter"); elog(ERROR, "could not initialize master Tcl interpreter");
/************************************************************ /************************************************************
* Create the two slave interpreters. Note: Tcl automatically does * Create the hash table for working interpreters
* Tcl_Init on the normal slave, and it's not wanted for the safe slave.
************************************************************/ ************************************************************/
if ((pltcl_norm_interp = memset(&hash_ctl, 0, sizeof(hash_ctl));
Tcl_CreateSlave(pltcl_hold_interp, "norm", 0)) == NULL) hash_ctl.keysize = sizeof(Oid);
elog(ERROR, "could not create \"normal\" interpreter"); hash_ctl.entrysize = sizeof(pltcl_interp_desc);
pltcl_init_interp(pltcl_norm_interp); hash_ctl.hash = oid_hash;
pltcl_interp_htab = hash_create("PL/Tcl interpreters",
if ((pltcl_safe_interp = 8,
Tcl_CreateSlave(pltcl_hold_interp, "safe", 1)) == NULL) &hash_ctl,
elog(ERROR, "could not create \"safe\" interpreter"); HASH_ELEM | HASH_FUNCTION);
pltcl_init_interp(pltcl_safe_interp);
/************************************************************ /************************************************************
* Initialize the proc and query hash tables * Create the hash table for function lookup
************************************************************/ ************************************************************/
pltcl_proc_hash = (Tcl_HashTable *) malloc(sizeof(Tcl_HashTable)); memset(&hash_ctl, 0, sizeof(hash_ctl));
pltcl_norm_query_hash = (Tcl_HashTable *) malloc(sizeof(Tcl_HashTable)); hash_ctl.keysize = sizeof(pltcl_proc_key);
pltcl_safe_query_hash = (Tcl_HashTable *) malloc(sizeof(Tcl_HashTable)); hash_ctl.entrysize = sizeof(pltcl_proc_ptr);
Tcl_InitHashTable(pltcl_proc_hash, TCL_STRING_KEYS); hash_ctl.hash = tag_hash;
Tcl_InitHashTable(pltcl_norm_query_hash, TCL_STRING_KEYS); pltcl_proc_htab = hash_create("PL/Tcl functions",
Tcl_InitHashTable(pltcl_safe_query_hash, TCL_STRING_KEYS); 100,
&hash_ctl,
HASH_ELEM | HASH_FUNCTION);
pltcl_pm_init_done = true; pltcl_pm_init_done = true;
} }
/********************************************************************** /**********************************************************************
* pltcl_init_interp() - initialize a Tcl interpreter * pltcl_init_interp() - initialize a new Tcl interpreter
*
* The work done here must be safe to do in the postmaster process,
* in case the pltcl library is preloaded in the postmaster. Note
* that this is applied separately to the "normal" and "safe" interpreters.
**********************************************************************/ **********************************************************************/
static void static void
pltcl_init_interp(Tcl_Interp *interp) pltcl_init_interp(pltcl_interp_desc *interp_desc, bool pltrusted)
{ {
Tcl_Interp *interp;
char interpname[32];
/************************************************************
* Create the Tcl interpreter as a slave of pltcl_hold_interp.
* Note: Tcl automatically does Tcl_Init in the untrusted case,
* and it's not wanted in the trusted case.
************************************************************/
snprintf(interpname, sizeof(interpname), "slave_%u", interp_desc->user_id);
if ((interp = Tcl_CreateSlave(pltcl_hold_interp, interpname,
pltrusted ? 1 : 0)) == NULL)
elog(ERROR, "could not create slave Tcl interpreter");
interp_desc->interp = interp;
/************************************************************
* Initialize the query hash table associated with interpreter
************************************************************/
Tcl_InitHashTable(&interp_desc->query_hash, TCL_STRING_KEYS);
/************************************************************ /************************************************************
* Install the commands for SPI support in the interpreter * Install the commands for SPI support in the interpreter
************************************************************/ ************************************************************/
...@@ -365,43 +427,39 @@ pltcl_init_interp(Tcl_Interp *interp) ...@@ -365,43 +427,39 @@ pltcl_init_interp(Tcl_Interp *interp)
pltcl_SPI_execute_plan, NULL, NULL); pltcl_SPI_execute_plan, NULL, NULL);
Tcl_CreateCommand(interp, "spi_lastoid", Tcl_CreateCommand(interp, "spi_lastoid",
pltcl_SPI_lastoid, NULL, NULL); pltcl_SPI_lastoid, NULL, NULL);
/************************************************************
* Try to load the unknown procedure from pltcl_modules
************************************************************/
pltcl_init_load_unknown(interp);
} }
/********************************************************************** /**********************************************************************
* pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function * pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function
* *
* This also takes care of any on-first-use initialization required. * This also takes care of any on-first-use initialization required.
* The initialization work done here can't be done in the postmaster, and
* hence is not safe to do at library load time, because it may invoke
* arbitrary user-defined code.
* Note: we assume caller has already connected to SPI. * Note: we assume caller has already connected to SPI.
**********************************************************************/ **********************************************************************/
static Tcl_Interp * static pltcl_interp_desc *
pltcl_fetch_interp(bool pltrusted) pltcl_fetch_interp(bool pltrusted)
{ {
Tcl_Interp *interp; Oid user_id;
pltcl_interp_desc *interp_desc;
bool found;
/* On first use, we try to load the unknown procedure from pltcl_modules */ /* Find or create the interpreter hashtable entry for this userid */
if (pltrusted) if (pltrusted)
{ user_id = GetUserId();
interp = pltcl_safe_interp;
if (!pltcl_be_safe_init_done)
{
pltcl_init_load_unknown(interp);
pltcl_be_safe_init_done = true;
}
}
else else
{ user_id = InvalidOid;
interp = pltcl_norm_interp;
if (!pltcl_be_norm_init_done)
{
pltcl_init_load_unknown(interp);
pltcl_be_norm_init_done = true;
}
}
return interp; interp_desc = hash_search(pltcl_interp_htab, &user_id,
HASH_ENTER,
&found);
if (!found)
pltcl_init_interp(interp_desc, pltrusted);
return interp_desc;
} }
/********************************************************************** /**********************************************************************
...@@ -532,6 +590,25 @@ PG_FUNCTION_INFO_V1(pltcl_call_handler); ...@@ -532,6 +590,25 @@ PG_FUNCTION_INFO_V1(pltcl_call_handler);
/* keep non-static */ /* keep non-static */
Datum Datum
pltcl_call_handler(PG_FUNCTION_ARGS) pltcl_call_handler(PG_FUNCTION_ARGS)
{
return pltcl_handler(fcinfo, true);
}
/*
* Alternative handler for unsafe functions
*/
PG_FUNCTION_INFO_V1(pltclu_call_handler);
/* keep non-static */
Datum
pltclu_call_handler(PG_FUNCTION_ARGS)
{
return pltcl_handler(fcinfo, false);
}
static Datum
pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted)
{ {
Datum retval; Datum retval;
FunctionCallInfo save_fcinfo; FunctionCallInfo save_fcinfo;
...@@ -552,12 +629,12 @@ pltcl_call_handler(PG_FUNCTION_ARGS) ...@@ -552,12 +629,12 @@ pltcl_call_handler(PG_FUNCTION_ARGS)
if (CALLED_AS_TRIGGER(fcinfo)) if (CALLED_AS_TRIGGER(fcinfo))
{ {
pltcl_current_fcinfo = NULL; pltcl_current_fcinfo = NULL;
retval = PointerGetDatum(pltcl_trigger_handler(fcinfo)); retval = PointerGetDatum(pltcl_trigger_handler(fcinfo, pltrusted));
} }
else else
{ {
pltcl_current_fcinfo = fcinfo; pltcl_current_fcinfo = fcinfo;
retval = pltcl_func_handler(fcinfo); retval = pltcl_func_handler(fcinfo, pltrusted);
} }
} }
PG_CATCH(); PG_CATCH();
...@@ -575,23 +652,11 @@ pltcl_call_handler(PG_FUNCTION_ARGS) ...@@ -575,23 +652,11 @@ pltcl_call_handler(PG_FUNCTION_ARGS)
} }
/*
* Alternative handler for unsafe functions
*/
PG_FUNCTION_INFO_V1(pltclu_call_handler);
/* keep non-static */
Datum
pltclu_call_handler(PG_FUNCTION_ARGS)
{
return pltcl_call_handler(fcinfo);
}
/********************************************************************** /**********************************************************************
* pltcl_func_handler() - Handler for regular function calls * pltcl_func_handler() - Handler for regular function calls
**********************************************************************/ **********************************************************************/
static Datum static Datum
pltcl_func_handler(PG_FUNCTION_ARGS) pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted)
{ {
pltcl_proc_desc *prodesc; pltcl_proc_desc *prodesc;
Tcl_Interp *volatile interp; Tcl_Interp *volatile interp;
...@@ -606,11 +671,12 @@ pltcl_func_handler(PG_FUNCTION_ARGS) ...@@ -606,11 +671,12 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
elog(ERROR, "could not connect to SPI manager"); elog(ERROR, "could not connect to SPI manager");
/* Find or compile the function */ /* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid); prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
pltrusted);
pltcl_current_prodesc = prodesc; pltcl_current_prodesc = prodesc;
interp = pltcl_fetch_interp(prodesc->lanpltrusted); interp = prodesc->interp_desc->interp;
/************************************************************ /************************************************************
* Create the tcl command to call the internal * Create the tcl command to call the internal
...@@ -738,7 +804,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS) ...@@ -738,7 +804,7 @@ pltcl_func_handler(PG_FUNCTION_ARGS)
* pltcl_trigger_handler() - Handler for trigger calls * pltcl_trigger_handler() - Handler for trigger calls
**********************************************************************/ **********************************************************************/
static HeapTuple static HeapTuple
pltcl_trigger_handler(PG_FUNCTION_ARGS) pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
{ {
pltcl_proc_desc *prodesc; pltcl_proc_desc *prodesc;
Tcl_Interp *volatile interp; Tcl_Interp *volatile interp;
...@@ -764,11 +830,12 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS) ...@@ -764,11 +830,12 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */ /* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
RelationGetRelid(trigdata->tg_relation)); RelationGetRelid(trigdata->tg_relation),
pltrusted);
pltcl_current_prodesc = prodesc; pltcl_current_prodesc = prodesc;
interp = pltcl_fetch_interp(prodesc->lanpltrusted); interp = prodesc->interp_desc->interp;
tupdesc = trigdata->tg_relation->rd_att; tupdesc = trigdata->tg_relation->rd_att;
...@@ -1086,18 +1153,14 @@ throw_tcl_error(Tcl_Interp *interp, const char *proname) ...@@ -1086,18 +1153,14 @@ throw_tcl_error(Tcl_Interp *interp, const char *proname)
* (InvalidOid) when compiling a plain function. * (InvalidOid) when compiling a plain function.
**********************************************************************/ **********************************************************************/
static pltcl_proc_desc * static pltcl_proc_desc *
compile_pltcl_function(Oid fn_oid, Oid tgreloid) compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
{ {
bool is_trigger = OidIsValid(tgreloid);
HeapTuple procTup; HeapTuple procTup;
Form_pg_proc procStruct; Form_pg_proc procStruct;
char internal_proname[128]; pltcl_proc_key proc_key;
Tcl_HashEntry *hashent; pltcl_proc_ptr *proc_ptr;
pltcl_proc_desc *prodesc = NULL; bool found;
Tcl_Interp *interp; pltcl_proc_desc *prodesc;
int i;
int hashnew;
int tcl_rc;
/* We'll need the pg_proc tuple in any case... */ /* We'll need the pg_proc tuple in any case... */
procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
...@@ -1105,39 +1168,35 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) ...@@ -1105,39 +1168,35 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
elog(ERROR, "cache lookup failed for function %u", fn_oid); elog(ERROR, "cache lookup failed for function %u", fn_oid);
procStruct = (Form_pg_proc) GETSTRUCT(procTup); procStruct = (Form_pg_proc) GETSTRUCT(procTup);
/************************************************************ /* Try to find function in pltcl_proc_htab */
* Build our internal proc name from the functions Oid proc_key.proc_id = fn_oid;
************************************************************/ proc_key.trig_id = tgreloid;
if (!is_trigger) proc_key.user_id = pltrusted ? GetUserId() : InvalidOid;
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u", fn_oid);
else
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u_trigger_%u", fn_oid, tgreloid);
/************************************************************ proc_ptr = hash_search(pltcl_proc_htab, &proc_key,
* Lookup the internal proc name in the hashtable HASH_ENTER,
************************************************************/ &found);
hashent = Tcl_FindHashEntry(pltcl_proc_hash, internal_proname); if (!found)
proc_ptr->proc_ptr = NULL;
prodesc = proc_ptr->proc_ptr;
/************************************************************ /************************************************************
* If it's present, must check whether it's still up to date. * If it's present, must check whether it's still up to date.
* This is needed because CREATE OR REPLACE FUNCTION can modify the * This is needed because CREATE OR REPLACE FUNCTION can modify the
* function's pg_proc entry without changing its OID. * function's pg_proc entry without changing its OID.
************************************************************/ ************************************************************/
if (hashent != NULL) if (prodesc != NULL)
{ {
bool uptodate; bool uptodate;
prodesc = (pltcl_proc_desc *) Tcl_GetHashValue(hashent);
uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) && uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self)); ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
if (!uptodate) if (!uptodate)
{ {
Tcl_DeleteHashEntry(hashent); proc_ptr->proc_ptr = NULL;
hashent = NULL; prodesc = NULL;
} }
} }
...@@ -1149,11 +1208,11 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) ...@@ -1149,11 +1208,11 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
* *
* Then we load the procedure into the Tcl interpreter. * Then we load the procedure into the Tcl interpreter.
************************************************************/ ************************************************************/
if (hashent == NULL) if (prodesc == NULL)
{ {
HeapTuple langTup; bool is_trigger = OidIsValid(tgreloid);
char internal_proname[128];
HeapTuple typeTup; HeapTuple typeTup;
Form_pg_language langStruct;
Form_pg_type typeStruct; Form_pg_type typeStruct;
Tcl_DString proc_internal_def; Tcl_DString proc_internal_def;
Tcl_DString proc_internal_body; Tcl_DString proc_internal_body;
...@@ -1162,6 +1221,19 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) ...@@ -1162,6 +1221,19 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
bool isnull; bool isnull;
char *proc_source; char *proc_source;
char buf[32]; char buf[32];
Tcl_Interp *interp;
int i;
int tcl_rc;
/************************************************************
* Build our internal proc name from the functions Oid + trigger Oid
************************************************************/
if (!is_trigger)
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u", fn_oid);
else
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u_trigger_%u", fn_oid, tgreloid);
/************************************************************ /************************************************************
* Allocate a new procedure description block * Allocate a new procedure description block
...@@ -1174,31 +1246,24 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) ...@@ -1174,31 +1246,24 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
MemSet(prodesc, 0, sizeof(pltcl_proc_desc)); MemSet(prodesc, 0, sizeof(pltcl_proc_desc));
prodesc->user_proname = strdup(NameStr(procStruct->proname)); prodesc->user_proname = strdup(NameStr(procStruct->proname));
prodesc->internal_proname = strdup(internal_proname); prodesc->internal_proname = strdup(internal_proname);
if (prodesc->user_proname == NULL || prodesc->internal_proname == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
prodesc->fn_tid = procTup->t_self; prodesc->fn_tid = procTup->t_self;
/* Remember if function is STABLE/IMMUTABLE */ /* Remember if function is STABLE/IMMUTABLE */
prodesc->fn_readonly = prodesc->fn_readonly =
(procStruct->provolatile != PROVOLATILE_VOLATILE); (procStruct->provolatile != PROVOLATILE_VOLATILE);
/* And whether it is trusted */
prodesc->lanpltrusted = pltrusted;
/************************************************************ /************************************************************
* Lookup the pg_language tuple by Oid * Identify the interpreter to use for the function
************************************************************/ ************************************************************/
langTup = SearchSysCache1(LANGOID, prodesc->interp_desc = pltcl_fetch_interp(prodesc->lanpltrusted);
ObjectIdGetDatum(procStruct->prolang)); interp = prodesc->interp_desc->interp;
if (!HeapTupleIsValid(langTup))
{
free(prodesc->user_proname);
free(prodesc->internal_proname);
free(prodesc);
elog(ERROR, "cache lookup failed for language %u",
procStruct->prolang);
}
langStruct = (Form_pg_language) GETSTRUCT(langTup);
prodesc->lanpltrusted = langStruct->lanpltrusted;
ReleaseSysCache(langTup);
interp = pltcl_fetch_interp(prodesc->lanpltrusted);
/************************************************************ /************************************************************
* Get the required information for input conversion of the * Get the required information for input conversion of the
...@@ -1404,11 +1469,12 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) ...@@ -1404,11 +1469,12 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid)
} }
/************************************************************ /************************************************************
* Add the proc description block to the hashtable * Add the proc description block to the hashtable. Note we do not
* attempt to free any previously existing prodesc block. This is
* annoying, but necessary since there could be active calls using
* the old prodesc.
************************************************************/ ************************************************************/
hashent = Tcl_CreateHashEntry(pltcl_proc_hash, proc_ptr->proc_ptr = prodesc;
prodesc->internal_proname, &hashnew);
Tcl_SetHashValue(hashent, (ClientData) prodesc);
} }
ReleaseSysCache(procTup); ReleaseSysCache(procTup);
...@@ -2064,10 +2130,7 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp, ...@@ -2064,10 +2130,7 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
* Insert a hashtable entry for the plan and return * Insert a hashtable entry for the plan and return
* the key to the caller * the key to the caller
************************************************************/ ************************************************************/
if (interp == pltcl_norm_interp) query_hash = &pltcl_current_prodesc->interp_desc->query_hash;
query_hash = pltcl_norm_query_hash;
else
query_hash = pltcl_safe_query_hash;
hashent = Tcl_CreateHashEntry(query_hash, qdesc->qname, &hashnew); hashent = Tcl_CreateHashEntry(query_hash, qdesc->qname, &hashnew);
Tcl_SetHashValue(hashent, (ClientData) qdesc); Tcl_SetHashValue(hashent, (ClientData) qdesc);
...@@ -2158,10 +2221,7 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp, ...@@ -2158,10 +2221,7 @@ pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
return TCL_ERROR; return TCL_ERROR;
} }
if (interp == pltcl_norm_interp) query_hash = &pltcl_current_prodesc->interp_desc->query_hash;
query_hash = pltcl_norm_query_hash;
else
query_hash = pltcl_safe_query_hash;
hashent = Tcl_FindHashEntry(query_hash, argv[i]); hashent = Tcl_FindHashEntry(query_hash, argv[i]);
if (hashent == NULL) if (hashent == NULL)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment