Fossil SCM

Fix the new read-only-repo security mechanism so that it enables write access when necessary.

drh 2022-12-29 19:39 trunk
Commit f8363db81b52e7c5cf98961378d485050447ef073d967914e8b434aa370d4bec
3 files changed +9 -1 +8 -1 +5
+9 -1
--- src/db.c
+++ src/db.c
@@ -424,10 +424,12 @@
424424
/* Create the triggers needed to protect sensitive settings from
425425
** being created or modified the first time that PROTECT_SENSITIVE
426426
** is enabled. Deleting a sensitive setting is harmless, so there
427427
** is not trigger to block deletes. After being created once, the
428428
** triggers persist for the life of the database connection. */
429
+ unsigned savedProtectMask = db.protectMask;
430
+ db.protectMask = 0;
429431
db_multi_exec(
430432
"CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
431433
" WHEN protected_setting(new.name) BEGIN"
432434
" SELECT raise(abort,'not authorized');"
433435
"END;\n"
@@ -435,10 +437,11 @@
435437
" WHEN protected_setting(new.name) BEGIN"
436438
" SELECT raise(abort,'not authorized');"
437439
"END;\n"
438440
);
439441
db.bProtectTriggers = 1;
442
+ db.protectMask = savedProtectMask;
440443
}
441444
db.protectMask = flags;
442445
}
443446
void db_protect(unsigned flags){
444447
db_protect_only(db.protectMask | flags);
@@ -454,10 +457,13 @@
454457
if( db.nProtect<1 ){
455458
fossil_panic("too many db_protect_pop() calls");
456459
}
457460
db.protectMask = db.aProtect[--db.nProtect];
458461
}
462
+int db_is_protected(unsigned flags){
463
+ return (db.protectMask & flags)!=0;
464
+}
459465
460466
/*
461467
** Verify that the desired database write protections are in place.
462468
** Throw a fatal error if not.
463469
*/
@@ -536,11 +542,11 @@
536542
"SECURITY: authorizer blocks DML on protected GLOBAL_CONFIG table\n");
537543
rc = SQLITE_DENY;
538544
}else if( (db.protectMask & PROTECT_READONLY)!=0
539545
&& sqlite3_stricmp(z2,"temp")!=0 ){
540546
fossil_errorlog(
541
- "SECURITY: authorizer blocks DML on table \"%s\" due to the\n"
547
+ "SECURITY: authorizer blocks DML on table \"%s\" due to the "
542548
"request coming from a different origin\n", z0);
543549
rc = SQLITE_DENY;
544550
}
545551
break;
546552
}
@@ -2318,14 +2324,16 @@
23182324
if( g.eHashPolicy<0 ){
23192325
g.eHashPolicy = hname_default_policy();
23202326
db_set_int("hash-policy", g.eHashPolicy, 0);
23212327
}
23222328
2329
+#if 0 /* No longer automatic. Need to run "fossil rebuild" to migrate */
23232330
/* Make a change to the CHECK constraint on the BLOB table for
23242331
** version 2.0 and later.
23252332
*/
23262333
rebuild_schema_update_2_0(); /* Do the Fossil-2.0 schema updates */
2334
+#endif
23272335
23282336
/* Additional checks that occur when opening the check-out database */
23292337
if( g.localOpen ){
23302338
23312339
/* If the repository database that was just opened has been
23322340
--- src/db.c
+++ src/db.c
@@ -424,10 +424,12 @@
424 /* Create the triggers needed to protect sensitive settings from
425 ** being created or modified the first time that PROTECT_SENSITIVE
426 ** is enabled. Deleting a sensitive setting is harmless, so there
427 ** is not trigger to block deletes. After being created once, the
428 ** triggers persist for the life of the database connection. */
 
 
429 db_multi_exec(
430 "CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
431 " WHEN protected_setting(new.name) BEGIN"
432 " SELECT raise(abort,'not authorized');"
433 "END;\n"
@@ -435,10 +437,11 @@
435 " WHEN protected_setting(new.name) BEGIN"
436 " SELECT raise(abort,'not authorized');"
437 "END;\n"
438 );
439 db.bProtectTriggers = 1;
 
440 }
441 db.protectMask = flags;
442 }
443 void db_protect(unsigned flags){
444 db_protect_only(db.protectMask | flags);
@@ -454,10 +457,13 @@
454 if( db.nProtect<1 ){
455 fossil_panic("too many db_protect_pop() calls");
456 }
457 db.protectMask = db.aProtect[--db.nProtect];
458 }
 
 
 
