Skip to content

Commit 51a9c30

Browse files
committed
Implement GH-17321: Add setAuthorizer to Pdo\Sqlite
1 parent 87499e4 commit 51a9c30

10 files changed

+325
-15
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ PHP NEWS
114114

115115
- PDO_SQLITE:
116116
. throw on null bytes / resolve GH-13952 (divinity76).
117+
. Implement GH-17321: Add setAuthorizer to Pdo\Sqlite. (nielsdos)
117118

118119
- PGSQL:
119120
. Added pg_close_stmt to close a prepared statement while allowing

UPGRADING

+5
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,11 @@ PHP 8.5 UPGRADE NOTES
309309
. Added enchant_dict_remove() to put a word on the exclusion list and
310310
remove it from the session dictionary.
311311

312+
- Pdo\Sqlite:
313+
. Added support for Pdo\Sqlite::setAuthorizer(), which is the equivalent of
314+
SQLite3::setAuthorizer(). The only interface difference is that the
315+
pdo version returns void.
316+
312317
- PGSQL:
313318
. pg_close_stmt offers an alternative way to close a prepared
314319
statement from the DEALLOCATE sql command in that we can reuse

ext/pdo_sqlite/pdo_sqlite.c

+31-1
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,36 @@ PHP_METHOD(Pdo_Sqlite, openBlob)
332332
}
333333
}
334334

