Fossil SCM

Add triggers to prevent changes to sensitive settings when PROTECT_SENSITIVE is engaged.

drh 2020-08-21 13:04 sec2020
Commit c9b9a77d592f031aa279b2accacb64440b9e03d2c96461cacf9ae889ba7c5141
2 files changed +2 -4 +56 -20
+2 -4
--- src/configure.c
+++ src/configure.c
@@ -438,13 +438,12 @@
438438
blob_append_sql(&sql,") VALUES(%s,%s",
439439
azToken[1] /*safe-for-%s*/, azToken[0]/*safe-for-%s*/);
440440
for(jj=2; jj<nToken; jj+=2){
441441
blob_append_sql(&sql, ",%s", azToken[jj+1] /*safe-for-%s*/);
442442
}
443
- db_unprotect(PROTECT_ALL);
443
+ db_protect_only(PROTECT_SENSITIVE);
444444
db_multi_exec("%s)", blob_sql_text(&sql));
445
- db_protect_pop();
446445
if( db_changes()==0 ){
447446
blob_reset(&sql);
448447
blob_append_sql(&sql, "UPDATE \"%w\" SET mtime=%s",
449448
&zName[1], azToken[0]/*safe-for-%s*/);
450449
for(jj=2; jj<nToken; jj+=2){
@@ -452,14 +451,13 @@
452451
azToken[jj], azToken[jj+1]/*safe-for-%s*/);
453452
}
454453
blob_append_sql(&sql, " WHERE \"%w\"=%s AND mtime<%s",
455454
aType[ii].zPrimKey, azToken[1]/*safe-for-%s*/,
456455
azToken[0]/*safe-for-%s*/);
457
- db_unprotect(PROTECT_ALL);
458456
db_multi_exec("%s", blob_sql_text(&sql));
459
- db_protect_pop();
460457
}
458
+ db_protect_pop();
461459
blob_reset(&sql);
462460
rebuildMask |= thisMask;
463461
}
464462
}
465463
466464
--- 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
+56 -20
--- src/db.c
+++ src/db.c
@@ -135,10 +135,11 @@
135135
const char *zStartFile; /* File in which transaction was started */
136136
int iStartLine; /* Line of zStartFile where transaction started */
137137
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
138138
void *pAuthArg; /* Argument to the authorizer */
139139
const char *zAuthName; /* Name of the authorizer */
140
+ int bProtectTriggers; /* True if protection triggers already exist */
140141
int nProtect; /* Slots of aProtect used */
141142
unsigned aProtect[10]; /* Saved values of protectMask */
142143
} db = {
143144
PROTECT_USER|PROTECT_CONFIG, /* protectMask */
144145
0, 0, 0, 0, 0, 0, };
@@ -247,11 +248,11 @@
247248
db.nBegin--;
248249
if( db.nBegin==0 ){
249250
int i;
250251
if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
251252
i = 0;
252
- db_unprotect(PROTECT_ALL);
253
+ db_protect_only(PROTECT_SENSITIVE);
253254
while( db.nBeforeCommit ){
254255
db.nBeforeCommit--;
255256
sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
256257
sqlite3_free(db.azBeforeCommit[i]);
257258
i++;
@@ -330,38 +331,58 @@
330331
db.nCommitHook++;
331332
}
332333
333334
#if INTERFACE
334335
/*
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.
336338
*/
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 */
341343
#define PROTECT_ALL 0x0f /* All of the above */
344
+#define PROTECT_NONE 0x00 /* Nothing. Everything is open */
342345
#endif /* INTERFACE */
343346
344347
/*
345348
** 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){
358362
if( db.nProtect>=count(db.aProtect) ){
359363
fossil_fatal("too many db_protect() calls");
360364
}
361365
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);
363384
}
364385
void db_unprotect(unsigned flags){
365386
if( db.nProtect>=count(db.aProtect) ){
366387
fossil_fatal("too many db_unprotect() calls");
367388
}
@@ -370,10 +391,21 @@
370391
}
371392
void db_protect_pop(void){
372393
if( db.nProtect<1 ) fossil_fatal("too many db_protect_pop() calls");
373394
db.protectMask = db.aProtect[--db.nProtect];
374395
}
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
+}
375407
376408
/*
377409
** Every Fossil database connection automatically registers the following
378410
** overarching authenticator callback, and leaves it registered for the
379411
** duration of the connection. This authenticator will call any
@@ -396,10 +428,13 @@
396428
&& sqlite3_stricmp(z0,"user")==0 ){
397429
rc = SQLITE_DENY;
398430
}else if( (db.protectMask & PROTECT_CONFIG)!=0 &&
399431
(sqlite3_stricmp(z0,"config")==0 ||
400432
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 ){
401436
rc = SQLITE_DENY;
402437
}else if( (db.protectMask & PROTECT_READONLY)!=0
403438
&& sqlite3_stricmp(z2,"temp")!=0 ){
404439
rc = SQLITE_DENY;
405440
}
@@ -2320,10 +2355,11 @@
23202355
}
23212356
g.db = 0;
23222357
}
23232358
g.repositoryOpen = 0;
23242359
g.localOpen = 0;
2360
+ db.bProtectTriggers = 0;
23252361
assert( g.dbConfig==0 );
23262362
assert( g.zConfigDbName==0 );
23272363
backoffice_run_if_needed();
23282364
}
23292365
@@ -3234,11 +3270,11 @@
32343270
fossil_free(zRepoSetting);
32353271
if( g.localOpen && g.zLocalRoot && g.zLocalRoot[0] ){
32363272
Blob localRoot;
32373273
file_canonical_name(g.zLocalRoot, &localRoot, 1);
32383274
zCkoutSetting = mprintf("ckout:%q", blob_str(&localRoot));
3239
- db_unprotect(PROTECT_CONFIG|PROTECT_SENSITIVE);
3275
+ db_unprotect(PROTECT_CONFIG);
32403276
db_multi_exec(
32413277
"DELETE FROM global_config WHERE name %s = %Q;",
32423278
filename_collation(), zCkoutSetting
32433279
);
32443280
db_multi_exec(
32453281
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button