Fossil SCM
Add triggers to prevent changes to sensitive settings when PROTECT_SENSITIVE is engaged.
Commit
c9b9a77d592f031aa279b2accacb64440b9e03d2c96461cacf9ae889ba7c5141
Parent
1b52c41415d4683…
2 files changed
+2
-4
+56
-20
+2
-4
| --- src/configure.c | ||
| +++ src/configure.c | ||
| @@ -438,13 +438,12 @@ | ||
| 438 | 438 | blob_append_sql(&sql,") VALUES(%s,%s", |
| 439 | 439 | azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/); |
| 440 | 440 | for(jj=2; jj<nToken; jj+=2){ |
| 441 | 441 | blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); |
| 442 | 442 | } |
| 443 | - db_unprotect(PROTECT_ALL); | |
| 443 | + db_protect_only(PROTECT_SENSITIVE); | |
| 444 | 444 | db_multi_exec("%s)", blob_sql_text(&sql)); |
| 445 | - db_protect_pop(); | |
| 446 | 445 | if( db_changes()==0 ){ |
| 447 | 446 | blob_reset(&sql); |
| 448 | 447 | blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s", |
| 449 | 448 | &zName[1], azToken[0]/*safe-for-%s*/); |
| 450 | 449 | for(jj=2; jj<nToken; jj+=2){ |
| @@ -452,14 +451,13 @@ | ||
| 452 | 451 | azToken[jj], azToken[jj+1]/*safe-for-%s*/); |
| 453 | 452 | } |
| 454 | 453 | blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s", |
| 455 | 454 | aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, |
| 456 | 455 | azToken[0]/*safe-for-%s*/); |
| 457 | - db_unprotect(PROTECT_ALL); | |
| 458 | 456 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 459 | - db_protect_pop(); | |
| 460 | 457 | } |
| 458 | + db_protect_pop(); | |
| 461 | 459 | blob_reset(&sql); |
| 462 | 460 | rebuildMask |= thisMask; |
| 463 | 461 | } |
| 464 | 462 | } |
| 465 | 463 | |
| 466 | 464 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -438,13 +438,12 @@ | |
| 438 | blob_append_sql(&sql,") VALUES(%s,%s", |
| 439 | azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/); |
| 440 | for(jj=2; jj<nToken; jj+=2){ |
| 441 | blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); |
| 442 | } |
| 443 | db_unprotect(PROTECT_ALL); |
| 444 | db_multi_exec("%s)", blob_sql_text(&sql)); |
| 445 | db_protect_pop(); |
| 446 | if( db_changes()==0 ){ |
| 447 | blob_reset(&sql); |
| 448 | blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s", |
| 449 | &zName[1], azToken[0]/*safe-for-%s*/); |
| 450 | for(jj=2; jj<nToken; jj+=2){ |
| @@ -452,14 +451,13 @@ | |
| 452 | azToken[jj], azToken[jj+1]/*safe-for-%s*/); |
| 453 | } |
| 454 | blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s", |
| 455 | aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, |
| 456 | azToken[0]/*safe-for-%s*/); |
| 457 | db_unprotect(PROTECT_ALL); |
| 458 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 459 | db_protect_pop(); |
| 460 | } |
| 461 | blob_reset(&sql); |
| 462 | rebuildMask |= thisMask; |
| 463 | } |
| 464 | } |
| 465 | |
| 466 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -438,13 +438,12 @@ | |
| 438 | blob_append_sql(&sql,") VALUES(%s,%s", |
| 439 | azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/); |
| 440 | for(jj=2; jj<nToken; jj+=2){ |
| 441 | blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/); |
| 442 | } |
| 443 | db_protect_only(PROTECT_SENSITIVE); |
| 444 | db_multi_exec("%s)", blob_sql_text(&sql)); |
| 445 | if( db_changes()==0 ){ |
| 446 | blob_reset(&sql); |
| 447 | blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s", |
| 448 | &zName[1], azToken[0]/*safe-for-%s*/); |
| 449 | for(jj=2; jj<nToken; jj+=2){ |
| @@ -452,14 +451,13 @@ | |
| 451 | azToken[jj], azToken[jj+1]/*safe-for-%s*/); |
| 452 | } |
| 453 | blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s", |
| 454 | aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/, |
| 455 | azToken[0]/*safe-for-%s*/); |
| 456 | db_multi_exec("%s", blob_sql_text(&sql)); |
| 457 | } |
| 458 | db_protect_pop(); |
| 459 | blob_reset(&sql); |
| 460 | rebuildMask |= thisMask; |
| 461 | } |
| 462 | } |
| 463 | |
| 464 |
M
src/db.c
+56
-20
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -135,10 +135,11 @@ | ||
| 135 | 135 | const char *zStartFile; /* File in which transaction was started */ |
| 136 | 136 | int iStartLine; /* Line of zStartFile where transaction started */ |
| 137 | 137 | int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); |
| 138 | 138 | void *pAuthArg; /* Argument to the authorizer */ |
| 139 | 139 | const char *zAuthName; /* Name of the authorizer */ |
| 140 | + int bProtectTriggers; /* True if protection triggers already exist */ | |
| 140 | 141 | int nProtect; /* Slots of aProtect used */ |
| 141 | 142 | unsigned aProtect[10]; /* Saved values of protectMask */ |
| 142 | 143 | } db = { |
| 143 | 144 | PROTECT_USER|PROTECT_CONFIG, /* protectMask */ |
| 144 | 145 | 0, 0, 0, 0, 0, 0, }; |
| @@ -247,11 +248,11 @@ | ||
| 247 | 248 | db.nBegin--; |
| 248 | 249 | if( db.nBegin==0 ){ |
| 249 | 250 | int i; |
| 250 | 251 | if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){ |
| 251 | 252 | i = 0; |
| 252 | - db_unprotect(PROTECT_ALL); | |
| 253 | + db_protect_only(PROTECT_SENSITIVE); | |
| 253 | 254 | while( db.nBeforeCommit ){ |
| 254 | 255 | db.nBeforeCommit--; |
| 255 | 256 | sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0); |
| 256 | 257 | sqlite3_free(db.azBeforeCommit[i]); |
| 257 | 258 | i++; |
| @@ -330,38 +331,58 @@ | ||
| 330 | 331 | db.nCommitHook++; |
| 331 | 332 | } |
| 332 | 333 | |
| 333 | 334 | #if INTERFACE |
| 334 | 335 | /* |
| 335 | -** Flag bits for db_protect() and db_unprotect() | |
| 336 | +** Flag bits for db_protect() and db_unprotect() indicating which parts | |
| 337 | +** of the databases should be write protected or write enabled, respectively. | |
| 336 | 338 | */ |
| 337 | -#define PROTECT_USER 0x01 | |
| 338 | -#define PROTECT_CONFIG 0x02 | |
| 339 | -#define PROTECT_SENSITIVE 0x04 | |
| 340 | -#define PROTECT_READONLY 0x08 | |
| 339 | +#define PROTECT_USER 0x01 /* USER table */ | |
| 340 | +#define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */ | |
| 341 | +#define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */ | |
| 342 | +#define PROTECT_READONLY 0x08 /* everything except TEMP tables */ | |
| 341 | 343 | #define PROTECT_ALL 0x0f /* All of the above */ |
| 344 | +#define PROTECT_NONE 0x00 /* Nothing. Everything is open */ | |
| 342 | 345 | #endif /* INTERFACE */ |
| 343 | 346 | |
| 344 | 347 | /* |
| 345 | 348 | ** Enable or disable database write protections. |
| 346 | -** Use db_protect() to enable write permissions. Use | |
| 347 | -** db_unprotect() to disable them. | |
| 348 | -** | |
| 349 | -** Each call to db_protect() and/or db_unprotect() should be followed | |
| 350 | -** by a corresponding call to db_protect_pop(). The db_protect_pop() | |
| 351 | -** call restores the protection settings to what they were before. | |
| 352 | -** | |
| 353 | -** The stack of protection settings is finite, so do not nest calls | |
| 354 | -** to db_protect()/db_unprotect() too deeply. And make sure calls | |
| 355 | -** to db_protect()/db_unprotect() are balanced. | |
| 356 | -*/ | |
| 357 | -void db_protect(unsigned flags){ | |
| 349 | +** | |
| 350 | +** db_protext(X) Add protects on X | |
| 351 | +** db_unprotect(X) Remove protections on X | |
| 352 | +** db_protect_only(X) Remove all prior protections then set | |
| 353 | +** protections to only X. | |
| 354 | +** | |
| 355 | +** Each of these routines pushes the previous protection mask onto | |
| 356 | +** a finite-size stack. Each should be followed by a call to | |
| 357 | +** db_protect_pop() to pop the stack and restore the protections that | |
| 358 | +** existed prior to the call. The protection mask stack has a limited | |
| 359 | +** depth, so take care not to next calls too deeply. | |
| 360 | +*/ | |
| 361 | +void db_protect_only(unsigned flags){ | |
| 358 | 362 | if( db.nProtect>=count(db.aProtect) ){ |
| 359 | 363 | fossil_fatal("too many db_protect() calls"); |
| 360 | 364 | } |
| 361 | 365 | db.aProtect[db.nProtect++] = db.protectMask; |
| 362 | - db.protectMask |= flags; | |
| 366 | + if( (flags & PROTECT_SENSITIVE)!=0 | |
| 367 | + && (db.protectMask & PROTECT_SENSITIVE)==0 | |
| 368 | + && db.bProtectTriggers==0 | |
| 369 | + ){ | |
| 370 | + db_multi_exec( | |
| 371 | + "CREATE TEMP TRIGGER IF NOT EXISTS protect_1" | |
| 372 | + " BEFORE INSERT ON config WHEN protected_setting(new.name)" | |
| 373 | + " BEGIN SELECT raise(abort,'not authorized'); END;\n" | |
| 374 | + "CREATE TEMP TRIGGER IF NOT EXISTS protect_2" | |
| 375 | + " BEFORE UPDATE ON config WHEN protected_setting(new.name)" | |
| 376 | + " BEGIN SELECT raise(abort,'not authorized'); END;\n" | |
| 377 | + ); | |
| 378 | + db.bProtectTriggers = 1; | |
| 379 | + } | |
| 380 | + db.protectMask = flags; | |
| 381 | +} | |
| 382 | +void db_protect(unsigned flags){ | |
| 383 | + db_protect_only(db.protectMask | flags); | |
| 363 | 384 | } |
| 364 | 385 | void db_unprotect(unsigned flags){ |
| 365 | 386 | if( db.nProtect>=count(db.aProtect) ){ |
| 366 | 387 | fossil_fatal("too many db_unprotect() calls"); |
| 367 | 388 | } |
| @@ -370,10 +391,21 @@ | ||
| 370 | 391 | } |
| 371 | 392 | void db_protect_pop(void){ |
| 372 | 393 | if( db.nProtect<1 ) fossil_fatal("too many db_protect_pop() calls"); |
| 373 | 394 | db.protectMask = db.aProtect[--db.nProtect]; |
| 374 | 395 | } |
| 396 | + | |
| 397 | +/* | |
| 398 | +** Verify that the desired database write pertections are in place. | |
| 399 | +** Throw a fatal error if not. | |
| 400 | +*/ | |
| 401 | +void db_assert_protected(unsigned flags){ | |
| 402 | + if( (flags & db.protectMask)!=flags ){ | |
| 403 | + fossil_fatal("internal security assertion fault: missing " | |
| 404 | + "database protection bits: %02x", flags & ~db.protectMask); | |
| 405 | + } | |
| 406 | +} | |
| 375 | 407 | |
| 376 | 408 | /* |
| 377 | 409 | ** Every Fossil database connection automatically registers the following |
| 378 | 410 | ** overarching authenticator callback, and leaves it registered for the |
| 379 | 411 | ** duration of the connection. This authenticator will call any |
| @@ -396,10 +428,13 @@ | ||
| 396 | 428 | && sqlite3_stricmp(z0,"user")==0 ){ |
| 397 | 429 | rc = SQLITE_DENY; |
| 398 | 430 | }else if( (db.protectMask & PROTECT_CONFIG)!=0 && |
| 399 | 431 | (sqlite3_stricmp(z0,"config")==0 || |
| 400 | 432 | sqlite3_stricmp(z0,"global_config")==0) ){ |
| 433 | + rc = SQLITE_DENY; | |
| 434 | + }else if( (db.protectMask & PROTECT_SENSITIVE)!=0 && | |
| 435 | + sqlite3_stricmp(z0,"global_config")==0 ){ | |
| 401 | 436 | rc = SQLITE_DENY; |
| 402 | 437 | }else if( (db.protectMask & PROTECT_READONLY)!=0 |
| 403 | 438 | && sqlite3_stricmp(z2,"temp")!=0 ){ |
| 404 | 439 | rc = SQLITE_DENY; |
| 405 | 440 | } |
| @@ -2320,10 +2355,11 @@ | ||
| 2320 | 2355 | } |
| 2321 | 2356 | g.db = 0; |
| 2322 | 2357 | } |
| 2323 | 2358 | g.repositoryOpen = 0; |
| 2324 | 2359 | g.localOpen = 0; |
| 2360 | + db.bProtectTriggers = 0; | |
| 2325 | 2361 | assert( g.dbConfig==0 ); |
| 2326 | 2362 | assert( g.zConfigDbName==0 ); |
| 2327 | 2363 | backoffice_run_if_needed(); |
| 2328 | 2364 | } |
| 2329 | 2365 | |
| @@ -3234,11 +3270,11 @@ | ||
| 3234 | 3270 | fossil_free(zRepoSetting); |
| 3235 | 3271 | if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){ |
| 3236 | 3272 | Blob localRoot; |
| 3237 | 3273 | file_canonical_name(g.zLocalRoot, &localRoot, 1); |
| 3238 | 3274 | zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot)); |
| 3239 | - db_unprotect(PROTECT_CONFIG|PROTECT_SENSITIVE); | |
| 3275 | + db_unprotect(PROTECT_CONFIG); | |
| 3240 | 3276 | db_multi_exec( |
| 3241 | 3277 | "DELETE FROM global_config WHERE name %s = %Q;", |
| 3242 | 3278 | filename_collation(), zCkoutSetting |
| 3243 | 3279 | ); |
| 3244 | 3280 | db_multi_exec( |
| 3245 | 3281 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -135,10 +135,11 @@ | |
| 135 | const char *zStartFile; /* File in which transaction was started */ |
| 136 | int iStartLine; /* Line of zStartFile where transaction started */ |
| 137 | int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); |
| 138 | void *pAuthArg; /* Argument to the authorizer */ |
| 139 | const char *zAuthName; /* Name of the authorizer */ |
| 140 | int nProtect; /* Slots of aProtect used */ |
| 141 | unsigned aProtect[10]; /* Saved values of protectMask */ |
| 142 | } db = { |
| 143 | PROTECT_USER|PROTECT_CONFIG, /* protectMask */ |
| 144 | 0, 0, 0, 0, 0, 0, }; |
| @@ -247,11 +248,11 @@ | |
| 247 | db.nBegin--; |
| 248 | if( db.nBegin==0 ){ |
| 249 | int i; |
| 250 | if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){ |
| 251 | i = 0; |
| 252 | db_unprotect(PROTECT_ALL); |
| 253 | while( db.nBeforeCommit ){ |
| 254 | db.nBeforeCommit--; |
| 255 | sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0); |
| 256 | sqlite3_free(db.azBeforeCommit[i]); |
| 257 | i++; |
| @@ -330,38 +331,58 @@ | |
| 330 | db.nCommitHook++; |
| 331 | } |
| 332 | |
| 333 | #if INTERFACE |
| 334 | /* |
| 335 | ** Flag bits for db_protect() and db_unprotect() |
| 336 | */ |
| 337 | #define PROTECT_USER 0x01 |
| 338 | #define PROTECT_CONFIG 0x02 |
| 339 | #define PROTECT_SENSITIVE 0x04 |
| 340 | #define PROTECT_READONLY 0x08 |
| 341 | #define PROTECT_ALL 0x0f /* All of the above */ |
| 342 | #endif /* INTERFACE */ |
| 343 | |
| 344 | /* |
| 345 | ** Enable or disable database write protections. |
| 346 | ** Use db_protect() to enable write permissions. Use |
| 347 | ** db_unprotect() to disable them. |
| 348 | ** |
| 349 | ** Each call to db_protect() and/or db_unprotect() should be followed |
| 350 | ** by a corresponding call to db_protect_pop(). The db_protect_pop() |
| 351 | ** call restores the protection settings to what they were before. |
| 352 | ** |
| 353 | ** The stack of protection settings is finite, so do not nest calls |
| 354 | ** to db_protect()/db_unprotect() too deeply. And make sure calls |
| 355 | ** to db_protect()/db_unprotect() are balanced. |
| 356 | */ |
| 357 | void db_protect(unsigned flags){ |
| 358 | if( db.nProtect>=count(db.aProtect) ){ |
| 359 | fossil_fatal("too many db_protect() calls"); |
| 360 | } |
| 361 | db.aProtect[db.nProtect++] = db.protectMask; |
| 362 | db.protectMask |= flags; |
| 363 | } |
| 364 | void db_unprotect(unsigned flags){ |
| 365 | if( db.nProtect>=count(db.aProtect) ){ |
| 366 | fossil_fatal("too many db_unprotect() calls"); |
| 367 | } |
| @@ -370,10 +391,21 @@ | |
| 370 | } |
| 371 | void db_protect_pop(void){ |
| 372 | if( db.nProtect<1 ) fossil_fatal("too many db_protect_pop() calls"); |
| 373 | db.protectMask = db.aProtect[--db.nProtect]; |
| 374 | } |
| 375 | |
| 376 | /* |
| 377 | ** Every Fossil database connection automatically registers the following |
| 378 | ** overarching authenticator callback, and leaves it registered for the |
| 379 | ** duration of the connection. This authenticator will call any |
| @@ -396,10 +428,13 @@ | |
| 396 | && sqlite3_stricmp(z0,"user")==0 ){ |
| 397 | rc = SQLITE_DENY; |
| 398 | }else if( (db.protectMask & PROTECT_CONFIG)!=0 && |
| 399 | (sqlite3_stricmp(z0,"config")==0 || |
| 400 | sqlite3_stricmp(z0,"global_config")==0) ){ |
| 401 | rc = SQLITE_DENY; |
| 402 | }else if( (db.protectMask & PROTECT_READONLY)!=0 |
| 403 | && sqlite3_stricmp(z2,"temp")!=0 ){ |
| 404 | rc = SQLITE_DENY; |
| 405 | } |
| @@ -2320,10 +2355,11 @@ | |
| 2320 | } |
| 2321 | g.db = 0; |
| 2322 | } |
| 2323 | g.repositoryOpen = 0; |
| 2324 | g.localOpen = 0; |
| 2325 | assert( g.dbConfig==0 ); |
| 2326 | assert( g.zConfigDbName==0 ); |
| 2327 | backoffice_run_if_needed(); |
| 2328 | } |
| 2329 | |
| @@ -3234,11 +3270,11 @@ | |
| 3234 | fossil_free(zRepoSetting); |
| 3235 | if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){ |
| 3236 | Blob localRoot; |
| 3237 | file_canonical_name(g.zLocalRoot, &localRoot, 1); |
| 3238 | zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot)); |
| 3239 | db_unprotect(PROTECT_CONFIG|PROTECT_SENSITIVE); |
| 3240 | db_multi_exec( |
| 3241 | "DELETE FROM global_config WHERE name %s = %Q;", |
| 3242 | filename_collation(), zCkoutSetting |
| 3243 | ); |
| 3244 | db_multi_exec( |
| 3245 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -135,10 +135,11 @@ | |
| 135 | const char *zStartFile; /* File in which transaction was started */ |
| 136 | int iStartLine; /* Line of zStartFile where transaction started */ |
| 137 | int (*xAuth)(void*,int,const char*,const char*,const char*,const char*); |
| 138 | void *pAuthArg; /* Argument to the authorizer */ |
| 139 | const char *zAuthName; /* Name of the authorizer */ |
| 140 | int bProtectTriggers; /* True if protection triggers already exist */ |
| 141 | int nProtect; /* Slots of aProtect used */ |
| 142 | unsigned aProtect[10]; /* Saved values of protectMask */ |
| 143 | } db = { |
| 144 | PROTECT_USER|PROTECT_CONFIG, /* protectMask */ |
| 145 | 0, 0, 0, 0, 0, 0, }; |
| @@ -247,11 +248,11 @@ | |
| 248 | db.nBegin--; |
| 249 | if( db.nBegin==0 ){ |
| 250 | int i; |
| 251 | if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){ |
| 252 | i = 0; |
| 253 | db_protect_only(PROTECT_SENSITIVE); |
| 254 | while( db.nBeforeCommit ){ |
| 255 | db.nBeforeCommit--; |
| 256 | sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0); |
| 257 | sqlite3_free(db.azBeforeCommit[i]); |
| 258 | i++; |
| @@ -330,38 +331,58 @@ | |
| 331 | db.nCommitHook++; |
| 332 | } |
| 333 | |
| 334 | #if INTERFACE |
| 335 | /* |
| 336 | ** Flag bits for db_protect() and db_unprotect() indicating which parts |
| 337 | ** of the databases should be write protected or write enabled, respectively. |
| 338 | */ |
| 339 | #define PROTECT_USER 0x01 /* USER table */ |
| 340 | #define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */ |
| 341 | #define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */ |
| 342 | #define PROTECT_READONLY 0x08 /* everything except TEMP tables */ |
| 343 | #define PROTECT_ALL 0x0f /* All of the above */ |
| 344 | #define PROTECT_NONE 0x00 /* Nothing. Everything is open */ |
| 345 | #endif /* INTERFACE */ |
| 346 | |
| 347 | /* |
| 348 | ** Enable or disable database write protections. |
| 349 | ** |
| 350 | ** db_protext(X) Add protects on X |
| 351 | ** db_unprotect(X) Remove protections on X |
| 352 | ** db_protect_only(X) Remove all prior protections then set |
| 353 | ** protections to only X. |
| 354 | ** |
| 355 | ** Each of these routines pushes the previous protection mask onto |
| 356 | ** a finite-size stack. Each should be followed by a call to |
| 357 | ** db_protect_pop() to pop the stack and restore the protections that |
| 358 | ** existed prior to the call. The protection mask stack has a limited |
| 359 | ** depth, so take care not to next calls too deeply. |
| 360 | */ |
| 361 | void db_protect_only(unsigned flags){ |
| 362 | if( db.nProtect>=count(db.aProtect) ){ |
| 363 | fossil_fatal("too many db_protect() calls"); |
| 364 | } |
| 365 | db.aProtect[db.nProtect++] = db.protectMask; |
| 366 | if( (flags & PROTECT_SENSITIVE)!=0 |
| 367 | && (db.protectMask & PROTECT_SENSITIVE)==0 |
| 368 | && db.bProtectTriggers==0 |
| 369 | ){ |
| 370 | db_multi_exec( |
| 371 | "CREATE TEMP TRIGGER IF NOT EXISTS protect_1" |
| 372 | " BEFORE INSERT ON config WHEN protected_setting(new.name)" |
| 373 | " BEGIN SELECT raise(abort,'not authorized'); END;\n" |
| 374 | "CREATE TEMP TRIGGER IF NOT EXISTS protect_2" |
| 375 | " BEFORE UPDATE ON config WHEN protected_setting(new.name)" |
| 376 | " BEGIN SELECT raise(abort,'not authorized'); END;\n" |
| 377 | ); |
| 378 | db.bProtectTriggers = 1; |
| 379 | } |
| 380 | db.protectMask = flags; |
| 381 | } |
| 382 | void db_protect(unsigned flags){ |
| 383 | db_protect_only(db.protectMask | flags); |
| 384 | } |
| 385 | void db_unprotect(unsigned flags){ |
| 386 | if( db.nProtect>=count(db.aProtect) ){ |
| 387 | fossil_fatal("too many db_unprotect() calls"); |
| 388 | } |
| @@ -370,10 +391,21 @@ | |
| 391 | } |
| 392 | void db_protect_pop(void){ |
| 393 | if( db.nProtect<1 ) fossil_fatal("too many db_protect_pop() calls"); |
| 394 | db.protectMask = db.aProtect[--db.nProtect]; |
| 395 | } |
| 396 | |
| 397 | /* |
| 398 | ** Verify that the desired database write pertections are in place. |
| 399 | ** Throw a fatal error if not. |
| 400 | */ |
| 401 | void db_assert_protected(unsigned flags){ |
| 402 | if( (flags & db.protectMask)!=flags ){ |
| 403 | fossil_fatal("internal security assertion fault: missing " |
| 404 | "database protection bits: %02x", flags & ~db.protectMask); |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | /* |
| 409 | ** Every Fossil database connection automatically registers the following |
| 410 | ** overarching authenticator callback, and leaves it registered for the |
| 411 | ** duration of the connection. This authenticator will call any |
| @@ -396,10 +428,13 @@ | |
| 428 | && sqlite3_stricmp(z0,"user")==0 ){ |
| 429 | rc = SQLITE_DENY; |
| 430 | }else if( (db.protectMask & PROTECT_CONFIG)!=0 && |
| 431 | (sqlite3_stricmp(z0,"config")==0 || |
| 432 | sqlite3_stricmp(z0,"global_config")==0) ){ |
| 433 | rc = SQLITE_DENY; |
| 434 | }else if( (db.protectMask & PROTECT_SENSITIVE)!=0 && |
| 435 | sqlite3_stricmp(z0,"global_config")==0 ){ |
| 436 | rc = SQLITE_DENY; |
| 437 | }else if( (db.protectMask & PROTECT_READONLY)!=0 |
| 438 | && sqlite3_stricmp(z2,"temp")!=0 ){ |
| 439 | rc = SQLITE_DENY; |
| 440 | } |
| @@ -2320,10 +2355,11 @@ | |
| 2355 | } |
| 2356 | g.db = 0; |
| 2357 | } |
| 2358 | g.repositoryOpen = 0; |
| 2359 | g.localOpen = 0; |
| 2360 | db.bProtectTriggers = 0; |
| 2361 | assert( g.dbConfig==0 ); |
| 2362 | assert( g.zConfigDbName==0 ); |
| 2363 | backoffice_run_if_needed(); |
| 2364 | } |
| 2365 | |
| @@ -3234,11 +3270,11 @@ | |
| 3270 | fossil_free(zRepoSetting); |
| 3271 | if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){ |
| 3272 | Blob localRoot; |
| 3273 | file_canonical_name(g.zLocalRoot, &localRoot, 1); |
| 3274 | zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot)); |
| 3275 | db_unprotect(PROTECT_CONFIG); |
| 3276 | db_multi_exec( |
| 3277 | "DELETE FROM global_config WHERE name %s = %Q;", |
| 3278 | filename_collation(), zCkoutSetting |
| 3279 | ); |
| 3280 | db_multi_exec( |
| 3281 |