Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Additional checks to ensure that db_set() and db_set_int() do not modify a sensitive setting unless PROTECT_BASELINE is disabled. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | sec2020 |
| Files: | files | file ages | folders |
| SHA3-256: |
ccdb5a9bb8b7a188e8c466a800ab9f9d |
| User & Date: | drh 2020-08-21 18:32:05.013 |
Context
|
2020-08-22
| ||
| 15:35 | Merge the latest enhancements from trunk. check-in: 11c1566a93 user: drh tags: sec2020 | |
| 10:45 | Merge Andy Goth's enhancements to the forum. Closed-Leaf check-in: 50cdb741db user: drh tags: sec2020-forum-refactor | |
|
2020-08-21
| ||
| 18:32 | Additional checks to ensure that db_set() and db_set_int() do not modify a sensitive setting unless PROTECT_BASELINE is disabled. check-in: ccdb5a9bb8 user: drh tags: sec2020 | |
| 15:08 | Add missing db_unprotect() calls to backoffice. check-in: c75dcc621b user: drh tags: sec2020 | |
Changes
Changes to src/db.c.
| ︙ | ︙ | |||
137 138 139 140 141 142 143 |
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
void *pAuthArg; /* Argument to the authorizer */
const char *zAuthName; /* Name of the authorizer */
int bProtectTriggers; /* True if protection triggers already exist */
int nProtect; /* Slots of aProtect used */
unsigned aProtect[10]; /* Saved values of protectMask */
} db = {
| | | 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
void *pAuthArg; /* Argument to the authorizer */
const char *zAuthName; /* Name of the authorizer */
int bProtectTriggers; /* True if protection triggers already exist */
int nProtect; /* Slots of aProtect used */
unsigned aProtect[10]; /* Saved values of protectMask */
} db = {
PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
0, 0, 0, 0, 0, 0, };
/*
** Arrange for the given file to be deleted on a failure.
*/
void db_delete_on_failure(const char *zFilename){
assert( db.nDeleteOnFail<count(db.azDeleteOnFail) );
|
| ︙ | ︙ | |||
336 337 338 339 340 341 342 | ** Flag bits for db_protect() and db_unprotect() indicating which parts ** of the databases should be write protected or write enabled, respectively. */ #define PROTECT_USER 0x01 /* USER table */ #define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */ #define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */ #define PROTECT_READONLY 0x08 /* everything except TEMP tables */ | > | | 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 | ** Flag bits for db_protect() and db_unprotect() indicating which parts ** of the databases should be write protected or write enabled, respectively. */ #define PROTECT_USER 0x01 /* USER table */ #define PROTECT_CONFIG 0x02 /* CONFIG and GLOBAL_CONFIG tables */ #define PROTECT_SENSITIVE 0x04 /* Sensitive and/or global settings */ #define PROTECT_READONLY 0x08 /* everything except TEMP tables */ #define PROTECT_BASELINE 0x10 /* protection system is working */ #define PROTECT_ALL 0x1f /* All of the above */ #define PROTECT_NONE 0x00 /* Nothing. Everything is open */ #endif /* INTERFACE */ /* ** Enable or disable database write protections. ** ** db_protext(X) Add protects on X |
| ︙ | ︙ | |||
408 409 410 411 412 413 414 |
** be compromised by an attack.
*/
void db_protect_only(unsigned flags){
if( db.nProtect>=count(db.aProtect)-2 ){
fossil_panic("too many db_protect() calls");
}
db.aProtect[db.nProtect++] = db.protectMask;
| | > > > | 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 |
** be compromised by an attack.
*/
void db_protect_only(unsigned flags){
if( db.nProtect>=count(db.aProtect)-2 ){
fossil_panic("too many db_protect() calls");
}
db.aProtect[db.nProtect++] = db.protectMask;
if( (flags & PROTECT_SENSITIVE)!=0
&& db.bProtectTriggers==0
&& g.repositoryOpen
){
/* Create the triggers needed to protect sensitive settings from
** being created or modified the first time that PROTECT_SENSITIVE
** is enabled. Deleting a sensitive setting is harmless, so there
** is not trigger to block deletes. After being created once, the
** triggers persist for the life of the database connection. */
db_multi_exec(
"CREATE TEMP TRIGGER protect_1 BEFORE INSERT ON config"
|
| ︙ | ︙ | |||
451 452 453 454 455 456 457 |
/*
** Verify that the desired database write pertections are in place.
** Throw a fatal error if not.
*/
void db_assert_protected(unsigned flags){
if( (flags & db.protectMask)!=flags ){
| | | > > > > > > > > > > > > > > | 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
/*
** Verify that the desired database write pertections are in place.
** Throw a fatal error if not.
*/
void db_assert_protected(unsigned flags){
if( (flags & db.protectMask)!=flags ){
fossil_panic("missing database write protection bits: %02x",
flags & ~db.protectMask);
}
}
/*
** Assert that either all protections are off (including PROTECT_BASELINE
** which is usually always enabled), or the setting named in the argument
** is no a sensitive setting.
**
** This assert() is used to verify that the db_set() and db_set_int()
** interfaces do not modify a sensitive setting.
*/
void db_assert_protection_off_or_not_sensitive(const char *zName){
if( db.protectMask!=0 && db_setting_is_protected(zName) ){
fossil_panic("unauthorized change to protected setting \"%s\"", zName);
}
}
/*
** Every Fossil database connection automatically registers the following
** overarching authenticator callback, and leaves it registered for the
** duration of the connection. This authenticator will call any
|
| ︙ | ︙ | |||
1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 |
sqlite3_result_error_nomem(context);
return;
}
strcpy(zOut, zTemp = obscure((char*)zIn));
fossil_free(zTemp);
sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
}
/*
** Implement the protected_setting(X) SQL function. This function returns
** true if X is the name of a protected (security-sensitive) setting and
** the db.protectSensitive flag is enabled. It returns false otherwise.
*/
| > > > > > > > > | < < < | < < < | 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 |
sqlite3_result_error_nomem(context);
return;
}
strcpy(zOut, zTemp = obscure((char*)zIn));
fossil_free(zTemp);
sqlite3_result_text(context, zOut, strlen(zOut), sqlite3_free);
}
/*
** Return True if zName is a protected (a.k.a. "sensitive") setting.
*/
int db_setting_is_protected(const char *zName){
const Setting *pSetting = zName ? db_find_setting(zName,0) : 0;
return pSetting!=0 && pSetting->sensitive!=0;
}
/*
** Implement the protected_setting(X) SQL function. This function returns
** true if X is the name of a protected (security-sensitive) setting and
** the db.protectSensitive flag is enabled. It returns false otherwise.
*/
LOCAL void db_protected_setting_func(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const char *zSetting;
if( (db.protectMask & PROTECT_SENSITIVE)==0 ){
sqlite3_result_int(context, 0);
return;
}
zSetting = (const char*)sqlite3_value_text(argv[0]);
sqlite3_result_int(context, db_setting_is_protected(zSetting));
}
/*
** Register the SQL functions that are useful both to the internal
** representation and to the "fossil sql" command.
*/
void db_add_aux_functions(sqlite3 *db){
|
| ︙ | ︙ | |||
1349 1350 1351 1352 1353 1354 1355 |
sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
alert_find_emailaddr_func, 0, 0);
sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
alert_display_name_func, 0, 0);
sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
db_obscure, 0, 0);
sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
| | | 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 |
sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
alert_find_emailaddr_func, 0, 0);
sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
alert_display_name_func, 0, 0);
sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
db_obscure, 0, 0);
sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
db_protected_setting_func, 0, 0);
}
#if USE_SEE
/*
** This is a pointer to the saved database encryption key string.
*/
static char *zSavedKey = 0;
|
| ︙ | ︙ | |||
3101 3102 3103 3104 3105 3106 3107 |
z = fossil_strdup(zDefault);
}else if( zFormat!=0 ){
z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
}
return z;
}
void db_set(const char *zName, const char *zValue, int globalFlag){
| | > < > | 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 |
z = fossil_strdup(zDefault);
}else if( zFormat!=0 ){
z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
}
return z;
}
void db_set(const char *zName, const char *zValue, int globalFlag){
db_assert_protection_off_or_not_sensitive(zName);
db_unprotect(PROTECT_CONFIG);
db_begin_transaction();
if( globalFlag ){
db_swap_connections();
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%Q)",
zName, zValue);
db_swap_connections();
}else{
db_multi_exec("REPLACE INTO config(name,value,mtime) VALUES(%Q,%Q,now())",
zName, zValue);
}
if( globalFlag && g.repositoryOpen ){
db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
}
db_end_transaction(0);
db_protect_pop();
}
void db_unset(const char *zName, int globalFlag){
db_begin_transaction();
db_unprotect(PROTECT_CONFIG);
if( globalFlag ){
db_swap_connections();
db_multi_exec("DELETE FROM global_config WHERE name=%Q", zName);
|
| ︙ | ︙ | |||
3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 |
db_swap_connections();
v = db_int(dflt, "SELECT value FROM global_config WHERE name=%Q", zName);
db_swap_connections();
}
return v;
}
void db_set_int(const char *zName, int value, int globalFlag){
db_unprotect(PROTECT_CONFIG);
if( globalFlag ){
db_swap_connections();
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
zName, value);
db_swap_connections();
}else{
| > | 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 |
db_swap_connections();
v = db_int(dflt, "SELECT value FROM global_config WHERE name=%Q", zName);
db_swap_connections();
}
return v;
}
void db_set_int(const char *zName, int value, int globalFlag){
db_assert_protection_off_or_not_sensitive(zName);
db_unprotect(PROTECT_CONFIG);
if( globalFlag ){
db_swap_connections();
db_multi_exec("REPLACE INTO global_config(name,value) VALUES(%Q,%d)",
zName, value);
db_swap_connections();
}else{
|
| ︙ | ︙ | |||
4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 |
}
if( globalFlag && isManifest ){
fossil_fatal("cannot set 'manifest' globally");
}
if( unsetFlag ){
db_unset(pSetting->name, globalFlag);
}else{
db_set(pSetting->name, g.argv[3], globalFlag);
}
if( isManifest && g.localOpen ){
manifest_to_disk(db_lget_int("checkout", 0));
}
}else{
while( pSetting->name ){
if( exactFlag ){
| > > | 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 |
}
if( globalFlag && isManifest ){
fossil_fatal("cannot set 'manifest' globally");
}
if( unsetFlag ){
db_unset(pSetting->name, globalFlag);
}else{
db_protect_only(PROTECT_NONE);
db_set(pSetting->name, g.argv[3], globalFlag);
db_protect_pop();
}
if( isManifest && g.localOpen ){
manifest_to_disk(db_lget_int("checkout", 0));
}
}else{
while( pSetting->name ){
if( exactFlag ){
|
| ︙ | ︙ |
Changes to src/setup.c.
| ︙ | ︙ | |||
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 |
if( zQ==0 && !disabled && P("submit") ){
zQ = "off";
}
if( zQ ){
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
login_verify_csrf_secret();
db_set(zVar, iQ ? "1" : "0", 0);
setup_incr_cfgcnt();
admin_log("Set option [%q] to [%q].",
zVar, iQ ? "on" : "off");
iVal = iQ;
}
}
@ <label><input type="checkbox" name="%s(zQParm)" \
| > > | 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
if( zQ==0 && !disabled && P("submit") ){
zQ = "off";
}
if( zQ ){
int iQ = fossil_strcmp(zQ,"on")==0 || atoi(zQ);
if( iQ!=iVal ){
login_verify_csrf_secret();
db_protect_only(PROTECT_NONE);
db_set(zVar, iQ ? "1" : "0", 0);
db_protect_pop();
setup_incr_cfgcnt();
admin_log("Set option [%q] to [%q].",
zVar, iQ ? "on" : "off");
iVal = iQ;
}
}
@ <label><input type="checkbox" name="%s(zQParm)" \
|
| ︙ | ︙ | |||
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
){
const char *zVal = db_get(zVar, zDflt);
const char *zQ = P(zQParm);
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
setup_incr_cfgcnt();
db_set(zVar, zQ, 0);
admin_log("Set entry_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
zVal = zQ;
}
@ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@ id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)" \
if( disabled ){
| > > | 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
){
const char *zVal = db_get(zVar, zDflt);
const char *zQ = P(zQParm);
if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
setup_incr_cfgcnt();
db_protect_only(PROTECT_NONE);
db_set(zVar, zQ, 0);
db_protect_pop();
admin_log("Set entry_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
zVal = zQ;
}
@ <input aria-label="%h(zLabel[0]?zLabel:zQParm)" type="text" \
@ id="%s(zQParm)" name="%s(zQParm)" value="%h(zVal)" size="%d(width)" \
if( disabled ){
|
| ︙ | ︙ | |||
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
int disabled /* 1 if the textarea should not be editable */
){
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
db_set(zVar, zQ, 0);
setup_incr_cfgcnt();
admin_log("Set textarea_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
if( rows>0 && cols>0 ){
@ <textarea id="id%s(zQP)" name="%s(zQP)" rows="%d(rows)" \
| > > | 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
int disabled /* 1 if the textarea should not be editable */
){
const char *z = db_get(zVar, zDflt);
const char *zQ = P(zQP);
if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
const int nZQ = (int)strlen(zQ);
login_verify_csrf_secret();
db_protect_only(PROTECT_NONE);
db_set(zVar, zQ, 0);
db_protect_pop();
setup_incr_cfgcnt();
admin_log("Set textarea_attribute %Q to: %.*s%s",
zVar, 20, zQ, (nZQ>20 ? "..." : ""));
z = zQ;
}
if( rows>0 && cols>0 ){
@ <textarea id="id%s(zQP)" name="%s(zQP)" rows="%d(rows)" \
|
| ︙ | ︙ |