summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2001-11-16 18:04:31 +0000
committerTom Lane2001-11-16 18:04:31 +0000
commit1ca717f377c71ff593d5d944133ef8939c1d4aee (patch)
tree62d24ed1b8f00168c68a67d82719b58014eea1e7
parentb0df7a60f2392d976a797fd6567114b18e3c9322 (diff)
plpython security and error handling fixes, from
Kevin Jacobs and Brad McLean.
-rw-r--r--src/pl/plpython/error.expected35
-rw-r--r--src/pl/plpython/plpython.c247
-rw-r--r--src/pl/plpython/plpython_error.sql8
-rw-r--r--src/pl/plpython/plpython_function.sql39
4 files changed, 272 insertions, 57 deletions
diff --git a/src/pl/plpython/error.expected b/src/pl/plpython/error.expected
index 9c9ac29ddf4..96de5da6603 100644
--- a/src/pl/plpython/error.expected
+++ b/src/pl/plpython/error.expected
@@ -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)
+
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index b749d8d5b54..056f01f19e7 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -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.
*/
diff --git a/src/pl/plpython/plpython_error.sql b/src/pl/plpython/plpython_error.sql
index 2f0486fed92..0cde4df9967 100644
--- a/src/pl/plpython/plpython_error.sql
+++ b/src/pl/plpython/plpython_error.sql
@@ -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();
diff --git a/src/pl/plpython/plpython_function.sql b/src/pl/plpython/plpython_function.sql
index bf8bf8bf9fc..46083ab2ba2 100644
--- a/src/pl/plpython/plpython_function.sql
+++ b/src/pl/plpython/plpython_function.sql
@@ -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';