Skip to content

Commit

Permalink
Add setAuthorizer method to SQLite3
Browse files Browse the repository at this point in the history
This adds the possibility to define a userland callback that will be
used to authorize or not an action on the database.
  • Loading branch information
BohwaZ authored and cmb69 committed Dec 20, 2019
1 parent 5f2f450 commit 3958592
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 17 deletions.
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ PHP NEWS
- sodium:
. Fixed bug #77646 (sign_detached() strings not terminated). (Frank)

- SQLite3:
. Added SQLite3::setAuthorizer() and respective class constants. (bohwaz)

- Standard:
. Fixed bug #77204 (getimagesize(): Read error! should mention file path).
(peter279k)
Expand Down
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,12 @@ PHP 8.0 UPGRADE NOTES
6. New Functions
========================================

- SQLite3:
. Add SQLite3::setAuthorizer() and respective class constants to set a
userland callback that will be used to authorize or not an action on the
database.
PR: https://github.com/php/php-src/pull/4797

- Standard:
. Added fdiv() method, which performs a floating-point division under
IEEE 754 semantics. Division by zero is considered well-defined and
Expand Down
2 changes: 2 additions & 0 deletions ext/sqlite3/php_sqlite3_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ typedef struct _php_sqlite3_db_object {
sqlite3 *db;
php_sqlite3_func *funcs;
php_sqlite3_collation *collations;
zend_fcall_info authorizer_fci;
zend_fcall_info_cache authorizer_fcc;

zend_bool exception;

Expand Down
177 changes: 160 additions & 17 deletions ext/sqlite3/sqlite3.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
ZEND_DECLARE_MODULE_GLOBALS(sqlite3)

static PHP_GINIT_FUNCTION(sqlite3);
static int php_sqlite3_authorizer(void *autharg, int access_type, const char *arg3, const char *arg4, const char *arg5, const char *arg6);
static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, const char *arg2, const char *arg3, const char *arg4);
static void sqlite3_param_dtor(zval *data);
static int php_sqlite3_compare_stmt_zval_free(php_sqlite3_free_list **free_list, zval *statement);

Expand Down Expand Up @@ -159,10 +159,10 @@ PHP_METHOD(sqlite3, open)
#endif

db_obj->initialised = 1;
db_obj->authorizer_fci = empty_fcall_info;
db_obj->authorizer_fcc = empty_fcall_info_cache;

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

#if SQLITE_VERSION_NUMBER >= 3026000
if (SQLITE3G(dbconfig_defensive)) {
Expand Down Expand Up @@ -1350,6 +1350,40 @@ PHP_METHOD(sqlite3, enableExceptions)
}
/* }}} */

/* {{{ proto bool SQLite3::setAuthorizer(mixed callback)
Register a callback function to be used as an authorizer by SQLite. The callback should return SQLite3::OK, SQLite3::IGNORE or SQLite3::DENY. */
PHP_METHOD(sqlite3, setAuthorizer)
{
php_sqlite3_db_object *db_obj;
zval *object = ZEND_THIS;
db_obj = Z_SQLITE3_DB_P(object);
zend_fcall_info fci;
zend_fcall_info_cache fcc;

SQLITE3_CHECK_INITIALIZED(db_obj, db_obj->initialised, SQLite3)

ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_FUNC_EX(fci, fcc, 1, 0)
ZEND_PARSE_PARAMETERS_END();

/* Clear previously set callback */
if (ZEND_FCI_INITIALIZED(db_obj->authorizer_fci)) {
zval_ptr_dtor(&db_obj->authorizer_fci.function_name);
db_obj->authorizer_fci.size = 0;
}

/* Only enable userland authorizer if argument is not NULL */
if (ZEND_FCI_INITIALIZED(fci)) {
db_obj->authorizer_fci = fci;
Z_ADDREF(db_obj->authorizer_fci.function_name);
db_obj->authorizer_fcc = fcc;
}

RETURN_TRUE;
}
/* }}} */


