From 433f114c3af805d99def2cfe55fd5332f091ac3e Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 26 Aug 2026 08:23:47 -0400 Subject: [PATCH] Hide ALTER default materialization from authorizers Suppress authorization callbacks only while running the internal row materialization for ALTER TABLE ADD COLUMN DEFAULT. The outer schema change remains authorized, and the callback is restored immediately afterward. Fixes #2428 Co-Authored-By: Codex --- src/doltlite_config.c | 12 +++- test/doltlite_regression_test_c.c | 97 +++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/src/doltlite_config.c b/src/doltlite_config.c index 5880d8eaaa..fe33670e6c 100644 --- a/src/doltlite_config.c +++ b/src/doltlite_config.c @@ -165,14 +165,22 @@ static void doltliteInternalMaterializeDefaultColumnFunc( return; } - /* Rewrite rows for the new column as InternalDml so triggers do not fire; - ** restore change counters. Stock ALTER touches no rows. */ + /* Stock ALTER exposes no row DML to triggers, counters, or authorizers. */ { int bWasSet = (db->mDbFlags & DBFLAG_InternalDml)!=0; i64 nChange = db->nChange; i64 nTotalChange = db->nTotalChange; +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; +#endif db->mDbFlags |= DBFLAG_InternalDml; +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = 0; +#endif rc = sqlite3_exec(db, zSql, 0, 0, 0); +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif /* Clear this bit only: restoring the word would undo flags set by prepare. */ if( !bWasSet ) db->mDbFlags &= ~DBFLAG_InternalDml; db->nChange = nChange; diff --git a/test/doltlite_regression_test_c.c b/test/doltlite_regression_test_c.c index 1ca064bfe8..18cd048264 100644 --- a/test/doltlite_regression_test_c.c +++ b/test/doltlite_regression_test_c.c @@ -1942,6 +1942,102 @@ static int denySchemaMasterRead( return SQLITE_OK; } +typedef struct AlterDefaultAuth AlterDefaultAuth; +struct AlterDefaultAuth { + int nAlter; + int nMasterUpdate; + int nUserRead; + int nUserUpdate; +}; + +static int denyAlterDefaultUserUpdate( + void *pCtx, + int action, + const char *zArg1, + const char *zArg2, + const char *zDb, + const char *zTrigger +){ + AlterDefaultAuth *p = (AlterDefaultAuth*)pCtx; + int isUserTable = zArg1 + && (strcmp(zArg1, "t")==0 || strcmp(zArg1, "u")==0); + (void)zArg2; + (void)zDb; + (void)zTrigger; + if( action==SQLITE_ALTER_TABLE ) p->nAlter++; + if( action==SQLITE_UPDATE && zArg1 + && strcmp(zArg1, "sqlite_master")==0 ){ + p->nMasterUpdate++; + } + if( action==SQLITE_READ && isUserTable ) p->nUserRead++; + if( action==SQLITE_UPDATE && isUserTable ){ + p->nUserUpdate++; + return SQLITE_DENY; + } + return SQLITE_OK; +} + +static void run_alter_default_authorizer(void){ + sqlite3 *db = 0; + char dbpath[256]; + char auxpath[256]; + char *zSql = 0; + AlterDefaultAuth auth; + + make_dbpath(dbpath, sizeof(dbpath), "test_alter_default_authorizer"); + make_dbpath(auxpath, sizeof(auxpath), "test_alter_default_authorizer_aux"); + removeDbFiles(dbpath); + removeDbFiles(auxpath); + check("alter_default_auth_open", open_db(dbpath, &db)==SQLITE_OK); + if( !db ) return; + + zSql = sqlite3_mprintf( + "CREATE TABLE t(a INTEGER PRIMARY KEY);" + "INSERT INTO t VALUES(1),(2);" + "ATTACH %Q AS aux;" + "CREATE TABLE aux.u(a INTEGER PRIMARY KEY);" + "INSERT INTO aux.u VALUES(3),(4);", auxpath); + check("alter_default_auth_setup_alloc", zSql!=0); + check("alter_default_auth_setup", zSql && execSql(db, zSql)==SQLITE_OK); + sqlite3_free(zSql); + memset(&auth, 0, sizeof(auth)); + check("alter_default_auth_install", + sqlite3_set_authorizer(db, denyAlterDefaultUserUpdate, &auth)==SQLITE_OK); + + check("alter_default_auth_main", + execSqlSilent(db, + "ALTER TABLE t ADD COLUMN b INTEGER DEFAULT 42")==SQLITE_OK); + check("alter_default_auth_main_authorized", auth.nAlter==1); + check("alter_default_auth_main_master_update", auth.nMasterUpdate>0); + check("alter_default_auth_main_no_user_read", auth.nUserRead==0); + check("alter_default_auth_main_no_user_update", auth.nUserUpdate==0); + check("alter_default_auth_main_rows", strcmp(queryScalarText(db, + "SELECT group_concat(a || ':' || b, ',') FROM t"), + "1:42,2:42")==0); + check("alter_default_auth_restored", + execSqlSilent(db, "UPDATE t SET b=43")==SQLITE_AUTH); + + memset(&auth, 0, sizeof(auth)); + check("alter_default_auth_attached", + execSqlSilent(db, + "ALTER TABLE aux.u ADD COLUMN c TEXT DEFAULT 'x'")==SQLITE_OK); + check("alter_default_auth_attached_authorized", auth.nAlter==1); + check("alter_default_auth_attached_master_update", auth.nMasterUpdate>0); + check("alter_default_auth_attached_no_user_read", auth.nUserRead==0); + check("alter_default_auth_attached_no_user_update", auth.nUserUpdate==0); + check("alter_default_auth_attached_rows", strcmp(queryScalarText(db, + "SELECT group_concat(a || ':' || c, ',') FROM aux.u"), + "3:x,4:x")==0); + check("alter_default_auth_attached_restored", + execSqlSilent(db, "UPDATE aux.u SET c='y'")==SQLITE_AUTH); + check("alter_default_auth_clear", + sqlite3_set_authorizer(db, 0, 0)==SQLITE_OK); + + sqlite3_close(db); + removeDbFiles(dbpath); + removeDbFiles(auxpath); +} + static void run_schema_hash_error_propagation(void){ sqlite3 *db = 0; char dbpath[256]; @@ -13515,6 +13611,7 @@ static const RegressionCase aCases[] = { { "storage_format_v12", "Storage Format Version 12 Test", run_storage_format_v12 }, { "directonly_dolt_functions", "Direct-Only Dolt Functions Test", run_directonly_dolt_functions }, { "refs_deserialize_overflow_guard", "Refs Deserialize Overflow Guard Test", run_refs_deserialize_overflow_guard }, + { "alter_default_authorizer", "ALTER Default Authorizer Test", run_alter_default_authorizer }, { "backup_safety", "Backup Safety Test", run_backup_safety }, { "backup_source_write_busy", "Backup Source Write Busy Test", run_backup_source_write_busy }, { "integer_pk_autocommit_append_correctness", "Integer PK Autocommit Append Correctness Test", run_integer_pk_autocommit_append_correctness },