The submission from Edward Muller I'm replying to is quite similar to what the other raging discussion here decided was the right level to target. There was one last year from Josh Kupershmidt with similar goals: http://archives.postgresql.org/pgsql-admin/2010-02/msg00052.php A good place to start is the concise summary of the new specification goal that Tom made in the other thread:

> If allowing same-user cancels is enough to solve 95% or 99% of the real-world use cases, let's just do that.

Same-user cancels, but not termination.  Only this, and nothing more.

Relative to that goal, Ed's patch was too permissive for termination, and since he's new to this code it didn't check all the error conditions possible here. Josh's patch had many of the right error checks, but it was more code than I liked for his slightly different permissions change. And its attempts to be helpful leaked role information. (That may have been just debugging debris left for review purposes) I mashed the best bits of both together, tried to simplify the result, then commented heavily upon the race conditions and design decisions the code reflects. Far as I can tell the patch is feature complete, including documentation.

Appropriate credits here would go Josh Kupershmidt, Edward Muller, and then myself; everyone did an equally useful chunk of this in that order. It's all packaged up for useful gitsumption at https://github.com/greg2ndQuadrant/postgres/tree/cancel_backend too. I attached it to the next CommitFest: https://commitfest.postgresql.org/action/patch_view?id=722 but would enjoy seeing a stake finally put through its evil heart before then, as I don't think there's much left to do now.

To demo I start with a limited user and a crazy, must be stopped backend:

$ createuser test
Shall the new role be a superuser? (y/n) n
Shall the new role be allowed to create databases? (y/n) n
Shall the new role be allowed to create more new roles? (y/n) n
$ psql -U test
test=> select pg_sleep(1000000);

Begin another session, find and try to terminate; get rejected with a hint, then follow it to cancel:

test=> select procpid,usename,current_query from pg_stat_activity;
-[ RECORD 1 ]-+------------------------------------------------------------
procpid       | 28154
usename       | test
current_query | select pg_sleep(1000000);

test=> select pg_terminate_backend(28154);
ERROR:  must be superuser to terminate other server processes
HINT:  you can use pg_cancel_backend() on your own processes
test=> select pg_cancel_backend(28154);
-[ RECORD 1 ]-----+--
pg_cancel_backend | t

And then this is shown on the first one:

test=> select pg_sleep(1000000);
ERROR:  canceling statement due to user request

Victory over the evil sleeping backend is complete, without a superuser in sight.

There's one obvious and questionable design decision I made to highlight. Right now the only consumers of pg_signal_backend are the cancel and terminate calls. What I did was make pg_signal_backend more permissive, adding the idea that role equivalence = allowed, and therefore granting that to anything else that might call it. And then I put a stricter check on termination. This results in a redundant check of superuser on the termination check, and the potential for mis-using pg_signal_backend. I documented all that and liked the result; it feels better to me to have pg_signal_backend provide an API that is more flexible here. Pushback to structure this differently is certainly possible though, and I'm happy to iterate the patch to address that. It might drift back toward something closer to Josh's original design.

