OSDN Git Service

plpython security and error handling fixes, from
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 16 Nov 2001 18:04:31 +0000 (18:04 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 16 Nov 2001 18:04:31 +0000 (18:04 +0000)
Kevin Jacobs and Brad McLean.

src/pl/plpython/error.expected
src/pl/plpython/plpython.c
src/pl/plpython/plpython_error.sql
src/pl/plpython/plpython_function.sql

index 9c9ac29..96de5da 100644 (file)
@@ -1,19 +1,36 @@
 SELECT invalid_type_uncaught('rick');
-ERROR:  plpython: Call of function `__plpython_procedure_invalid_type_uncaught_1175341' failed.
+NOTICE:  plpython: in function __plpython_procedure_invalid_type_uncaught_49801:
 plpy.SPIError: Cache lookup for type `test' failed.
 SELECT invalid_type_caught('rick');
-NOTICE:  ("Cache lookup for type `test' failed.",)
- invalid_type_caught 
----------------------
-(1 row)
-
+NOTICE:  plpython: in function __plpython_procedure_invalid_type_caught_49802:
+plpy.SPIError: Cache lookup for type `test' failed.
 SELECT invalid_type_reraised('rick');
-ERROR:  plpython: Call of function `__plpython_procedure_invalid_type_reraised_1175343' failed.
-plpy.Error: ("Cache lookup for type `test' failed.",)
+NOTICE:  plpython: in function __plpython_procedure_invalid_type_reraised_49803:
+plpy.SPIError: Cache lookup for type `test' failed.
 SELECT valid_type('rick');
  valid_type 
 ------------
  
 (1 row)
 
+SELECT read_file('/etc/passwd');
+ERROR:  plpython: Call of function `__plpython_procedure_read_file_49809' failed.
+exceptions.IOError: can't open files in restricted mode
+SELECT write_file('/tmp/plpython','This is very bad');
+ERROR:  plpython: Call of function `__plpython_procedure_write_file_49810' failed.
+exceptions.IOError: can't open files in restricted mode
+SELECT getpid();
+ERROR:  plpython: Call of function `__plpython_procedure_getpid_49811' failed.
+exceptions.AttributeError: getpid
+SELECT uname();
+ERROR:  plpython: Call of function `__plpython_procedure_uname_49812' failed.
+exceptions.AttributeError: uname
+SELECT sys_exit();
+ERROR:  plpython: Call of function `__plpython_procedure_sys_exit_49813' failed.
+exceptions.AttributeError: exit
+SELECT sys_argv();
+    sys_argv    
+----------------
+ ['RESTRICTED']
+(1 row)
+
index b749d8d..056f01f 100644 (file)
@@ -29,7 +29,7 @@
  * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
  *
  * IDENTIFICATION
- *     $Header: /cvsroot/pgsql/src/pl/plpython/plpython.c,v 1.12 2001/11/05 17:46:39 momjian Exp $
+ *     $Header: /cvsroot/pgsql/src/pl/plpython/plpython.c,v 1.13 2001/11/16 18:04:31 tgl Exp $
  *
  *********************************************************************
  */
@@ -188,6 +188,10 @@ static void PLy_init_interp(void);
 static void PLy_init_safe_interp(void);
 static void PLy_init_plpy(void);
 
+/* Helper functions used during initialization */
+static int  populate_methods(PyObject *klass, PyMethodDef *methods);
+static PyObject *build_tuple(char* string_list[], int len);
+
 /* error handler.  collects the current Python exception, if any,
  * and appends it to the error and sends it to elog
  */
@@ -199,6 +203,10 @@ static void
 PLy_exception_set(PyObject *, const char *,...)
 __attribute__((format(printf, 2, 3)));
 
+/* Get the innermost python procedure called from the backend.
+ */
+static char *PLy_procedure_name(PLyProcedure *);
+
 /* some utility functions
  */
 static void *PLy_malloc(size_t);
@@ -240,6 +248,10 @@ static void PLy_input_datum_func2(PLyDatumToOb *, Form_pg_type);
 static void PLy_output_tuple_funcs(PLyTypeInfo *, TupleDesc);
 static void PLy_input_tuple_funcs(PLyTypeInfo *, TupleDesc);
 
+/* RExec methods
+ */
+static PyObject *PLy_r_open(PyObject *self, PyObject* args);
+
 /* conversion functions
  */
 static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc);