#if SQLITE_VERSION_NUMBER >= 3006011
/* {{{ proto bool SQLite3::backup(SQLite3 destination_db[, string source_dbname = "main"[, string destination_dbname = "main"]])
Backups the current database to another one. */
Expand Down Expand Up @@ -2118,6 +2152,7 @@ static const zend_function_entry php_sqlite3_class_methods[] = {
PHP_ME(sqlite3, createCollation, arginfo_class_SQLite3_createCollation, ZEND_ACC_PUBLIC)
PHP_ME(sqlite3, openBlob, arginfo_class_SQLite3_openBlob, ZEND_ACC_PUBLIC)
PHP_ME(sqlite3, enableExceptions, arginfo_class_SQLite3_enableExceptions, ZEND_ACC_PUBLIC)
PHP_ME(sqlite3, setAuthorizer, arginfo_class_SQLite3_setAuthorizer, ZEND_ACC_PUBLIC)
#if SQLITE_VERSION_NUMBER >= 3006011
PHP_ME(sqlite3, backup, arginfo_class_SQLite3_backup, ZEND_ACC_PUBLIC)
#endif
Expand Down Expand Up @@ -2158,32 +2193,92 @@ static const zend_function_entry php_sqlite3_result_class_methods[] = {

/* {{{ Authorization Callback
*/
static int php_sqlite3_authorizer(void *autharg, int access_type, const char *arg3, const char *arg4, const char *arg5, const char *arg6)
static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, const char *arg2, const char *arg3, const char *arg4)
{
switch (access_type) {
case SQLITE_ATTACH:
{
if (memcmp(arg3, ":memory:", sizeof(":memory:")) && *arg3) {
if (strncmp(arg3, "file:", 5) == 0) {
/* Check open_basedir restrictions first */
if (PG(open_basedir) && *PG(open_basedir)) {
if (action == SQLITE_ATTACH) {
if (memcmp(arg1, ":memory:", sizeof(":memory:")) && *arg1) {
if (strncmp(arg1, "file:", 5) == 0) {
/* starts with "file:" */
if (!arg3[5]) {
if (!arg1[5]) {
return SQLITE_DENY;
}
if (php_check_open_basedir(arg3 + 5)) {
if (php_check_open_basedir(arg1 + 5)) {
return SQLITE_DENY;
}
}
if (php_check_open_basedir(arg3)) {
if (php_check_open_basedir(arg1)) {
return SQLITE_DENY;
}
}
return SQLITE_OK;
}
}

default:
/* access allowed */
return SQLITE_OK;
php_sqlite3_db_object *db_obj = (php_sqlite3_db_object *)autharg;
zend_fcall_info *fci = &db_obj->authorizer_fci;

/* fallback to access allowed if authorizer callback is not defined */
if (fci->size == 0) {
return SQLITE_OK;
}

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

ZVAL_LONG(&argv[0], action);

if (NULL == arg1) {
ZVAL_NULL(&argv[1]);
} else {
ZVAL_STRING(&argv[1], arg1);
}

if (NULL == arg2) {
ZVAL_NULL(&argv[2]);
} else {
ZVAL_STRING(&argv[2], arg2);
}

if (NULL == arg3) {
ZVAL_NULL(&argv[3]);
} else {
ZVAL_STRING(&argv[3], arg3);
}

if (NULL == arg4) {
ZVAL_NULL(&argv[4]);
} else {
ZVAL_STRING(&argv[4], arg4);
}

fci->retval = &retval;
fci->param_count = 5;
fci->params = argv;
fci->no_separation = 0;

int authreturn = SQLITE_DENY;

if (zend_call_function(fci, &db_obj->authorizer_fcc) != SUCCESS || Z_ISUNDEF(retval)) {
php_sqlite3_error(db_obj, "An error occurred while invoking the authorizer callback");
} else {
if (Z_TYPE(retval) != IS_LONG) {
php_sqlite3_error(db_obj, "The authorizer callback returned an invalid type: expected int");
} else {
authreturn = Z_LVAL(retval);

if (authreturn != SQLITE_OK && authreturn != SQLITE_IGNORE && authreturn != SQLITE_DENY) {
php_sqlite3_error(db_obj, "The authorizer callback returned an invalid value");
authreturn = SQLITE_DENY;
}
}
}

zend_fcall_info_args_clear(fci, 0);
zval_ptr_dtor(&retval);

return authreturn;
}
/* }}} */

Expand Down Expand Up @@ -2223,6 +2318,11 @@ static void php_sqlite3_object_free_storage(zend_object *object) /* {{{ */
return;
}

/* Release function_name from authorizer */
if (intern->authorizer_fci.size > 0) {
zval_ptr_dtor(&intern->authorizer_fci.function_name);
}

while (intern->funcs) {
func = intern->funcs;
intern->funcs = func->next;
Expand Down Expand Up @@ -2444,6 +2544,49 @@ PHP_MINIT_FUNCTION(sqlite3)
REGISTER_LONG_CONSTANT("SQLITE3_OPEN_READWRITE", SQLITE_OPEN_READWRITE, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SQLITE3_OPEN_CREATE", SQLITE_OPEN_CREATE, CONST_CS | CONST_PERSISTENT);

/* Class constants */
zend_declare_class_constant_long(php_sqlite3_sc_entry, "OK", sizeof("OK") - 1, SQLITE_OK);

/* Constants for authorizer return */
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DENY", sizeof("DENY") - 1, SQLITE_DENY);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "IGNORE", sizeof("IGNORE") - 1, SQLITE_IGNORE);

/* Constants for authorizer actions */
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_INDEX", sizeof("CREATE_INDEX") - 1, SQLITE_CREATE_INDEX);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TABLE", sizeof("CREATE_TABLE") - 1, SQLITE_CREATE_TABLE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_INDEX", sizeof("CREATE_TEMP_INDEX") - 1, SQLITE_CREATE_TEMP_INDEX);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_TABLE", sizeof("CREATE_TEMP_TABLE") - 1, SQLITE_CREATE_TEMP_TABLE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_TRIGGER", sizeof("CREATE_TEMP_TRIGGER") - 1, SQLITE_CREATE_TEMP_TRIGGER);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TEMP_VIEW", sizeof("CREATE_TEMP_VIEW") - 1, SQLITE_CREATE_TEMP_VIEW);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_TRIGGER", sizeof("CREATE_TRIGGER") - 1, SQLITE_CREATE_TRIGGER);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_VIEW", sizeof("CREATE_VIEW") - 1, SQLITE_CREATE_VIEW);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DELETE", sizeof("DELETE") - 1, SQLITE_DELETE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_INDEX", sizeof("DROP_INDEX") - 1, SQLITE_DROP_INDEX);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TABLE", sizeof("DROP_TABLE") - 1, SQLITE_DROP_TABLE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_INDEX", sizeof("DROP_TEMP_INDEX") - 1, SQLITE_DROP_TEMP_INDEX);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_TABLE", sizeof("DROP_TEMP_TABLE") - 1, SQLITE_DROP_TEMP_TABLE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_TRIGGER", sizeof("DROP_TEMP_TRIGGER") - 1, SQLITE_DROP_TEMP_TRIGGER);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TEMP_VIEW", sizeof("DROP_TEMP_VIEW") - 1, SQLITE_DROP_TEMP_VIEW);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_TRIGGER", sizeof("DROP_TRIGGER") - 1, SQLITE_DROP_TRIGGER);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_VIEW", sizeof("DROP_VIEW") - 1, SQLITE_DROP_VIEW);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "INSERT", sizeof("INSERT") - 1, SQLITE_INSERT);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "PRAGMA", sizeof("PRAGMA") - 1, SQLITE_PRAGMA);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "READ", sizeof("READ") - 1, SQLITE_READ);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "SELECT", sizeof("SELECT") - 1, SQLITE_SELECT);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "TRANSACTION", sizeof("TRANSACTION") - 1, SQLITE_TRANSACTION);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "UPDATE", sizeof("UPDATE") - 1, SQLITE_UPDATE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ATTACH", sizeof("ATTACH") - 1, SQLITE_ATTACH);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DETACH", sizeof("DETACH") - 1, SQLITE_DETACH);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ALTER_TABLE", sizeof("ALTER_TABLE") - 1, SQLITE_ALTER_TABLE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "REINDEX", sizeof("REINDEX") - 1, SQLITE_REINDEX);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "ANALYZE", sizeof("ANALYZE") - 1, SQLITE_ANALYZE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "CREATE_VTABLE", sizeof("CREATE_VTABLE") - 1, SQLITE_CREATE_VTABLE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "DROP_VTABLE", sizeof("DROP_VTABLE") - 1, SQLITE_DROP_VTABLE);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "FUNCTION", sizeof("FUNCTION") - 1, SQLITE_FUNCTION);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "SAVEPOINT", sizeof("SAVEPOINT") - 1, SQLITE_SAVEPOINT);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "COPY", sizeof("COPY") - 1, SQLITE_COPY);
zend_declare_class_constant_long(php_sqlite3_sc_entry, "RECURSIVE", sizeof("RECURSIVE") - 1, SQLITE_RECURSIVE);

