]> granicus.if.org Git - postgresql/blob - src/pl/plpython/plpy_main.c
Prevent privilege escalation in explicit calls to PL validators.
[postgresql] / src / pl / plpython / plpy_main.c
1 /*
2  * PL/Python main entry points
3  *
4  * src/pl/plpython/plpy_main.c
5  */
6
7 #include "postgres.h"
8
9 #include "access/htup_details.h"
10 #include "catalog/pg_proc.h"
11 #include "catalog/pg_type.h"
12 #include "commands/trigger.h"
13 #include "executor/spi.h"
14 #include "miscadmin.h"
15 #include "utils/guc.h"
16 #include "utils/memutils.h"
17 #include "utils/rel.h"
18 #include "utils/syscache.h"
19
20 #include "plpython.h"
21
22 #include "plpy_main.h"
23
24 #include "plpy_elog.h"
25 #include "plpy_exec.h"
26 #include "plpy_plpymodule.h"
27 #include "plpy_procedure.h"
28 #include "plpy_subxactobject.h"
29
30
31 /*
32  * exported functions
33  */
34
35 #if PY_MAJOR_VERSION >= 3
36 /* Use separate names to avoid clash in pg_pltemplate */
37 #define plpython_validator plpython3_validator
38 #define plpython_call_handler plpython3_call_handler
39 #define plpython_inline_handler plpython3_inline_handler
40 #endif
41
42 extern void _PG_init(void);
43 extern Datum plpython_validator(PG_FUNCTION_ARGS);
44 extern Datum plpython_call_handler(PG_FUNCTION_ARGS);
45 extern Datum plpython_inline_handler(PG_FUNCTION_ARGS);
46
47 #if PY_MAJOR_VERSION < 3
48 /* Define aliases plpython2_call_handler etc */
49 extern Datum plpython2_validator(PG_FUNCTION_ARGS);
50 extern Datum plpython2_call_handler(PG_FUNCTION_ARGS);
51 extern Datum plpython2_inline_handler(PG_FUNCTION_ARGS);
52 #endif
53
54 PG_MODULE_MAGIC;
55
56 PG_FUNCTION_INFO_V1(plpython_validator);
57 PG_FUNCTION_INFO_V1(plpython_call_handler);
58 PG_FUNCTION_INFO_V1(plpython_inline_handler);
59
60 #if PY_MAJOR_VERSION < 3
61 PG_FUNCTION_INFO_V1(plpython2_validator);
62 PG_FUNCTION_INFO_V1(plpython2_call_handler);
63 PG_FUNCTION_INFO_V1(plpython2_inline_handler);
64 #endif
65
66
67 static bool PLy_procedure_is_trigger(Form_pg_proc procStruct);
68 static void plpython_error_callback(void *arg);
69 static void plpython_inline_error_callback(void *arg);
70 static void PLy_init_interp(void);
71
72 static PLyExecutionContext *PLy_push_execution_context(void);
73 static void PLy_pop_execution_context(void);
74
75 static const int plpython_python_version = PY_MAJOR_VERSION;
76
77 /* initialize global variables */
78 PyObject   *PLy_interp_globals = NULL;
79
80 /* this doesn't need to be global; use PLy_current_execution_context() */
81 static PLyExecutionContext *PLy_execution_contexts = NULL;
82
83
84 void
85 _PG_init(void)
86 {
87         /* Be sure we do initialization only once (should be redundant now) */
88         static bool inited = false;
89         const int **version_ptr;
90
91         if (inited)
92                 return;
93
94         /* Be sure we don't run Python 2 and 3 in the same session (might crash) */
95         version_ptr = (const int **) find_rendezvous_variable("plpython_python_version");
96         if (!(*version_ptr))
97                 *version_ptr = &plpython_python_version;
98         else
99         {
100                 if (**version_ptr != plpython_python_version)
101                         ereport(FATAL,
102                                         (errmsg("Python major version mismatch in session"),
103                                          errdetail("This session has previously used Python major version %d, and it is now attempting to use Python major version %d.",
104                                                            **version_ptr, plpython_python_version),
105                                          errhint("Start a new session to use a different Python major version.")));
106         }
107
108         pg_bindtextdomain(TEXTDOMAIN);
109
110 #if PY_MAJOR_VERSION >= 3
111         PyImport_AppendInittab("plpy", PyInit_plpy);
112 #endif
113         Py_Initialize();
114 #if PY_MAJOR_VERSION >= 3
115         PyImport_ImportModule("plpy");
116 #endif
117         PLy_init_interp();
118         PLy_init_plpy();
119         if (PyErr_Occurred())
120                 PLy_elog(FATAL, "untrapped error in initialization");
121
122         init_procedure_caches();
123
124         explicit_subtransactions = NIL;
125
126         PLy_execution_contexts = NULL;
127
128         inited = true;
129 }
130
131 /*
132  * This should only be called once from _PG_init. Initialize the Python
133  * interpreter and global data.
134  */
135 void
136 PLy_init_interp(void)
137 {
138         static PyObject *PLy_interp_safe_globals = NULL;
139         PyObject   *mainmod;
140
141         mainmod = PyImport_AddModule("__main__");
142         if (mainmod == NULL || PyErr_Occurred())
143                 PLy_elog(ERROR, "could not import \"__main__\" module");
144         Py_INCREF(mainmod);
145         PLy_interp_globals = PyModule_GetDict(mainmod);
146         PLy_interp_safe_globals = PyDict_New();
147         if (PLy_interp_safe_globals == NULL)
148                 PLy_elog(ERROR, "could not create globals");
149         PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals);
150         Py_DECREF(mainmod);
151         if (PLy_interp_globals == NULL || PyErr_Occurred())
152                 PLy_elog(ERROR, "could not initialize globals");
153 }
154
155 Datum
156 plpython_validator(PG_FUNCTION_ARGS)
157 {
158         Oid                     funcoid = PG_GETARG_OID(0);
159         HeapTuple       tuple;
160         Form_pg_proc procStruct;
161         bool            is_trigger;
162
163         if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
164                 PG_RETURN_VOID();
165
166         if (!check_function_bodies)
167         {
168                 PG_RETURN_VOID();
169         }
170
171         /* Get the new function's pg_proc entry */
172         tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
173         if (!HeapTupleIsValid(tuple))
174                 elog(ERROR, "cache lookup failed for function %u", funcoid);
175         procStruct = (Form_pg_proc) GETSTRUCT(tuple);
176
177         is_trigger = PLy_procedure_is_trigger(procStruct);
178
179         ReleaseSysCache(tuple);
180
181         /* We can't validate triggers against any particular table ... */
182         PLy_procedure_get(funcoid, InvalidOid, is_trigger);
183
184         PG_RETURN_VOID();
185 }
186
187 #if PY_MAJOR_VERSION < 3
188 Datum
189 plpython2_validator(PG_FUNCTION_ARGS)
190 {
191         /* call plpython validator with our fcinfo so it gets our oid */
192         return plpython_validator(fcinfo);
193 }
194 #endif   /* PY_MAJOR_VERSION < 3 */
195
196 Datum
197 plpython_call_handler(PG_FUNCTION_ARGS)
198 {
199         Datum           retval;
200         PLyExecutionContext *exec_ctx;
201         ErrorContextCallback plerrcontext;
202
203         /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
204         if (SPI_connect() != SPI_OK_CONNECT)
205                 elog(ERROR, "SPI_connect failed");
206
207         /*
208          * Push execution context onto stack.  It is important that this get
209          * popped again, so avoid putting anything that could throw error between
210          * here and the PG_TRY.  (plpython_error_callback expects the stack entry
211          * to be there, so we have to make the context first.)
212          */
213         exec_ctx = PLy_push_execution_context();
214
215         /*
216          * Setup error traceback support for ereport()
217          */
218         plerrcontext.callback = plpython_error_callback;
219         plerrcontext.previous = error_context_stack;
220         error_context_stack = &plerrcontext;
221
222         PG_TRY();
223         {
224                 Oid                     funcoid = fcinfo->flinfo->fn_oid;
225                 PLyProcedure *proc;
226
227                 if (CALLED_AS_TRIGGER(fcinfo))
228                 {
229                         Relation        tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
230                         HeapTuple       trv;
231
232                         proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true);
233                         exec_ctx->curr_proc = proc;
234                         trv = PLy_exec_trigger(fcinfo, proc);
235                         retval = PointerGetDatum(trv);
236                 }
237                 else
238                 {
239                         proc = PLy_procedure_get(funcoid, InvalidOid, false);
240                         exec_ctx->curr_proc = proc;
241                         retval = PLy_exec_function(fcinfo, proc);
242                 }
243         }
244         PG_CATCH();
245         {
246                 PLy_pop_execution_context();
247                 PyErr_Clear();
248                 PG_RE_THROW();
249         }
250         PG_END_TRY();
251
252         /* Pop the error context stack */
253         error_context_stack = plerrcontext.previous;
254         /* ... and then the execution context */
255         PLy_pop_execution_context();
256
257         return retval;
258 }
259
260 #if PY_MAJOR_VERSION < 3
261 Datum
262 plpython2_call_handler(PG_FUNCTION_ARGS)
263 {
264         return plpython_call_handler(fcinfo);
265 }
266 #endif   /* PY_MAJOR_VERSION < 3 */
267
268 Datum
269 plpython_inline_handler(PG_FUNCTION_ARGS)
270 {
271         InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
272         FunctionCallInfoData fake_fcinfo;
273         FmgrInfo        flinfo;
274         PLyProcedure proc;
275         PLyExecutionContext *exec_ctx;
276         ErrorContextCallback plerrcontext;
277
278         /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
279         if (SPI_connect() != SPI_OK_CONNECT)
280                 elog(ERROR, "SPI_connect failed");
281
282         MemSet(&fake_fcinfo, 0, sizeof(fake_fcinfo));
283         MemSet(&flinfo, 0, sizeof(flinfo));
284         fake_fcinfo.flinfo = &flinfo;
285         flinfo.fn_oid = InvalidOid;
286         flinfo.fn_mcxt = CurrentMemoryContext;
287
288         MemSet(&proc, 0, sizeof(PLyProcedure));
289         proc.pyname = PLy_strdup("__plpython_inline_block");
290         proc.result.out.d.typoid = VOIDOID;
291
292         /*
293          * Push execution context onto stack.  It is important that this get
294          * popped again, so avoid putting anything that could throw error between
295          * here and the PG_TRY.  (plpython_inline_error_callback doesn't currently
296          * need the stack entry, but for consistency with plpython_call_handler we
297          * do it in this order.)
298          */
299         exec_ctx = PLy_push_execution_context();
300
301         /*
302          * Setup error traceback support for ereport()
303          */
304         plerrcontext.callback = plpython_inline_error_callback;
305         plerrcontext.previous = error_context_stack;
306         error_context_stack = &plerrcontext;
307
308         PG_TRY();
309         {
310                 PLy_procedure_compile(&proc, codeblock->source_text);
311                 exec_ctx->curr_proc = &proc;
312                 PLy_exec_function(&fake_fcinfo, &proc);
313         }
314         PG_CATCH();
315         {
316                 PLy_pop_execution_context();
317                 PLy_procedure_delete(&proc);
318                 PyErr_Clear();
319                 PG_RE_THROW();
320         }
321         PG_END_TRY();
322
323         /* Pop the error context stack */
324         error_context_stack = plerrcontext.previous;
325         /* ... and then the execution context */
326         PLy_pop_execution_context();
327
328         /* Now clean up the transient procedure we made */
329         PLy_procedure_delete(&proc);
330
331         PG_RETURN_VOID();
332 }
333
334 #if PY_MAJOR_VERSION < 3
335 Datum
336 plpython2_inline_handler(PG_FUNCTION_ARGS)
337 {
338         return plpython_inline_handler(fcinfo);
339 }
340 #endif   /* PY_MAJOR_VERSION < 3 */
341
342 static bool
343 PLy_procedure_is_trigger(Form_pg_proc procStruct)
344 {
345         return (procStruct->prorettype == TRIGGEROID ||
346                         (procStruct->prorettype == OPAQUEOID &&
347                          procStruct->pronargs == 0));
348 }
349
350 static void
351 plpython_error_callback(void *arg)
352 {
353         PLyExecutionContext *exec_ctx = PLy_current_execution_context();
354
355         if (exec_ctx->curr_proc)
356                 errcontext("PL/Python function \"%s\"",
357                                    PLy_procedure_name(exec_ctx->curr_proc));
358 }
359
360 static void
361 plpython_inline_error_callback(void *arg)
362 {
363         errcontext("PL/Python anonymous code block");
364 }
365
366 PLyExecutionContext *
367 PLy_current_execution_context(void)
368 {
369         if (PLy_execution_contexts == NULL)
370                 elog(ERROR, "no Python function is currently executing");
371
372         return PLy_execution_contexts;
373 }
374
375 static PLyExecutionContext *
376 PLy_push_execution_context(void)
377 {
378         PLyExecutionContext *context = PLy_malloc(sizeof(PLyExecutionContext));
379
380         context->curr_proc = NULL;
381         context->scratch_ctx = AllocSetContextCreate(TopTransactionContext,
382                                                                                                  "PL/Python scratch context",
383                                                                                                  ALLOCSET_DEFAULT_MINSIZE,
384                                                                                                  ALLOCSET_DEFAULT_INITSIZE,
385                                                                                                  ALLOCSET_DEFAULT_MAXSIZE);
386         context->next = PLy_execution_contexts;
387         PLy_execution_contexts = context;
388         return context;
389 }
390
391 static void
392 PLy_pop_execution_context(void)
393 {
394         PLyExecutionContext *context = PLy_execution_contexts;
395
396         if (context == NULL)
397                 elog(ERROR, "no Python function is currently executing");
398
399         PLy_execution_contexts = context->next;
400
401         MemoryContextDelete(context->scratch_ctx);
402         PLy_free(context);
403 }