@@ -255,6 +267,11 @@ static PyObject *PLyString_FromString(const char *);
 static int     PLy_first_call = 1;
 static volatile int PLy_call_level = 0;
 
+/*
+ * Last function called by postgres backend
+ */
+static PLyProcedure *PLy_last_procedure = NULL;
+
 /* this gets modified in plpython_call_handler and PLy_elog.
  * test it any old where, but do NOT modify it anywhere except
  * those two functions
@@ -265,35 +282,60 @@ static PyObject *PLy_interp_globals = NULL;
 static PyObject *PLy_interp_safe = NULL;
 static PyObject *PLy_interp_safe_globals = NULL;
 static PyObject *PLy_importable_modules = NULL;
+static PyObject *PLy_ok_posix_names = NULL;
+static PyObject *PLy_ok_sys_names = NULL;
 static PyObject *PLy_procedure_cache = NULL;
 
-char      *PLy_importable_modules_list[] = {
+static char *PLy_importable_modules_list[] = {
        "array",
        "bisect",
+       "binascii",
        "calendar",
        "cmath",
+       "codecs",
        "errno",
        "marshal",
        "math",
        "md5",
        "mpz",
        "operator",
+       "pcre",
        "pickle",
        "random",
        "re",
+       "regex",
+       "sre",
        "sha",
        "string",
        "StringIO",
+       "struct",
        "time",
        "whrandom",
        "zlib"
 };
 
+static char *PLy_ok_posix_names_list[] = {
+       /* None for now */
+};
+
+static char *PLy_ok_sys_names_list[] = {
+       "byteeorder",
+       "copyright",
+       "getdefaultencoding",
+       "getrefcount",
+       "hexrevision",
+       "maxint",
+       "maxunicode",
+       "platform",
+       "version",
+       "version_info"
+};
+
 /* Python exceptions
  */
-PyObject   *PLy_exc_error = NULL;
-PyObject   *PLy_exc_fatal = NULL;
-PyObject   *PLy_exc_spi_error = NULL;
+static PyObject   *PLy_exc_error = NULL;
+static PyObject   *PLy_exc_fatal = NULL;
+static PyObject   *PLy_exc_spi_error = NULL;
 
 /* some globals for the python module
  */
@@ -334,7 +376,6 @@ perm_fmgr_info(Oid functionId, FmgrInfo *finfo)
        fmgr_info_cxt(functionId, finfo, TopMemoryContext);
 }
 
-
 Datum
 plpython_call_handler(PG_FUNCTION_ARGS)
 {
@@ -366,8 +407,10 @@ plpython_call_handler(PG_FUNCTION_ARGS)
                }
                else
                        PLy_restart_in_progress += 1;
-               if (proc)
+               if (proc) 
+               {
                        Py_DECREF(proc->me);
+               }
                RERAISE_EXC();
        }
 