--
Greg Smith   2ndQuadrant US    g...@2ndquadrant.com   Baltimore, MD
PostgreSQL Training, Services, and 24x7 Support  www.2ndQuadrant.us

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index e7f7fe0..f145c3f 100644
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
*************** SELECT set_config('log_statement_stats',
*** 14244,14251 ****
     <para>
      The functions shown in <xref
      linkend="functions-admin-signal-table"> send control signals to
!     other server processes.  Use of these functions is restricted
!     to superusers.
     </para>
  
     <table id="functions-admin-signal-table">
--- 14244,14251 ----
     <para>
      The functions shown in <xref
      linkend="functions-admin-signal-table"> send control signals to
!     other server processes.  Use of these functions is usually restricted
!     to superusers, with noted exceptions.
     </para>
  
     <table id="functions-admin-signal-table">
*************** SELECT set_config('log_statement_stats',
*** 14262,14268 ****
          <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
          </entry>
         <entry><type>boolean</type></entry>
!        <entry>Cancel a backend's current query</entry>
        </row>
        <row>
         <entry>
--- 14262,14271 ----
          <literal><function>pg_cancel_backend(<parameter>pid</parameter> <type>int</>)</function></literal>
          </entry>
         <entry><type>boolean</type></entry>
!        <entry>Cancel a backend's current query.  You can execute this against
!         another backend that has exactly the same role as the user calling the
!         function.  In all other cases, you must be a superuser.
!         </entry>
        </row>
        <row>
         <entry>
*************** SELECT set_config('log_statement_stats',
*** 14304,14309 ****
--- 14307,14316 ----
      <command>postgres</command> processes on the server (using
      <application>ps</> on Unix or the <application>Task
      Manager</> on <productname>Windows</>).
+     For the more permissive <function>pg_cancel_backend</>, the role of an
+     active backend can be found from
+     the <structfield>usename</structfield> column of the
+     <structname>pg_stat_activity</structname> view.
     </para>
  
     <para>
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 7a2e0c8..b052149 100644
*** a/src/backend/utils/adt/misc.c
--- b/src/backend/utils/adt/misc.c
***************
*** 30,35 ****
--- 30,36 ----
  #include "postmaster/syslogger.h"
  #include "storage/fd.h"
  #include "storage/pmsignal.h"
+ #include "storage/proc.h"
  #include "storage/procarray.h"
  #include "tcop/tcopprot.h"
  #include "utils/builtins.h"
*************** current_query(PG_FUNCTION_ARGS)
*** 71,84 ****
  
  /*
   * Functions to send signals to other backends.
   */
  static bool
  pg_signal_backend(int pid, int sig)
  {
! 	if (!superuser())
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 			(errmsg("must be superuser to signal other server processes"))));
  
  	if (!IsBackendPid(pid))
  	{
--- 72,115 ----
  
  /*
   * Functions to send signals to other backends.
+  * 
+  * When calling pg_signal_backend, non-superusers are allowed to send signals
+  * to other backends if they are running as the same role.  Make sure you're
+  * comfortable with that before using it for other types of signaling.  If not,
+  * add your own checks first, as pg_terminate_backend does here.
   */
  static bool
  pg_signal_backend(int pid, int sig)
  {
! 	PGPROC	*proc;
! 	bool	allowed = false;
! 
! 	if (superuser())
! 		allowed = true;
! 	else
! 	{
! 		/*
! 		 * Check for matching roles if we've already failed the superuser test.
! 		 *
! 		 * Trust that BackendPidGetProc will return NULL if the pid isn't valid,
! 		 * even though the check for whether it's a backend process is below.
! 		 * The IsBackendPid check can't be relied on as definitive even if it
! 		 * was first.  The process might end between successive checks
! 		 * regardless of their order, and we don't want to acquire a lock just
! 		 * just to eliminate that possibility.  Since the signal being passed
! 		 * might be a request for cancellation, this is not necessarily even a
! 		 * problem.
! 		 */
! 		proc = BackendPidGetProc(pid);
! 
! 		if ((proc != NULL) && (proc->roleId == GetUserId()))
! 			allowed = true;
! 	}
! 
! 	if (!allowed)
  		ereport(ERROR,
  				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
! 			(errmsg("must be superuser or have the same role to signal other server processes"))));
  
  	if (!IsBackendPid(pid))
  	{
*************** pg_cancel_backend(PG_FUNCTION_ARGS)
*** 115,120 ****
--- 146,161 ----
  Datum
  pg_terminate_backend(PG_FUNCTION_ARGS)
  {
+ 	/*
+ 	 * Since the permissions check for signaling isn't as strict as for
+ 	 * termination, run the superuser only one here and suggest alternatives.
+ 	 */
+ 	if (!superuser())
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ 				 errmsg("must be superuser to terminate other server processes"),
+ 				 errhint("you can use pg_cancel_backend() on your own processes")));
+ 
  	PG_RETURN_BOOL(pg_signal_backend(PG_GETARG_INT32(0), SIGTERM));
  }
  
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to