#ifdef SQLITE_DETERMINISTIC
REGISTER_LONG_CONSTANT("SQLITE3_DETERMINISTIC", SQLITE_DETERMINISTIC, CONST_CS | CONST_PERSISTENT);
#endif
Expand Down
3 changes: 3 additions & 0 deletions ext/sqlite3/sqlite3.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ function enableExceptions(bool $enableExceptions = false) {}

/** @return bool */
function enableExtendedResultCodes(bool $enable = true) {}

/** @return bool */
function setAuthorizer(?callable $callback) {}
}

class SQLite3Stmt
Expand Down
4 changes: 4 additions & 0 deletions ext/sqlite3/sqlite3_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3_enableExtendedResultCodes, 0, 0, 0)
ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3_setAuthorizer, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3Stmt___construct, 0, 0, 2)
ZEND_ARG_OBJ_INFO(0, sqlite3, SQLite3, 0)
ZEND_ARG_TYPE_INFO(0, sql, IS_STRING, 0)
Expand Down
104 changes: 104 additions & 0 deletions ext/sqlite3/tests/sqlite3_40_setauthorizer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
--TEST--
SQLite3 user authorizer callback
--SKIPIF--
<?php require_once(__DIR__ . '/skipif.inc'); ?>
--FILE--
<?php

$db = new SQLite3(':memory:');
$db->enableExceptions(true);

$db->setAuthorizer(function (int $action) {
if ($action == SQLite3::SELECT) {
return SQLite3::OK;
}

return SQLite3::DENY;
});

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

try {
// This one should fail
var_dump($db->querySingle('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->querySingle('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 SQLITE3::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->querySingle('SELECT 1;'));
} catch (\Exception $e) {
echo $e->getMessage() . "\n";
echo $e->getPrevious()->getMessage() . "\n";
}

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

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

?>
--EXPECTF--
int(1)
Unable to prepare statement: 23, not authorized
bool(true)
int(42)
string(6) "SELECT"
string(3) ",,,"
string(4) "READ"
string(12) "test,a,main,"
string(4) "READ"
string(12) "test,a,main,"
bool(true)
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,"
bool(true)
Unable to prepare statement: 23, not authorized
The authorizer callback returned an invalid type: expected int
Unable to prepare statement: 23, not authorized
The authorizer callback returned an invalid value

0 comments on commit 3958592

Please sign in to comment.