From 5b92691f852a3a50bfde5bb9cf6c45efd721eadf Mon Sep 17 00:00:00 2001 From: drh <> Date: Fri, 1 May 2026 23:56:16 +0000 Subject: [PATCH] This is an idea for new APIs to improve upon sqlite3_set_authorizer(). After implementing it, however, I'm having second throughts. Saved here in case I changed my mind. FossilOrigin-Name: c3c3d9731535650ba1bcac8709ead27ce037736b31b7efef37422b502e10d332 --- manifest | 19 +++++---- manifest.tags | 4 +- manifest.uuid | 2 +- src/auth.c | 88 +++++++++++++++++++++++++++++++++++++++++ src/main.c | 3 ++ src/sqlite.h.in | 103 +++++++++++++++++++++++++++++++----------------- 6 files changed, 171 insertions(+), 48 deletions(-) diff --git a/manifest b/manifest index 6826eb3eb7..caf1faed7d 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\san\sSQLITE_MISUSE\smemory\serror\sin\scheck-ini\s[1f940357f7bb160b]. -D 2026-05-01T17:42:15.461 +C This\sis\san\sidea\sfor\snew\sAPIs\sto\simprove\supon\ssqlite3_set_authorizer().\nAfter\simplementing\sit,\showever,\sI'm\shaving\ssecond\sthroughts.\s\sSaved\shere\nin\scase\sI\schanged\smy\smind. +D 2026-05-01T23:56:16.497 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -671,7 +671,7 @@ F sqlite3.pc.in e6dee284fba59ef500092fdc1843df3be8433323a3733c91da96690a50a5b398 F src/alter.c 7d7ddbdc189f0e0c686e32ee170abdddc95c11f2089e40df4ffcee88f5334826 F src/analyze.c 03bcfc083fc0cccaa9ded93604e1d4244ea245c17285d463ef6a60425fcb247d F src/attach.c c58278c7d2d954785591c4fde81669ec3e4d52f348c453b028a19ae8adf4f338 -F src/auth.c ebec42df26b34a62b6750d30d9c2c03554a1c522020182476f7729a439fef04f +F src/auth.c ca42996e133f1b7b50447b54291db6a5e2b7c8fbca015235c101dcc97e719917 F src/backup.c 6ebe22ccbedfcb92423833992130e8d65824be4e6599c3a03f540ab38fc7d13c F src/bitvec.c e242d4496774dfc88fa278177dd23b607dce369ccafb3f61b41638eea2c9b399 F src/btmutex.c 30dada73a819a1ef5b7583786370dce1842e12e1ad941e4d05ac29695528daea @@ -699,7 +699,7 @@ F src/insert.c 77aa90e7ddbebf131c5de504854b5a61c25b11d53580649fb375b23752793651 F src/json.c 5027b856cd9b621dc9ba66b211e21a440ccdc63cefdefb44c51e7d3ac550d1a4 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 78d5b06f18996ffa1203129b28fea043f63a87a4117539678f1d761c30b4ff65 -F src/main.c 6180079f53ccdd784df2eddc3751f49ea7153c5959bee792b19ad9f4bdbcf437 +F src/main.c 022f66a3adb94db148c4ad9ff4357a4d4586fea0c0546f8b91f745bd54a3529c F src/malloc.c 422f7e0498e1c9ef967f06283b6f2c0b16db6b905d8e06f6dbc8baaa3e4e6c5a F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645 F src/mem1.c 3bb59158c38e05f6270e761a9f435bf19827a264c13d1631c58b84bdc96d73b2 @@ -737,7 +737,7 @@ F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c 4c05cde130f26991b7411d8c6809e0630625e18078742c963a047b4b9cc01d49 F src/shell.c.in 3ed0902de2a82c92ee535c1bc627fb7cfd64cc3b81762f1c10b59c634597da10 -F src/sqlite.h.in 39d2e09114d2bdb7afd998f4a469c8f8cd065f8093835a7d0422f260fc78fb4f +F src/sqlite.h.in fa079952ae30eb0921e3b5eb11e4c73741d7dee86d202b2fa7e4c688d6f9b0cf F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 9788c301f95370fa30e808861f1d2e6f022a816ddbe2a4f67486784c1b31db2e F src/sqliteInt.h 5071d64ad787d9922e524d834c3fe7b7a11989d29dc6ea25f7944892a43266f4 @@ -2203,8 +2203,11 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 9190f1b9b8889e4d80c370db36b916744d5779de0ec51da0369bbe74adb7b931 -R daf617341525cb93b193d2a453da3d1c +P fc64113e8f9aee0161e682db7c244a7849f2db86551a557b276deb1785941476 +R 5c538d610a39b9c009307badcddf6e4d +T *branch * push-pop-authorizer +T *sym-push-pop-authorizer * +T -sym-trunk * U drh -Z 80e6eac2e96d40aca876dc4a525f888d +Z a0686f1cab4b032dd5f99fc3c9d92233 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.tags b/manifest.tags index bec971799f..96602da501 100644 --- a/manifest.tags +++ b/manifest.tags @@ -1,2 +1,2 @@ -branch trunk -tag trunk +branch push-pop-authorizer +tag push-pop-authorizer diff --git a/manifest.uuid b/manifest.uuid index 4243df44fe..812aaecc02 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -fc64113e8f9aee0161e682db7c244a7849f2db86551a557b276deb1785941476 +c3c3d9731535650ba1bcac8709ead27ce037736b31b7efef37422b502e10d332 diff --git a/src/auth.c b/src/auth.c index 1088f844a7..ffd6281a83 100644 --- a/src/auth.c +++ b/src/auth.c @@ -257,4 +257,92 @@ void sqlite3AuthContextPop(AuthContext *pContext){ } } +/* +** There is a single instance of this object for each nested +** authorizer. +*/ +typedef struct NestedAuth { + int (*xPriorAuth)(void*,int,const char*,const char*,const char*,const char*); + void *pPriorArg; + int (*xThisAuth)(void*,int,const char*,const char*,const char*,const char*); + void *pThisArg; + int bOvrd; +} NestedAuth; + +/* +** This is an authorizer implementation that invokes two separate +** authorizers and returns the most restrictive answer. +** +** The pArg value is a pointer to an sqlite3_auth object that contains +** the function pointer and augument for two other (real) authorizers. +** This routine runs both of the nested authorizers. If either returns +** SQLITE_DENY, this routine returns SQLITE_DENY. If neither returns +** SQLITE_DENY but either of them return SQLITE_IGNORE, then this +** routine returns SQLITE_IGNORE. Only if both authorizers return +** SQLITE_OK does this routine return SQLITE_OK. +*/ +static int sqlite3NestedAuthorizer( + void *pArg, /* Must be an sqlite3_auth object */ + int eOp, /* Operation to be authorized */ + const char *z1, /* First argument */ + const char *z2, /* Second argument */ + const char *z3, /* Third argument */ + const char *z4 /* Fourth argument */ +){ + NestedAuth *p = (NestedAuth*)pArg; + int rc1, rc2; + if( p->xThisAuth==0 ){ + rc1 = SQLITE_OK; + /* If the top-most authorizer is a NULL, then do no perform + ** any other authorizations. */ + }else{ + rc1 = p->xThisAuth(p->pThisArg,eOp,z1,z2,z3,z4); + if( rc1==SQLITE_DENY || p->bOvrd ) return rc1; + } + assert( p->xPriorAuth!=0 ); + rc2 = p->xPriorAuth(p->pPriorArg,eOp,z1,z2,z3,z4); + if( rc2 ) return rc2; + return rc1; +} + +/* +** Push a new authorizer function xAuth with pArg. This new authorizer +** runs first before any prior authorizer. +*/ +int sqlite3_push_authorizer( + sqlite3 *db, + int (*xAuth)(void*,int,const char*,const char*,const char*,const char*), + void *pArg, + int bOvrd +){ + NestedAuth *p; + if( db==0 ) return SQLITE_MISUSE; + if( db->xAuth==0 ){ + return sqlite3_set_authorizer(db, xAuth, pArg); + } + p = sqlite3DbMallocZero(db, sizeof(NestedAuth)); + if( p==0 ) return SQLITE_NOMEM; + p->xPriorAuth = db->xAuth; + p->pPriorArg = db->pAuthArg; + p->xThisAuth = xAuth; + p->pThisArg = pArg; + p->bOvrd = bOvrd; + return sqlite3_set_authorizer(db, sqlite3NestedAuthorizer, p); +} + +/* +** Pop the top-most authorizer off of the authorizer stack. +*/ +int sqlite3_pop_authorizer(sqlite3 *db){ + if( db==0 ) return SQLITE_MISUSE; + if( db->xAuth==sqlite3NestedAuthorizer ){ + NestedAuth *p = (NestedAuth*)db->pAuthArg; + (void)sqlite3_set_authorizer(db, p->xPriorAuth, p->pPriorArg); + sqlite3DbFree(db, p); + }else{ + (void)sqlite3_set_authorizer(db, 0, 0); + } + return SQLITE_OK; +} + #endif /* SQLITE_OMIT_AUTHORIZATION */ diff --git a/src/main.c b/src/main.c index 6b856df84b..bd231a97c6 100644 --- a/src/main.c +++ b/src/main.c @@ -1302,6 +1302,9 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ if( p->xDestructor ) p->xDestructor(p->pData); sqlite3_free(p); } + while( db->xAuth ){ + sqlite3_pop_authorizer(db); + } /* Convert the connection into a zombie and then close it. */ diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 23eccccff1..8b03f893d6 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -3388,58 +3388,57 @@ void sqlite3_randomness(int N, void *P); ** METHOD: sqlite3 ** KEYWORDS: {authorizer callback} ** -** ^This routine registers an authorizer callback with a particular -** [database connection], supplied in the first argument. -** ^The authorizer callback is invoked as SQL statements are being compiled -** by [sqlite3_prepare()] or its variants [sqlite3_prepare_v2()], -** [sqlite3_prepare_v3()], [sqlite3_prepare16()], [sqlite3_prepare16_v2()], -** and [sqlite3_prepare16_v3()]. ^At various +** The sqlite3_set_authorizer(D,X,P) and sqlite3_push_authorizer(D,X,P,F) +** routines registers an authorizer callback X with a particular +** [database connection] D. The sqlite3_push_authorizer() interface +** is preferred. +** +** The authorizer callback is invoked multiple times as SQL statements +** are being compiled by [sqlite3_prepare()] or its variants +** [sqlite3_prepare_v2()], [sqlite3_prepare_v3()], [sqlite3_prepare16()], +** [sqlite3_prepare16_v2()], and [sqlite3_prepare16_v3()]. At various ** points during the compilation process, as logic is being created ** to perform various actions, the authorizer callback is invoked to -** see if those actions are allowed. ^The authorizer callback should +** see if those actions are allowed. The authorizer callback should ** return [SQLITE_OK] to allow the action, [SQLITE_IGNORE] to disallow the ** specific action but allow the SQL statement to continue to be ** compiled, or [SQLITE_DENY] to cause the entire SQL statement to be -** rejected with an error. ^If the authorizer callback returns +** rejected with an error. If the authorizer callback returns ** any value other than [SQLITE_IGNORE], [SQLITE_OK], or [SQLITE_DENY] ** then the [sqlite3_prepare_v2()] or equivalent call that triggered ** the authorizer will fail with an error message. ** -** When the callback returns [SQLITE_OK], that means the operation -** requested is ok. ^When the callback returns [SQLITE_DENY], the -** [sqlite3_prepare_v2()] or equivalent call that triggered the -** authorizer will fail with an error message explaining that -** access is denied. -** -** ^The first parameter to the authorizer callback is a copy of the third -** parameter to the sqlite3_set_authorizer() interface. ^The second parameter +** The first parameter to the authorizer callback is a copy of the third +** parameter (P) to the sqlite3_set_authorizer(D,X,P) or +** sqlite3_push_authorizer(D,X,P,F) interface that created the +** authorizer. The second parameter ** to the callback is an integer [SQLITE_COPY | action code] that specifies -** the particular action to be authorized. ^The third through sixth parameters +** the particular action to be authorized. The third through sixth parameters ** to the callback are either NULL pointers or zero-terminated strings ** that contain additional details about the action to be authorized. ** Applications must always be prepared to encounter a NULL pointer in any ** of the third through the sixth parameters of the authorization callback. ** -** ^If the action code is [SQLITE_READ] +** If the action code is [SQLITE_READ] ** and the callback returns [SQLITE_IGNORE] then the ** [prepared statement] statement is constructed to substitute ** a NULL value in place of the table column that would have ** been read if [SQLITE_OK] had been returned. The [SQLITE_IGNORE] ** return can be used to deny an untrusted user access to individual ** columns of a table. -** ^When a table is referenced by a [SELECT] but no column values are +** When a table is referenced by a [SELECT] but no column values are ** extracted from that table (for example in a query like ** "SELECT count(*) FROM tab") then the [SQLITE_READ] authorizer callback ** is invoked once for that table with a column name that is an empty string. -** ^If the action code is [SQLITE_DELETE] and the callback returns +** If the action code is [SQLITE_DELETE] and the callback returns ** [SQLITE_IGNORE] then the [DELETE] operation proceeds but the ** [truncate optimization] is disabled and all rows are deleted individually. ** -** An authorizer is used when [sqlite3_prepare | preparing] -** SQL statements from an untrusted source, to ensure that the SQL statements -** do not try to access data they are not allowed to see, or that they do not -** try to execute malicious statements that damage the database. For -** example, an application may allow a user to enter arbitrary +** The authorizer mechanism is designed for use when +** [sqlite3_prepare | preparing] SQL statements from an untrusted source, +** to ensure that the SQL statements do not access data they are +** not allowed to see, or change data that they are not allowed to +** modify. For example, an application may allow a user to enter arbitrary ** SQL queries for evaluation by a database. But the application does ** not want the user to be able to make arbitrary changes to the ** database. An authorizer could then be put in place while the @@ -3451,22 +3450,45 @@ void sqlite3_randomness(int N, void *P); ** and limiting database size using the [max_page_count] [PRAGMA] ** in addition to using an authorizer. ** -** ^(Only a single authorizer can be in place on a database connection -** at a time. Each call to sqlite3_set_authorizer overrides the -** previous call.)^ ^Disable the authorizer by installing a NULL callback. -** The authorizer is disabled by default. -** -** The authorizer callback must not do anything that will modify -** the database connection that invoked the authorizer callback. -** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their -** database connections for the meaning of "modify" in this paragraph. -** -** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the +** The sqlite3_set_authorizer(D,X,P) interface causes X to be the only +** authorizer that runs. Any previously registered authorizers are +** discarded. Older versions of SQLite (prior to version 3.54.0) +** only support sqlite3_set_authorizer(). +** +** The sqlite3_push_authorizer(D,X,P,F) interface pushes a new authorizer +** onto a stack of authorizers. The sqlite3_pop_authorizer(D) pops that +** authorizer back off the stack. If the F flag is 1, then the pushed +** authorizer callback is the only one that is considered - previous +** authorizers are retained so that they can be restored after the +** next sqlite3_pop_authorizer(), but they are not used while the new +** authorizer is still on the stack. If the F flag to +** sqlite3_push_authorizer(D,X,P,F) is zero, that means that +** the new authorizer runs first, but preexisting authorizers are also +** run and the result is the most restrictive result returned by +** any authorizer. SQLITE_DENY is more restrictive than SQLITE_IGNORE, +** and SQLITE_IGNORE is more restrictive than SQLITE_OK. +** +** A call to sqlite3_push_authorizer() will return SQLITE_NOMEM if it +** is unable to allocate memory to hold prior authorizers. +** +** Use sqlite3_push_authorizer(D,0,0,1) to disable all authorizers on +** database connection D temporarily, but remember prior authorizers +** so that they are restored at the next sqlite3_pop_authorizer(D). +** +** It is illegal to invoke sqlite3_set_authorizer() if there are pending +** (unpopped) calls to sqlite3_push_authorizer(). Doing so can lead +** to memory leaks. The use of sqlite3_set_authorizer() is discouraged. +** New applications should use only sqlite3_push_authorizer() and +** sqlite3_pop_authorizer(). +** +**