@@ -805,7 +848,7 @@ PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure * proc)
        if (plrv == NULL)
        {
                elog(FATAL, "Aiieee, PLy_procedure_call returned NULL");
-#if 0
+#ifdef NOT_USED
                if (!PLy_restart_in_progress)
                        PLy_elog(ERROR, "plpython: Function \"%s\" failed.", proc->proname);
 
@@ -853,11 +896,15 @@ PyObject *
 PLy_procedure_call(PLyProcedure * proc, char *kargs, PyObject * vargs)
 {
        PyObject   *rv;
+       PLyProcedure *current;
 
        enter();
 
+       current = PLy_last_procedure;
+       PLy_last_procedure = proc;
        PyDict_SetItemString(proc->globals, kargs, vargs);
        rv = PyObject_CallFunction(proc->reval, "O", proc->code);
+       PLy_last_procedure = current;
 
        if ((rv == NULL) || (PyErr_Occurred()))
        {
@@ -1150,12 +1197,6 @@ PLy_procedure_compile(PLyProcedure * proc, const char *src)
        if ((proc->interp == NULL) || (PyErr_Occurred()))
                PLy_elog(ERROR, "Unable to create rexec.RExec instance");
 
-       /*
-        * tweak the list of permitted modules
-        */
-       PyObject_SetAttrString(proc->interp, "ok_builtin_modules",
-                                                  PLy_importable_modules);
-
        proc->reval = PyObject_GetAttrString(proc->interp, "r_eval");
        if ((proc->reval == NULL) || (PyErr_Occurred()))
                PLy_elog(ERROR, "Unable to get method `r_eval' from rexec.RExec");
@@ -1632,9 +1673,12 @@ static PyObject *PLy_plan_status(PyObject *, PyObject *);
 static PyObject *PLy_result_new(void);
 static void PLy_result_dealloc(PyObject *);
 static PyObject *PLy_result_getattr(PyObject *, char *);
+#ifdef NOT_USED
+/* Appear to be unused */
 static PyObject *PLy_result_fetch(PyObject *, PyObject *);
 static PyObject *PLy_result_nrows(PyObject *, PyObject *);
 static PyObject *PLy_result_status(PyObject *, PyObject *);
+#endif
 static int     PLy_result_length(PyObject *);
 static PyObject *PLy_result_item(PyObject *, int);
 static PyObject *PLy_result_slice(PyObject *, int, int);
@@ -1650,7 +1694,7 @@ static PyObject *PLy_spi_execute_plan(PyObject *, PyObject *, int);
 static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *, int, int);
 
 
-PyTypeObject PLy_PlanType = {
+static PyTypeObject PLy_PlanType = {
        PyObject_HEAD_INIT(NULL)
        0,                                                      /* ob_size */
        "PLyPlan",                                      /* tp_name */
@@ -1679,13 +1723,13 @@ PyTypeObject PLy_PlanType = {
        PLy_plan_doc,                           /* tp_doc */
 };
 
-PyMethodDef PLy_plan_methods[] = {
+static PyMethodDef PLy_plan_methods[] = {
        {"status", (PyCFunction) PLy_plan_status, METH_VARARGS, NULL},
        {NULL, NULL, 0, NULL}
 };
 
 
-PySequenceMethods PLy_result_as_sequence = {
+static PySequenceMethods PLy_result_as_sequence = {
        (inquiry) PLy_result_length,    /* sq_length */
        (binaryfunc) 0,                         /* sq_concat */
        (intargfunc) 0,                         /* sq_repeat */
@@ -1695,7 +1739,7 @@ PySequenceMethods PLy_result_as_sequence = {
        (intintobjargproc) PLy_result_ass_slice,        /* sq_ass_slice */
 };
 
-PyTypeObject PLy_ResultType = {
+static PyTypeObject PLy_ResultType = {
        PyObject_HEAD_INIT(NULL)
        0,                                                      /* ob_size */
        "PLyResult",                            /* tp_name */
@@ -1723,14 +1767,15 @@ PyTypeObject PLy_ResultType = {
        0,                                                      /* tp_xxx4 */
        PLy_result_doc,                         /* tp_doc */
 };
-
-PyMethodDef PLy_result_methods[] = {
+#ifdef NOT_USED
+/* Appear to be unused */
+static PyMethodDef PLy_result_methods[] = {
        {"fetch", (PyCFunction) PLy_result_fetch, METH_VARARGS, NULL,},
        {"nrows", (PyCFunction) PLy_result_nrows, METH_VARARGS, NULL},
        {"status", (PyCFunction) PLy_result_status, METH_VARARGS, NULL},
        {NULL, NULL, 0, NULL}
 };
-
+#endif
 
 static PyMethodDef PLy_methods[] = {
        /*
@@ -1833,7 +1878,7 @@ PLy_plan_status(PyObject * self, PyObject * args)
 /* result object methods
  */
 
-static PyObject *
+PyObject *
 PLy_result_new(void)
 {
        PLyResultObject *ob;
@@ -1853,7 +1898,7 @@ PLy_result_new(void)
        return (PyObject *) ob;
 }
 
-static void
+void
 PLy_result_dealloc(PyObject * arg)
 {
        PLyResultObject *ob = (PLyResultObject *) arg;
@@ -1867,19 +1912,20 @@ PLy_result_dealloc(PyObject * arg)
        PyMem_DEL(ob);
 }
 
-static PyObject *
+PyObject *
 PLy_result_getattr(PyObject * self, char *attr)
 {
        return NULL;
 }
-
-static PyObject *
+#ifdef NOT_USED
+/* Appear to be unused */
+PyObject *
 PLy_result_fetch(PyObject * self, PyObject * args)
 {
        return NULL;
 }
 
-static PyObject *
+PyObject *
 PLy_result_nrows(PyObject * self, PyObject * args)
 {
        PLyResultObject *ob = (PLyResultObject *) self;
@@ -1888,7 +1934,7 @@ PLy_result_nrows(PyObject * self, PyObject * args)
        return ob->nrows;
 }
 
-static PyObject *
+PyObject *
 PLy_result_status(PyObject * self, PyObject * args)
 {
        PLyResultObject *ob = (PLyResultObject *) self;
@@ -1896,7 +1942,7 @@ PLy_result_status(PyObject * self, PyObject * args)
        Py_INCREF(ob->status);
        return ob->status;
 }
-
+#endif
 int
 PLy_result_length(PyObject * arg)
 {
@@ -1991,7 +2037,8 @@ PLy_spi_prepare(PyObject * self, PyObject * args)
                if (!PyErr_Occurred())
                        PyErr_SetString(PLy_exc_spi_error,
                                                        "Unknown error in PLy_spi_prepare.");
-               return NULL;
+               PLy_elog(NOTICE,"in function %s:",PLy_procedure_name(PLy_last_procedure));
+               RERAISE_EXC();
        }
 
        if (list != NULL)
@@ -2097,7 +2144,7 @@ PLy_spi_execute(PyObject * self, PyObject * args)
 
        enter();
 
-#if 0
+#ifdef NOT_USED
 
        /*
         * there should - hahaha - be an python exception set so just return
@@ -2187,7 +2234,8 @@ PLy_spi_execute_plan(PyObject * ob, PyObject * list, int limit)
                if (!PyErr_Occurred())
                        PyErr_SetString(PLy_exc_error,
                                                        "Unknown error in PLy_spi_execute_plan");
-               return NULL;
+               PLy_elog(NOTICE,"in function %s:",PLy_procedure_name(PLy_last_procedure));
+               RERAISE_EXC();
        }
 
        if (nargs)
@@ -2249,16 +2297,15 @@ PLy_spi_execute_query(char *query, int limit)
        if (TRAP_EXC())
        {
                RESTORE_EXC();
-
                if ((!PLy_restart_in_progress) && (!PyErr_Occurred()))
                        PyErr_SetString(PLy_exc_spi_error,
                                                        "Unknown error in PLy_spi_execute_query.");
-               return NULL;
+               PLy_elog(NOTICE,"in function %s:",PLy_procedure_name(PLy_last_procedure));
+               RERAISE_EXC();
        }
 
        rv = SPI_exec(query, limit);
        RESTORE_EXC();
-
        if (rv < 0)
        {
                PLy_exception_set(PLy_exc_spi_error,
@@ -2311,7 +2358,7 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status)
                                                "Unknown error in PLy_spi_execute_fetch_result");
                        Py_DECREF(result);
                        PLy_typeinfo_dealloc(&args);
-                       return NULL;
+                       RERAISE_EXC();
                }
 
                if (rows)
@@ -2450,13 +2497,33 @@ PLy_init_plpy(void)
                elog(ERROR, "Unable to init plpy.");
 }
 
+/*
+ *  New RExec methods
+ */
+
+PyObject* 
+PLy_r_open(PyObject *self, PyObject* args)
+{
+       PyErr_SetString(PyExc_IOError, "can't open files in restricted mode");
+       return NULL;
+}
+
+
+static PyMethodDef PLy_r_exec_methods[] = {
+       {"r_open", (PyCFunction)PLy_r_open, METH_VARARGS, NULL},
+       {NULL, NULL, 0, NULL}
+};
+
+/*
+ *  Init new RExec
+ */
+
 void
 PLy_init_safe_interp(void)
 {
-       PyObject   *rmod;
+       PyObject   *rmod, *rexec, *rexec_dict;
        char       *rname = "rexec";
-       int                     i,
-                               imax;
+       int         len;
 
        enter();
 
@@ -2467,19 +2534,93 @@ PLy_init_safe_interp(void)
        PyDict_SetItemString(PLy_interp_globals, rname, rmod);
        PLy_interp_safe = rmod;
 
-       imax = sizeof(PLy_importable_modules_list) / sizeof(char *);
-       PLy_importable_modules = PyTuple_New(imax);
-       for (i = 0; i < imax; i++)
-       {
-               PyObject   *m = PyString_FromString(PLy_importable_modules_list[i]);
+       len = sizeof(PLy_importable_modules_list) / sizeof(char *);
+       PLy_importable_modules = build_tuple(PLy_importable_modules_list, len);
 
-               PyTuple_SetItem(PLy_importable_modules, i, m);
-       }
+       len = sizeof(PLy_ok_posix_names_list) / sizeof(char *);
+       PLy_ok_posix_names = build_tuple(PLy_ok_posix_names_list, len);
+
+       len = sizeof(PLy_ok_sys_names_list) / sizeof(char *);
+       PLy_ok_sys_names = build_tuple(PLy_ok_sys_names_list, len);
 
        PLy_interp_safe_globals = PyDict_New();
        if (PLy_interp_safe_globals == NULL)
                PLy_elog(ERROR, "Unable to create shared global dictionary.");
 
+       /*
+        * get an rexec.RExec class
+        */
+       rexec = PyDict_GetItemString(PyModule_GetDict(rmod), "RExec");
+
+       if (rexec == NULL || !PyClass_Check(rexec))
+               PLy_elog(ERROR, "Unable to get RExec object.");
+
+
+       rexec_dict = ((PyClassObject*)rexec)->cl_dict;
+
+       /*
+        * tweak the list of permitted modules, posix and sys functions 
+        */
+       PyDict_SetItemString(rexec_dict, "ok_builtin_modules", PLy_importable_modules);
+       PyDict_SetItemString(rexec_dict, "ok_posix_names",     PLy_ok_posix_names);
+       PyDict_SetItemString(rexec_dict, "ok_sys_names",       PLy_ok_sys_names);
+
+       /*
+        * change the r_open behavior
+        */
+       if( populate_methods(rexec, PLy_r_exec_methods) )
+               PLy_elog(ERROR, "Failed to update RExec methods.");
+}
+
+/* Helper function to build tuples from string lists */
+static
+PyObject *build_tuple(char* string_list[], int len)
+{
+       PyObject *tup = PyTuple_New(len);
+       int i;
+       for (i = 0; i < len; i++)
+       {
+               PyObject *m = PyString_FromString(string_list[i]);
+
+               PyTuple_SetItem(tup, i, m);
+       }
+       return tup;
+}
+
+/* Helper function for populating a class with method wrappers. */
+static int
+populate_methods(PyObject *klass, PyMethodDef *methods)
+{
+       if (!klass || !methods)
+               return 0;
+
+       for ( ; methods->ml_name; ++methods) {
+
+               /* get a wrapper for the built-in function */   
+               PyObject *func = PyCFunction_New(methods, NULL);
+               PyObject *meth;
+               int status;
+
+               if (!func)
+                       return -1;
+
+               /* turn the function into an unbound method */  
+               if (!(meth = PyMethod_New(func, NULL, klass))) {
+                       Py_DECREF(func);
+                       return -1;
+               }
+
+               /* add method to dictionary */
+               status = PyDict_SetItemString( ((PyClassObject*)klass)->cl_dict, 
+                                               methods->ml_name, meth);
+               Py_DECREF(meth);
+               Py_DECREF(func);
+
+               /* stop now if an error occurred, otherwise do the next method */
+               if (status)
+                       return status;
+       }
+       return 0;
 }
 
 
@@ -2566,7 +2707,7 @@ PLy_log(volatile int level, PyObject * self, PyObject * args)
                 * hideously.
                 */
                elog(FATAL, "plpython: Aiieee, elog threw an unknown exception!");
-               return NULL;
+               RERAISE_EXC();
        }
 
        elog(level, sv);
@@ -2584,6 +2725,18 @@ PLy_log(volatile int level, PyObject * self, PyObject * args)
 }
 
 
+/* Get the last procedure name called by the backend ( the innermost,
+ * If a plpython procedure call calls the backend and the backend calls
+ * another plpython procedure )
+ */
+
+char *PLy_procedure_name(PLyProcedure *proc)
+{
+        if ( proc == NULL )
+               return "<unknown procedure>";
+        return proc->proname;
+}
+
 /* output a python traceback/exception via the postgresql elog
  * function.  not pretty.
  */
index 2f0486f..0cde4df 100644 (file)
@@ -7,3 +7,11 @@ SELECT invalid_type_uncaught('rick');
 SELECT invalid_type_caught('rick');
 SELECT invalid_type_reraised('rick');
 SELECT valid_type('rick');
+
+-- Security sandbox tests
+SELECT read_file('/etc/passwd');
+SELECT write_file('/tmp/plpython','This is very bad');
+SELECT getpid();
+SELECT uname();
+SELECT sys_exit();
+SELECT sys_argv();
index bf8bf8b..46083ab 100644 (file)
@@ -257,6 +257,12 @@ if len(rv):
 return None
 '
        LANGUAGE 'plpython';
+/* Flat out syntax error
+*/
+CREATE FUNCTION sql_syntax_error() RETURNS text
+        AS
+'plpy.execute("syntax error")'
+        LANGUAGE 'plpython';
 
 /* check the handling of uncaught python exceptions
  */
@@ -287,5 +293,36 @@ return seq
 '
        LANGUAGE 'plpython';
 
-
+CREATE OR REPLACE FUNCTION read_file(text) RETURNS text AS '
+  return open(args[0]).read()
+' LANGUAGE 'plpython';
+
+CREATE OR REPLACE FUNCTION write_file(text,text) RETURNS text AS '
+  open(args[0],"w").write(args[1])
+' LANGUAGE 'plpython';
+
+CREATE OR REPLACE FUNCTION getpid() RETURNS int4 AS '
+  import os
+  return os.getpid()
+' LANGUAGE 'plpython';
+
+CREATE OR REPLACE FUNCTION uname() RETURNS int4 AS '
+  import os
+  return os.uname()
+' LANGUAGE 'plpython';
+
+CREATE OR REPLACE FUNCTION sys_exit() RETURNS text AS '
+  import sys
+  return sys.exit()
+' LANGUAGE 'plpython';
+
+CREATE OR REPLACE FUNCTION sys_argv() RETURNS text AS '
+  import sys
+  return str(sys.argv)
+' LANGUAGE 'plpython';
+
+CREATE OR REPLACE FUNCTION sys_version() RETURNS text AS '
+  import sys
+  return str(sys.version)
+' LANGUAGE 'plpython';