459
460 /*
461 ** Verify that the desired database write protections are in place.
462 ** Throw a fatal error if not.
463 */
@@ -536,11 +542,11 @@
536 "SECURITY: authorizer blocks DML on protected GLOBAL_CONFIG table\n");
537 rc = SQLITE_DENY;
538 }else if( (db.protectMask & PROTECT_READONLY)!=0
539 && sqlite3_stricmp(z2,"temp")!=0 ){
540 fossil_errorlog(
541 "SECURITY: authorizer blocks DML on table \"%s\" due to the\n"
542 "request coming from a different origin\n", z0);
543 rc = SQLITE_DENY;
544 }
545 break;
546 }
@@ -2318,14 +2324,16 @@
2318 if( g.eHashPolicy<0 ){
2319 g.eHashPolicy = hname_default_policy();
2320 db_set_int("hash-policy", g.eHashPolicy, 0);
2321 }
2322
 
2323 /* Make a change to the CHECK constraint on the BLOB table for
2324 ** version 2.0 and later.
2325 */
2326 rebuild_schema_update_2_0(); /* Do the Fossil-2.0 schema updates */
 
2327
2328 /* Additional checks that occur when opening the check-out database */
2329 if( g.localOpen ){
2330
2331 /* If the repository database that was just opened has been
2332
--- src/db.c
+++ src/db.c
@@ -424,10 +424,12 @@
424 /* Create the triggers needed to protect sensitive settings from
425 ** being created or modified the first time that PROTECT_SENSITIVE
426 ** is enabled. Deleting a sensitive setting is harmless, so there
427 ** is not trigger to block deletes. After being created once, the
428 ** triggers persist for the life of the database connection. */
429 unsigned savedProtectMask = db.protectMask;
430 db.protectMask = 0;
431 db_multi_exec(
432 "CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
433 " WHEN protected_setting(new.name) BEGIN"
434 " SELECT raise(abort,'not authorized');"
435 "END;\n"
@@ -435,10 +437,11 @@
437 " WHEN protected_setting(new.name) BEGIN"
438 " SELECT raise(abort,'not authorized');"
439 "END;\n"
440 );
441 db.bProtectTriggers = 1;
442 db.protectMask = savedProtectMask;
443 }
444 db.protectMask = flags;
445 }
446 void db_protect(unsigned flags){
447 db_protect_only(db.protectMask | flags);
@@ -454,10 +457,13 @@
457 if( db.nProtect<1 ){
458 fossil_panic("too many db_protect_pop() calls");
459 }
460 db.protectMask = db.aProtect[--db.nProtect];
461 }
462 int db_is_protected(unsigned flags){
463 return (db.protectMask & flags)!=0;
464 }
465
466 /*
467 ** Verify that the desired database write protections are in place.
468 ** Throw a fatal error if not.
469 */
@@ -536,11 +542,11 @@
542 "SECURITY: authorizer blocks DML on protected GLOBAL_CONFIG table\n");
543 rc = SQLITE_DENY;
544 }else if( (db.protectMask & PROTECT_READONLY)!=0
545 && sqlite3_stricmp(z2,"temp")!=0 ){
546 fossil_errorlog(
547 "SECURITY: authorizer blocks DML on table \"%s\" due to the "
548 "request coming from a different origin\n", z0);
549 rc = SQLITE_DENY;
550 }
551 break;
552 }
@@ -2318,14 +2324,16 @@
2324 if( g.eHashPolicy<0 ){
2325 g.eHashPolicy = hname_default_policy();
2326 db_set_int("hash-policy", g.eHashPolicy, 0);
2327 }
2328
2329 #if 0 /* No longer automatic. Need to run "fossil rebuild" to migrate */
2330 /* Make a change to the CHECK constraint on the BLOB table for
2331 ** version 2.0 and later.
2332 */
2333 rebuild_schema_update_2_0(); /* Do the Fossil-2.0 schema updates */
2334 #endif
2335
2336 /* Additional checks that occur when opening the check-out database */
2337 if( g.localOpen ){
2338
2339 /* If the repository database that was just opened has been
2340
+8 -1
--- src/doc.c
+++ src/doc.c
@@ -650,12 +650,19 @@
650650
** Look for a file named zName in the check-in with RID=vid. Load the content
651651
** of that file into pContent and return the RID for the file. Or return 0
652652
** if the file is not found or could not be loaded.
653653
*/
654654
int doc_load_content(int vid, const char *zName, Blob *pContent){
655
- int writable = db_is_writeable("repository");
655
+ int writable;
656656
int rid; /* The RID of the file being loaded */
657
+ if( db_is_protected(PROTECT_READONLY)
658
+ || !db_is_writeable("repository")
659
+ ){
660
+ writable = 0;
661
+ }else{
662
+ writable = 1;
663
+ }
657664
if( writable ){
658665
db_end_transaction(0);
659666
db_begin_write();
660667
}
661668
if( !db_table_exists("repository", "vcache") || !writable ){
662669
--- src/doc.c
+++ src/doc.c
@@ -650,12 +650,19 @@
650 ** Look for a file named zName in the check-in with RID=vid. Load the content
651 ** of that file into pContent and return the RID for the file. Or return 0
652 ** if the file is not found or could not be loaded.
653 */
654 int doc_load_content(int vid, const char *zName, Blob *pContent){
655 int writable = db_is_writeable("repository");
656 int rid; /* The RID of the file being loaded */
 
 
 
 
 
 
 
657 if( writable ){
658 db_end_transaction(0);
659 db_begin_write();
660 }
661 if( !db_table_exists("repository", "vcache") || !writable ){
662
--- src/doc.c
+++ src/doc.c
@@ -650,12 +650,19 @@
650 ** Look for a file named zName in the check-in with RID=vid. Load the content
651 ** of that file into pContent and return the RID for the file. Or return 0
652 ** if the file is not found or could not be loaded.
653 */
654 int doc_load_content(int vid, const char *zName, Blob *pContent){
655 int writable;
656 int rid; /* The RID of the file being loaded */
657 if( db_is_protected(PROTECT_READONLY)
658 || !db_is_writeable("repository")
659 ){
660 writable = 0;
661 }else{
662 writable = 1;
663 }
664 if( writable ){
665 db_end_transaction(0);
666 db_begin_write();
667 }
668 if( !db_table_exists("repository", "vcache") || !writable ){
669
+5
--- src/main.c
+++ src/main.c
@@ -1670,10 +1670,11 @@
16701670
const char *zPathInfo = PD("PATH_INFO", "");
16711671
char *zPath = NULL;
16721672
int i;
16731673
const CmdOrPage *pCmd = 0;
16741674
const char *zBase = g.zRepositoryName;
1675
+ int isReadonly = 0;
16751676
16761677
g.zPhase = "process_one_web_page";
16771678
#if !defined(_WIN32)
16781679
signal(SIGSEGV, sigsegv_handler);
16791680
#endif
@@ -2060,10 +2061,11 @@
20602061
}
20612062
#endif
20622063
if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
20632064
cgi_decode_post_parameters();
20642065
if( !cgi_same_origin() ){
2066
+ isReadonly = 1;
20652067
db_protect(PROTECT_READONLY);
20662068
}
20672069
}
20682070
if( g.fCgiTrace ){
20692071
fossil_trace("######## Calling %s #########\n", pCmd->zName);
@@ -2105,10 +2107,13 @@
21052107
Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
21062108
}
21072109
}
21082110
}
21092111
#endif
2112
+ if( isReadonly ){
2113
+ db_protect_pop();
2114
+ }
21102115
}
21112116
21122117
/* Return the result.
21132118
*/
21142119
g.zPhase = "web-page reply";
21152120
--- src/main.c
+++ src/main.c
@@ -1670,10 +1670,11 @@
1670 const char *zPathInfo = PD("PATH_INFO", "");
1671 char *zPath = NULL;
1672 int i;
1673 const CmdOrPage *pCmd = 0;
1674 const char *zBase = g.zRepositoryName;
 
1675
1676 g.zPhase = "process_one_web_page";
1677 #if !defined(_WIN32)
1678 signal(SIGSEGV, sigsegv_handler);
1679 #endif
@@ -2060,10 +2061,11 @@
2060 }
2061 #endif
2062 if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
2063 cgi_decode_post_parameters();
2064 if( !cgi_same_origin() ){
 
2065 db_protect(PROTECT_READONLY);
2066 }
2067 }
2068 if( g.fCgiTrace ){
2069 fossil_trace("######## Calling %s #########\n", pCmd->zName);
@@ -2105,10 +2107,13 @@
2105 Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
2106 }
2107 }
2108 }
2109 #endif
 
 
 
