plpython security and error handling fixes, from
authorTom Lane
Fri, 16 Nov 2001 18:04:31 +0000 (18:04 +0000)
committerTom Lane
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 9c9ac29ddf4676f387d35b2af0f8e665e03bffdb..96de5da6603a645b31852b06cf0ad7c7f4a03af8 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 b749d8d5b54832a818dc80942de4aad9f188cb72..056f01f19e7bebd4ac71fbea6cf00f62782a38db 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 "";
+        return proc->proname;
+}
+
 /* output a python traceback/exception via the postgresql elog
  * function.  not pretty.
  */
index 2f0486fed9231ebd6da05900f8291c5be0d233b2..0cde4df9967f84e297f504f06544858aa9c2bc41 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 bf8bf8bf9fce7e7d9e221f72280c9554c9f06f5b..46083ab2ba2554b8f544a8dd8a4cbd4e0e07356e 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';