]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
This is an idea for new APIs to improve upon sqlite3_set_authorizer(). push-pop-authorizer
authordrh <>
Fri, 1 May 2026 23:56:16 +0000 (23:56 +0000)
committerdrh <>
Fri, 1 May 2026 23:56:16 +0000 (23:56 +0000)
After implementing it, however, I'm having second throughts.  Saved here
in case I changed my mind.

FossilOrigin-Name: c3c3d9731535650ba1bcac8709ead27ce037736b31b7efef37422b502e10d332

manifest
manifest.tags
manifest.uuid
src/auth.c
src/main.c
src/sqlite.h.in

index 6826eb3eb770cb6f06559cd2506f141a63bec505..caf1faed7df6bd39945897faae1a122fff273bd3 100644 (file)
--- 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.
index bec971799ff1b8ee641c166c7aeb22d12c785393..96602da501bcdc3b11bcbeb31629b60908929ae3 100644 (file)
@@ -1,2 +1,2 @@
-branch trunk
-tag trunk
+branch push-pop-authorizer
+tag push-pop-authorizer
index 4243df44fe641b8cf5b3f2f31287b4c8cdba2dd5..812aaecc0280a2d32bafeabe8643dc6acf5f852e 100644 (file)
@@ -1 +1 @@
-fc64113e8f9aee0161e682db7c244a7849f2db86551a557b276deb1785941476
+c3c3d9731535650ba1bcac8709ead27ce037736b31b7efef37422b502e10d332
index 1088f844a753bdf2b87ab9bf4dda1af1091041da..ffd6281a833bfbc01c193e334eec25894e053d8c 100644 (file)
@@ -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 */
index 6b856df84b98cdff5d5e25705c26e7e1b64e1680..bd231a97c64de2edf75b09f92aad5091cbd6f5a2 100644 (file)
@@ -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.
   */
index 23eccccff151294f2b2609a52ec9f60d371eba4a..8b03f893d604b6d457fd93b3622bc9a3b2a6e876 100644 (file)
@@ -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().
+**
+** <h2>Important Details</h2>
+**
+** When [sqlite3_prepare_v2()] is used to prepare a statement, the
 ** statement might be re-prepared during [sqlite3_step()] due to a
 ** schema change.  Hence, the application should ensure that the
 ** correct authorizer callback remains in place during the [sqlite3_step()].
 **
-** ^Note that the authorizer callback is invoked only during
+** Note that the authorizer callback is invoked only during
 ** [sqlite3_prepare()] or its variants.  Authorization is not
 ** performed during statement evaluation in [sqlite3_step()], unless
 ** as stated in the previous paragraph, sqlite3_step() invokes
@@ -3477,6 +3499,13 @@ int sqlite3_set_authorizer(
   int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
   void *pUserData
 );
+int sqlite3_push_authorizer(
+  sqlite3*,
+  int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
+  void *pUserData,
+  int bOvrd
+);
+int sqlite3_pop_authorizer(sqlite3*);
 
 /*
 ** CAPI3REF: Authorizer Return Codes