335+
PHP_METHOD(Pdo_Sqlite, setAuthorizer)
336+
{
337+
zend_fcall_info fci = empty_fcall_info;
338+
zend_fcall_info_cache fcc = empty_fcall_info_cache;
339+
340+
ZEND_PARSE_PARAMETERS_START(1, 1)
341+
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE_OR_NULL(fci, fcc)
342+
ZEND_PARSE_PARAMETERS_END();
343+
344+
pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
345+
PDO_CONSTRUCT_CHECK_WITH_CLEANUP(free_fcc);
346+
pdo_sqlite_db_handle *db_handle = (pdo_sqlite_db_handle *) dbh->driver_data;
347+
348+
/* Clear previously set callback */
349+
if (ZEND_FCC_INITIALIZED(db_handle->authorizer_fcc)) {
350+
zend_fcc_dtor(&db_handle->authorizer_fcc);
351+
}
352+
353+
/* Only enable userland authorizer if argument is not NULL */
354+
if (ZEND_FCI_INITIALIZED(fci)) {
355+
zend_fcc_dup(&db_handle->authorizer_fcc, &fcc);
356+
}
357+
358+
return;
359+
360+
free_fcc:
361+
zend_release_fcall_info_cache(&fcc);
362+
RETURN_THROWS();
363+
}
364+
335365
static int php_sqlite_collation_callback(void *context, int string1_len, const void *string1,
336366
int string2_len, const void *string2)
337367
{
@@ -349,7 +379,7 @@ static int php_sqlite_collation_callback(void *context, int string1_len, const v
349379
if (!Z_ISUNDEF(retval)) {
350380
if (Z_TYPE(retval) != IS_LONG) {
351381
zend_string *func_name = get_active_function_or_method_name();
352-
zend_type_error("%s(): Return value of the callback must be of type int, %s returned",
382+
zend_type_error("%s(): Return value of the collation callback must be of type int, %s returned",
353383
ZSTR_VAL(func_name), zend_zval_value_name(&retval));
354384
zend_string_release(func_name);
355385
zval_ptr_dtor(&retval);

ext/pdo_sqlite/pdo_sqlite.stub.php

+12
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ class Sqlite extends \PDO
3333
/** @cvalue PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES */
3434
public const int ATTR_EXTENDED_RESULT_CODES = UNKNOWN;
3535

36+
/** @cvalue SQLITE_OK */
37+
public const int OK = UNKNOWN;
38+
39+
/* Constants for authorizer return */
40+
41+
/** @cvalue SQLITE_DENY */
42+
public const int DENY = UNKNOWN;
43+
/** @cvalue SQLITE_IGNORE */
44+
public const int IGNORE = UNKNOWN;
45+
3646
// Registers an aggregating User Defined Function for use in SQL statements
3747
public function createAggregate(
3848
string $name,
@@ -63,4 +73,6 @@ public function openBlob(
6373
?string $dbname = "main",
6474
int $flags = \Pdo\Sqlite::OPEN_READONLY
6575
) {}
76+
77+
public function setAuthorizer(?callable $callback): void {}
6678
}

ext/pdo_sqlite/pdo_sqlite_arginfo.h

+25-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/pdo_sqlite/php_pdo_sqlite_int.h

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ typedef struct {
5050
pdo_sqlite_error_info einfo;
5151
struct pdo_sqlite_func *funcs;
5252
struct pdo_sqlite_collation *collations;
53+
zend_fcall_info_cache authorizer_fcc;
5354
} pdo_sqlite_db_handle;
5455

5556
typedef struct {

ext/pdo_sqlite/sqlite_driver.c

+70-13
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ static void pdo_sqlite_cleanup_callbacks(pdo_sqlite_db_handle *H)
9797
{
9898
struct pdo_sqlite_func *func;
9999

100+
if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) {
101+
zend_fcc_dtor(&H->authorizer_fcc);
102+
}
103+
100104
while (H->funcs) {
101105
func = H->funcs;
102106
H->funcs = func->next;
@@ -714,6 +718,10 @@ static void pdo_sqlite_get_gc(pdo_dbh_t *dbh, zend_get_gc_buffer *gc_buffer)
714718
{
715719
pdo_sqlite_db_handle *H = dbh->driver_data;
716720

721+
if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) {
722+
zend_get_gc_buffer_add_fcc(gc_buffer, &H->authorizer_fcc);
723+
}
724+
717725
struct pdo_sqlite_func *func = H->funcs;
718726
while (func) {
719727
if (ZEND_FCC_INITIALIZED(func->func)) {
@@ -784,24 +792,75 @@ static char *make_filename_safe(const char *filename)
784792
return estrdup(filename);
785793
}
786794

787-
static int authorizer(void *autharg, int access_type, const char *arg3, const char *arg4,
788-
const char *arg5, const char *arg6)
795+
#define ZVAL_NULLABLE_STRING(zv, str) do { \
796+
if ((str)) { \
797+
ZVAL_STRING((zv), (str)); \
798+
} else { \
799+
ZVAL_NULL(zv); \
800+
} \
801+
} while (0)
802+
803+
static int authorizer(void *autharg, int access_type, const char *arg1, const char *arg2,
804+
const char *arg3, const char *arg4)
789805
{
790-
char *filename;
791-
switch (access_type) {
792-
case SQLITE_ATTACH: {
793-
filename = make_filename_safe(arg3);
806+
if (PG(open_basedir) && *PG(open_basedir)) {
807+
if (access_type == SQLITE_ATTACH) {
808+
char *filename = make_filename_safe(arg1);
794809
if (!filename) {
795810
return SQLITE_DENY;
796811
}
797812
efree(filename);
798-
return SQLITE_OK;
799813
}
814+
}
800815

801-
default:
802-
/* access allowed */
803-
return SQLITE_OK;
816+
pdo_sqlite_db_handle *db_obj = autharg;
817+
818+
/* fallback to access allowed if authorizer callback is not defined */
819+
if (!ZEND_FCC_INITIALIZED(db_obj->authorizer_fcc)) {
820+
return SQLITE_OK;
804821
}
822+
823+
/* call userland authorizer callback, if set */
824+
zval retval;
825+
zval argv[5];
826+
827+
ZVAL_LONG(&argv[0], access_type);
828+
ZVAL_NULLABLE_STRING(&argv[1], arg1);
829+
ZVAL_NULLABLE_STRING(&argv[2], arg2);
830+
ZVAL_NULLABLE_STRING(&argv[3], arg3);
831+
ZVAL_NULLABLE_STRING(&argv[4], arg4);
832+
833+
int authreturn = SQLITE_DENY;
834+
835+
zend_call_known_fcc(&db_obj->authorizer_fcc, &retval, /* argc */ 5, argv, /* named_params */ NULL);
836+
if (Z_ISUNDEF(retval)) {
837+
ZEND_ASSERT(EG(exception));
838+
} else {
839+
if (Z_TYPE(retval) != IS_LONG) {
840+
zend_string *func_name = get_active_function_or_method_name();
841+
zend_type_error("%s(): Return value of the authorizer callback must be of type int, %s returned",
842+
ZSTR_VAL(func_name), zend_zval_value_name(&retval));
843+
zend_string_release(func_name);
844+
} else {
845+
authreturn = Z_LVAL(retval);
846+
847+
if (authreturn != SQLITE_OK && authreturn != SQLITE_IGNORE && authreturn != SQLITE_DENY) {
848+
zend_string *func_name = get_active_function_or_method_name();
849+
zend_value_error("%s(): Return value of the authorizer callback must be one of Pdo\\Sqlite::OK, Pdo\\Sqlite::DENY, or Pdo\\Sqlite::IGNORE",
850+
ZSTR_VAL(func_name));
851+
zend_string_release(func_name);
852+
authreturn = SQLITE_DENY;
853+
}
854+
}
855+
}
856+
857+
zval_ptr_dtor(&retval);
858+
zval_ptr_dtor(&argv[1]);
859+
zval_ptr_dtor(&argv[2]);
860+
zval_ptr_dtor(&argv[3]);
861+
zval_ptr_dtor(&argv[4]);
862+
863+
return authreturn;
805864
}
806865

807866
static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */
@@ -843,9 +902,7 @@ static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{
843902
goto cleanup;
844903
}
845904

846-
if (PG(open_basedir) && *PG(open_basedir)) {
847-
sqlite3_set_authorizer(H->db, authorizer, NULL);
848-
}
905+
sqlite3_set_authorizer(H->db, authorizer, H);
849906

850907
if (driver_options) {
851908
timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, timeout);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
--TEST--
2+
Pdo\Sqlite user authorizer callback
3+
--EXTENSIONS--
4+
pdo_sqlite
5+
--FILE--
6+
<?php
7+
8+
$db = new Pdo\Sqlite('sqlite::memory:');
9+
10+
$db->setAuthorizer(function (int $action) {
11+
if ($action == 21 /* SELECT */) {
12+
return Pdo\Sqlite::OK;
13+
}
14+
15+
return Pdo\Sqlite::DENY;
16+
});
17+
18+
// This query should be accepted
19+
var_dump($db->query('SELECT 1;'));
20+
21+
try {
22+
// This one should fail
23+
var_dump($db->exec('CREATE TABLE test (a, b);'));
24+
} catch (\Exception $e) {
25+
echo $e->getMessage() . "\n";
26+
}
27+
28+
// Test disabling the authorizer
29+
$db->setAuthorizer(null);
30+
31+
// This should now succeed
32+
var_dump($db->exec('CREATE TABLE test (a); INSERT INTO test VALUES (42);'));
33+
var_dump($db->exec('SELECT a FROM test;'));
34+
35+
// Test if we are getting the correct arguments
36+
$db->setAuthorizer(function (int $action) {
37+
$constants = ["COPY", "CREATE_INDEX", "CREATE_TABLE", "CREATE_TEMP_INDEX", "CREATE_TEMP_TABLE", "CREATE_TEMP_TRIGGER", "CREATE_TEMP_VIEW", "CREATE_TRIGGER", "CREATE_VIEW", "DELETE", "DROP_INDEX", "DROP_TABLE", "DROP_TEMP_INDEX", "DROP_TEMP_TABLE", "DROP_TEMP_TRIGGER", "DROP_TEMP_VIEW", "DROP_TRIGGER", "DROP_VIEW", "INSERT", "PRAGMA", "READ", "SELECT", "TRANSACTION", "UPDATE"];
38+
39+
var_dump($constants[$action], implode(',', array_slice(func_get_args(), 1)));
40+
return Pdo\Sqlite::OK;
41+
});
42+
43+
var_dump($db->exec('SELECT * FROM test WHERE a = 42;'));
44+
var_dump($db->exec('DROP TABLE test;'));
45+
46+
// Try to return something invalid from the authorizer
47+
$db->setAuthorizer(function () {
48+
return 'FAIL';
49+
});
50+
51+
try {
52+
var_dump($db->query('SELECT 1;'));
53+
} catch (\Error $e) {
54+
echo $e->getMessage() . "\n";
55+
}
56+
57+
$db->setAuthorizer(function () {
58+
return 4200;
59+
});
60+
61+
try {
62+
var_dump($db->query('SELECT 1;'));
63+
} catch (\Error $e) {
64+
echo $e->getMessage() . "\n";
65+
}
66+
67+
?>
68+
--EXPECTF--
69+
object(PDOStatement)#%d (1) {
70+
["queryString"]=>
71+
string(9) "SELECT 1;"
72+
}
73+
SQLSTATE[HY000]: General error: 23 not authorized
74+
int(1)
75+
int(1)
76+
string(6) "SELECT"
77+
string(3) ",,,"
78+
string(4) "READ"
79+
string(12) "test,a,main,"
80+
string(4) "READ"
81+
string(12) "test,a,main,"
82+
int(1)
83+
string(6) "DELETE"
84+
string(20) "sqlite_master,,main,"
85+
string(10) "DROP_TABLE"
86+
string(11) "test,,main,"
87+
string(6) "DELETE"
88+
string(11) "test,,main,"
89+
string(6) "DELETE"
90+
string(20) "sqlite_master,,main,"
91+
string(4) "READ"
92+
string(28) "sqlite_master,tbl_name,main,"
93+
string(4) "READ"
94+
string(24) "sqlite_master,type,main,"
95+
string(6) "UPDATE"
96+
string(28) "sqlite_master,rootpage,main,"
97+
string(4) "READ"
98+
string(28) "sqlite_master,rootpage,main,"
99+
int(1)
100+
PDO::query(): Return value of the authorizer callback must be of type int, string returned
101+
PDO::query(): Return value of the authorizer callback must be one of Pdo\Sqlite::OK, Pdo\Sqlite::DENY, or Pdo\Sqlite::IGNORE

0 commit comments

Comments
 (0)