2110 }
2111
2112 /* Return the result.
2113 */
2114 g.zPhase = "web-page reply";
2115
--- src/main.c
+++ src/main.c
@@ -1670,10 +1670,11 @@
1670 const char *zPathInfo = PD("PATH_INFO", "");
1671 char *zPath = NULL;
1672 int i;
1673 const CmdOrPage *pCmd = 0;
1674 const char *zBase = g.zRepositoryName;
1675 int isReadonly = 0;
1676
1677 g.zPhase = "process_one_web_page";
1678 #if !defined(_WIN32)
1679 signal(SIGSEGV, sigsegv_handler);
1680 #endif
@@ -2060,10 +2061,11 @@
2061 }
2062 #endif
2063 if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
2064 cgi_decode_post_parameters();
2065 if( !cgi_same_origin() ){
2066 isReadonly = 1;
2067 db_protect(PROTECT_READONLY);
2068 }
2069 }
2070 if( g.fCgiTrace ){
2071 fossil_trace("######## Calling %s #########\n", pCmd->zName);
@@ -2105,10 +2107,13 @@
2107 Th_WebpageNotify(pCmd->zName+1, pCmd->eCmdFlags);
2108 }
2109 }
2110 }
2111 #endif
2112 if( isReadonly ){
2113 db_protect_pop();
2114 }
2115 }
2116
2117 /* Return the result.
2118 */
2119 g.zPhase = "web-page reply";
2120

Keyboard Shortcuts

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