Skip to content

Commit

Permalink
Implement phpGH-17321: Add setAuthorizer to Pdo\Sqlite
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsdos committed Feb 23, 2025
1 parent 67a349d commit c85cd94
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 14 deletions.
3 changes: 3 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ PHP 8.5 UPGRADE NOTES
. Added enchant_dict_remove() to put a word on the exclusion list and
remove it from the session dictionary.

- Pdo\Sqlite:
. Added support for setAuthorizer() like Sqlite3 has.

- PGSQL:
. pg_close_stmt offers an alternative way to close a prepared
statement from the DEALLOCATE sql command in that we can reuse
Expand Down
30 changes: 30 additions & 0 deletions ext/pdo_sqlite/pdo_sqlite.c
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,36 @@ PHP_METHOD(Pdo_Sqlite, openBlob)
}
}

PHP_METHOD(Pdo_Sqlite, setAuthorizer)
{
zend_fcall_info fci = empty_fcall_info;
zend_fcall_info_cache fcc = empty_fcall_info_cache;

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE_OR_NULL(fci, fcc)
ZEND_PARSE_PARAMETERS_END();

pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
PDO_CONSTRUCT_CHECK_WITH_CLEANUP(free_fcc);
pdo_sqlite_db_handle *db_handle = (pdo_sqlite_db_handle *) dbh->driver_data;

/* Clear previously set callback */
if (ZEND_FCC_INITIALIZED(db_handle->authorizer_fcc)) {
zend_fcc_dtor(&db_handle->authorizer_fcc);
}

/* Only enable userland authorizer if argument is not NULL */
if (ZEND_FCI_INITIALIZED(fci)) {
zend_fcc_dup(&db_handle->authorizer_fcc, &fcc);
}

return;

free_fcc:
zend_release_fcall_info_cache(&fcc);
RETURN_THROWS();
}

