Skip to content

Add setAuthorizer method to SQLite3 (implement userland authorizer) #4797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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