static int php_sqlite_collation_callback(void *context, int string1_len, const void *string1,
int string2_len, const void *string2)
{
Expand Down
13 changes: 13 additions & 0 deletions ext/pdo_sqlite/pdo_sqlite.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ class Sqlite extends \PDO

/** @cvalue PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES */
public const int ATTR_EXTENDED_RESULT_CODES = UNKNOWN;
/** @cvalue SQLITE_OK */

/** @cvalue SQLITE_OK */
public const int OK = UNKNOWN;

/* Constants for authorizer return */

/** @cvalue SQLITE_DENY */
public const int DENY = UNKNOWN;
/** @cvalue SQLITE_IGNORE */
public const int IGNORE = UNKNOWN;

// Registers an aggregating User Defined Function for use in SQL statements
public function createAggregate(
Expand Down Expand Up @@ -63,4 +74,6 @@ public function openBlob(
?string $dbname = "main",
int $flags = \Pdo\Sqlite::OPEN_READONLY
) {}

public function setAuthorizer(?callable $callback): void {}
}
26 changes: 25 additions & 1 deletion ext/pdo_sqlite/pdo_sqlite_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/pdo_sqlite/php_pdo_sqlite_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ typedef struct {
pdo_sqlite_error_info einfo;
struct pdo_sqlite_func *funcs;
struct pdo_sqlite_collation *collations;
zend_fcall_info_cache authorizer_fcc;
} pdo_sqlite_db_handle;

typedef struct {
Expand Down
77 changes: 64 additions & 13 deletions ext/pdo_sqlite/sqlite_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ static void pdo_sqlite_cleanup_callbacks(pdo_sqlite_db_handle *H)
{
struct pdo_sqlite_func *func;

if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) {
zend_fcc_dtor(&H->authorizer_fcc);
}

while (H->funcs) {
func = H->funcs;
H->funcs = func->next;
Expand Down Expand Up @@ -701,6 +705,10 @@ static void pdo_sqlite_get_gc(pdo_dbh_t *dbh, zend_get_gc_buffer *gc_buffer)
{
pdo_sqlite_db_handle *H = dbh->driver_data;

if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) {
zend_get_gc_buffer_add_fcc(gc_buffer, &H->authorizer_fcc);
}

struct pdo_sqlite_func *func = H->funcs;
while (func) {
if (ZEND_FCC_INITIALIZED(func->func)) {
Expand Down Expand Up @@ -771,24 +779,69 @@ static char *make_filename_safe(const char *filename)
return estrdup(filename);
}

static int authorizer(void *autharg, int access_type, const char *arg3, const char *arg4,
const char *arg5, const char *arg6)
#define ZVAL_NULLABLE_STRING(zv, str) do { \
if ((str)) { \
ZVAL_STRING((zv), (str)); \
} else { \
ZVAL_NULL(zv); \
} \
} while (0)

static int authorizer(void *autharg, int access_type, const char *arg1, const char *arg2,
const char *arg3, const char *arg4)
{
char *filename;
switch (access_type) {
case SQLITE_ATTACH: {
filename = make_filename_safe(arg3);
if (PG(open_basedir) && *PG(open_basedir)) {
if (access_type == SQLITE_ATTACH) {
char *filename = make_filename_safe(arg1);
if (!filename) {
return SQLITE_DENY;
}
efree(filename);
return SQLITE_OK;
}
}

default:
/* access allowed */
return SQLITE_OK;
pdo_sqlite_db_handle *db_obj = autharg;

/* fallback to access allowed if authorizer callback is not defined */
if (!ZEND_FCC_INITIALIZED(db_obj->authorizer_fcc)) {
return SQLITE_OK;
}

/* call userland authorizer callback, if set */
zval retval;
zval argv[5];

ZVAL_LONG(&argv[0], access_type);
ZVAL_NULLABLE_STRING(&argv[1], arg1);
ZVAL_NULLABLE_STRING(&argv[2], arg2);
ZVAL_NULLABLE_STRING(&argv[3], arg3);
ZVAL_NULLABLE_STRING(&argv[4], arg4);

int authreturn = SQLITE_DENY;

zend_call_known_fcc(&db_obj->authorizer_fcc, &retval, /* argc */ 5, argv, /* named_params */ NULL);
if (Z_ISUNDEF(retval)) {
ZEND_ASSERT(EG(exception));
} else {
if (Z_TYPE(retval) != IS_LONG) {
zend_throw_exception_ex(php_pdo_get_exception(), 0, "The authorizer callback returned an invalid type: expected int");
} else {
authreturn = Z_LVAL(retval);

if (authreturn != SQLITE_OK && authreturn != SQLITE_IGNORE && authreturn != SQLITE_DENY) {
zend_throw_exception_ex(php_pdo_get_exception(), 0, "The authorizer callback returned an invalid value: %d", authreturn);
authreturn = SQLITE_DENY;
}
}
}

zval_ptr_dtor(&retval);
zval_ptr_dtor(&argv[1]);
zval_ptr_dtor(&argv[2]);
zval_ptr_dtor(&argv[3]);
zval_ptr_dtor(&argv[4]);

return authreturn;
}

static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
Expand Down Expand Up @@ -830,9 +883,7 @@ static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{
goto cleanup;
}

if (PG(open_basedir) && *PG(open_basedir)) {
sqlite3_set_authorizer(H->db, authorizer, NULL);
}
sqlite3_set_authorizer(H->db, authorizer, H);

if (driver_options) {
timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, timeout);
Expand Down
102 changes: 102 additions & 0 deletions ext/pdo_sqlite/tests/subclasses/pdosqlite_setauthorizer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
--TEST--
Pdo\Sqlite user authorizer callback
--EXTENSIONS--
pdo_sqlite
--FILE--
<?php

$db = new Pdo\Sqlite('sqlite::memory:');

$db->setAuthorizer(function (int $action) {
if ($action == 21 /* SELECT */) {
return Pdo\Sqlite::OK;
}

return Pdo\Sqlite::DENY;
});

// This query should be accepted
var_dump($db->query('SELECT 1;'));

try {
// This one should fail
var_dump($db->exec('CREATE TABLE test (a, b);'));
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}

// Test disabling the authorizer
$db->setAuthorizer(null);

// This should now succeed
var_dump($db->exec('CREATE TABLE test (a); INSERT INTO test VALUES (42);'));
var_dump($db->exec('SELECT a FROM test;'));

// Test if we are getting the correct arguments
$db->setAuthorizer(function (int $action) {
$constants = (new ReflectionClass('SQLite3'))->getConstants();
$constants = array_flip($constants);

var_dump($constants[$action], implode(',', array_slice(func_get_args(), 1)));
return Pdo\Sqlite::OK;
});

var_dump($db->exec('SELECT * FROM test WHERE a = 42;'));
var_dump($db->exec('DROP TABLE test;'));

// Try to return something invalid from the authorizer
$db->setAuthorizer(function () {
return 'FAIL';
});

try {
var_dump($db->query('SELECT 1;'));
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}

$db->setAuthorizer(function () {
return 4200;
});

try {
var_dump($db->query('SELECT 1;'));
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECTF--
object(PDOStatement)#%d (1) {
["queryString"]=>
string(9) "SELECT 1;"
}
SQLSTATE[HY000]: General error: 23 not authorized
int(1)
int(1)
string(6) "SELECT"
string(3) ",,,"
string(4) "READ"
string(12) "test,a,main,"
string(4) "READ"
string(12) "test,a,main,"
int(1)
string(6) "DELETE"
string(20) "sqlite_master,,main,"
string(10) "DROP_TABLE"
string(11) "test,,main,"
string(6) "DELETE"
string(11) "test,,main,"
string(6) "DELETE"
string(20) "sqlite_master,,main,"
string(4) "READ"
string(28) "sqlite_master,tbl_name,main,"
string(4) "READ"
string(24) "sqlite_master,type,main,"
string(6) "UPDATE"
string(28) "sqlite_master,rootpage,main,"
string(4) "READ"
string(28) "sqlite_master,rootpage,main,"
int(1)
The authorizer callback returned an invalid type: expected int
The authorizer callback returned an invalid value: 4200
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
--TEST--
Pdo\Sqlite user authorizer trampoline callback
--EXTENSIONS--
pdo_sqlite
--FILE--
<?php

class TrampolineTest {
public function __call(string $name, array $arguments) {
echo 'Trampoline for ', $name, PHP_EOL;
if ($arguments[0] == 21 /* SELECT */) {
return Pdo\Sqlite::OK;
}

return Pdo\Sqlite::DENY;
}
}
$o = new TrampolineTest();
$callback = [$o, 'authorizer'];

$db = new Pdo\Sqlite('sqlite::memory:');

$db->setAuthorizer($callback);

// This query should be accepted
var_dump($db->query('SELECT 1;'));

try {
// This one should fail
var_dump($db->query('CREATE TABLE test (a, b);'));
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECTF--
Trampoline for authorizer
object(PDOStatement)#%d (1) {
["queryString"]=>
string(9) "SELECT 1;"
}
Trampoline for authorizer
SQLSTATE[HY000]: General error: 23 not authorized
Loading

0 comments on commit c85cd94

Please sign in to comment.