Fossil SCM

Enhancements to the command-string sanitizer safety-net.

drh 2020-06-09 17:42 trunk merge
Commit 68b68ce673ed652af8972ed2b778edd4870e488f775580e35d4110814e71f77c
1 file changed +78 -13
+78 -13
--- src/util.c
+++ src/util.c
@@ -156,13 +156,20 @@
156156
}
157157
}
158158
return zStart;
159159
}
160160
161
+/*
162
+** If this local variable is set, fossil_assert_safe_command_string()
163
+** returns false on an unsafe command-string rather than abort. Set
164
+** this variable for testing.
165
+*/
166
+static int safeCmdStrTest = 0;
167
+
161168
/*
162169
** Check the input string to ensure that it is safe to pass into system().
163
-** A string is unsafe for system() if it contains any of the following:
170
+** A string is unsafe for system() on unix if it contains any of the following:
164171
**
165172
** * Any occurrance of '$' or '`' except after \
166173
** * Any of the following characters, unquoted: ;|& or \n except
167174
** these characters are allowed as the very last character in the
168175
** string.
@@ -171,28 +178,30 @@
171178
** This routine is intended as a second line of defense against attack.
172179
** It should never fail. Dangerous shell strings should be detected and
173180
** fixed before calling fossil_system(). This routine serves only as a
174181
** safety net in case of bugs elsewhere in the system.
175182
**
176
-** If an unsafe string is seen, the process aborts.
183
+** If an unsafe string is seen, either abort or return false.
177184
*/
178
-void fossil_assert_safe_command_string(const char *z){
185
+static int fossil_assert_safe_command_string(const char *z){
186
+ int unsafe = 0;
187
+#ifndef _WIN32
188
+ /* Unix */
179189
int inQuote = 0;
180190
int i, c;
181
- int unsafe = 0;
182
- for(i=0; (c = z[i])!=0; i++){
191
+ for(i=0; !unsafe && (c = z[i])!=0; i++){
183192
switch( c ){
184193
case '$':
185194
case '`': {
186
- unsafe = i+1;
195
+ if( inQuote!='\'' ) unsafe = i+1;
187196
break;
188197
}
189198
case ';':
190199
case '|':
191200
case '&':
192201
case '\n': {
193
- if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1;
202
+ if( inQuote!='\'' && z[i+1]!=0 ) unsafe = i+1;
194203
break;
195204
}
196205
case '"':
197206
case '\'': {
198207
if( inQuote==0 ){
@@ -203,21 +212,52 @@
203212
break;
204213
}
205214
case '\\': {
206215
if( z[i+1]==0 ){
207216
unsafe = i+1;
208
- }else{
217
+ }else if( inQuote!='\'' ){
209218
i++;
210219
}
211220
break;
212221
}
213222
}
214223
}
224
+ if( inQuote ) unsafe = i;
225
+#else
226
+ /* Windows */
227
+ int i, c;
228
+ int inQuote = 0;
229
+ for(i=0; !unsafe && (c = z[i])!=0; i++){
230
+ switch( c ){
231
+ case '|':
232
+ case '&':
233
+ case '\n': {
234
+ if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1;
235
+ break;
236
+ }
237
+ case '"': {
238
+ if( inQuote==c ){
239
+ inQuote = 0;
240
+ }else{
241
+ inQuote = c;
242
+ }
243
+ break;
244
+ }
245
+ }
246
+ }
247
+ if( inQuote ) unsafe = i;
248
+#endif
215249
if( unsafe ){
216
- fossil_fatal("Unsafe command string: %s\n%*shere ----^",
217
- z, unsafe+13, "");
250
+ char *zMsg = mprintf("Unsafe command string: %s\n%*shere ----^",
251
+ z, unsafe+13, "");
252
+ if( safeCmdStrTest ){
253
+ fossil_print("%z\n", zMsg);
254
+ }else{
255
+ fossil_panic("%s", zMsg);
256
+ }
218257
}
258
+ return !unsafe;
219259
}
220260
221261
/*
222262
** This function implements a cross-platform "system()" interface.
223263
*/
@@ -230,19 +270,20 @@
230270
char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
231271
wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd);
232272
if( g.fSystemTrace ) {
233273
fossil_trace("SYSTEM: %s\n", zNewCmd);
234274
}
235
- fossil_assert_safe_command_string(zOrigCmd);
236
- rc = _wsystem(zUnicode);
275
+ if( fossil_assert_safe_command_string(zOrigCmd) ){
276
+ rc = _wsystem(zUnicode);
277
+ }
237278
fossil_unicode_free(zUnicode);
238279
free(zNewCmd);
239280
#else
240281
/* On unix, evaluate the command directly.
241282
*/
242283
if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd);
243
- fossil_assert_safe_command_string(zOrigCmd);
284
+ if( !fossil_assert_safe_command_string(zOrigCmd) ) return 1;
244285
245286
/* Unix systems should never shell-out while processing an HTTP request,
246287
** either via CGI, SCGI, or direct HTTP. The following assert verifies
247288
** this. And the following assert proves that Fossil is not vulnerable
248289
** to the ShellShock or BashDoor bug.
@@ -254,10 +295,34 @@
254295
rc = system(zOrigCmd);
255296
fossil_limit_memory(1);
256297
#endif
257298
return rc;
258299
}
300
+
301
+/*
302
+** COMMAND: test-fossil-system
303
+**
304
+** Read lines of input and send them to fossil_system() for evaluation.
305
+** Use this command to verify that fossil_system() will not run "unsafe"
306
+** commands.
307
+*/
308
+void test_fossil_system_cmd(void){
309
+ char zLine[10000];
310
+ safeCmdStrTest = 1;
311
+ while(1){
312
+ size_t n;
313
+ printf("system-test> ");
314
+ fflush(stdout);
315
+ if( !fgets(zLine, sizeof(zLine), stdin) ) break;
316
+ n = strlen(zLine);
317
+ while( n>0 && fossil_isspace(zLine[n-1]) ) n--;
318
+ zLine[n] = 0;
319
+ printf("cmd: [%s]\n", zLine);
320
+ fflush(stdout);
321
+ fossil_system(zLine);
322
+ }
323
+}
259324
260325
/*
261326
** Like strcmp() except that it accepts NULL pointers. NULL sorts before
262327
** all non-NULL string pointers. Also, this strcmp() is a binary comparison
263328
** that does not consider locale.
264329
--- src/util.c
+++ src/util.c
@@ -156,13 +156,20 @@
156 }
157 }
158 return zStart;
159 }
160
 
 
 
 
 
 
 
161 /*
162 ** Check the input string to ensure that it is safe to pass into system().
163 ** A string is unsafe for system() if it contains any of the following:
164 **
165 ** * Any occurrance of '$' or '`' except after \
166 ** * Any of the following characters, unquoted: ;|& or \n except
167 ** these characters are allowed as the very last character in the
168 ** string.
@@ -171,28 +178,30 @@
171 ** This routine is intended as a second line of defense against attack.
172 ** It should never fail. Dangerous shell strings should be detected and
173 ** fixed before calling fossil_system(). This routine serves only as a
174 ** safety net in case of bugs elsewhere in the system.
175 **
176 ** If an unsafe string is seen, the process aborts.
177 */
178 void fossil_assert_safe_command_string(const char *z){
 
 
 
179 int inQuote = 0;
180 int i, c;
181 int unsafe = 0;
182 for(i=0; (c = z[i])!=0; i++){
183 switch( c ){
184 case '$':
185 case '`': {
186 unsafe = i+1;
187 break;
188 }
189 case ';':
190 case '|':
191 case '&':
192 case '\n': {
193 if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1;
194 break;
195 }
196 case '"':
197 case '\'': {
198 if( inQuote==0 ){
@@ -203,21 +212,52 @@
203 break;
204 }
205 case '\\': {
206 if( z[i+1]==0 ){
207 unsafe = i+1;
208 }else{
209 i++;
210 }
211 break;
212 }
213 }
214 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215 if( unsafe ){
216 fossil_fatal("Unsafe command string: %s\n%*shere ----^",
217 z, unsafe+13, "");
 
 
 
 
 
218 }
 
219 }
220
221 /*
222 ** This function implements a cross-platform "system()" interface.
223 */
@@ -230,19 +270,20 @@
230 char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
231 wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd);
232 if( g.fSystemTrace ) {
233 fossil_trace("SYSTEM: %s\n", zNewCmd);
234 }
235 fossil_assert_safe_command_string(zOrigCmd);
236 rc = _wsystem(zUnicode);
 
237 fossil_unicode_free(zUnicode);
238 free(zNewCmd);
239 #else
240 /* On unix, evaluate the command directly.
241 */
242 if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd);
243 fossil_assert_safe_command_string(zOrigCmd);
244
245 /* Unix systems should never shell-out while processing an HTTP request,
246 ** either via CGI, SCGI, or direct HTTP. The following assert verifies
247 ** this. And the following assert proves that Fossil is not vulnerable
248 ** to the ShellShock or BashDoor bug.
@@ -254,10 +295,34 @@
254 rc = system(zOrigCmd);
255 fossil_limit_memory(1);
256 #endif
257 return rc;
258 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
260 /*
261 ** Like strcmp() except that it accepts NULL pointers. NULL sorts before
262 ** all non-NULL string pointers. Also, this strcmp() is a binary comparison
263 ** that does not consider locale.
264
--- src/util.c
+++ src/util.c
@@ -156,13 +156,20 @@
156 }
157 }
158 return zStart;
159 }
160
161 /*
162 ** If this local variable is set, fossil_assert_safe_command_string()
163 ** returns false on an unsafe command-string rather than abort. Set
164 ** this variable for testing.
165 */
166 static int safeCmdStrTest = 0;
167
168 /*
169 ** Check the input string to ensure that it is safe to pass into system().
170 ** A string is unsafe for system() on unix if it contains any of the following:
171 **
172 ** * Any occurrance of '$' or '`' except after \
173 ** * Any of the following characters, unquoted: ;|& or \n except
174 ** these characters are allowed as the very last character in the
175 ** string.
@@ -171,28 +178,30 @@
178 ** This routine is intended as a second line of defense against attack.
179 ** It should never fail. Dangerous shell strings should be detected and
180 ** fixed before calling fossil_system(). This routine serves only as a
181 ** safety net in case of bugs elsewhere in the system.
182 **
183 ** If an unsafe string is seen, either abort or return false.
184 */
185 static int fossil_assert_safe_command_string(const char *z){
186 int unsafe = 0;
187 #ifndef _WIN32
188 /* Unix */
189 int inQuote = 0;
190 int i, c;
191 for(i=0; !unsafe && (c = z[i])!=0; i++){
 
192 switch( c ){
193 case '$':
194 case '`': {
195 if( inQuote!='\'' ) unsafe = i+1;
196 break;
197 }
198 case ';':
199 case '|':
200 case '&':
201 case '\n': {
202 if( inQuote!='\'' && z[i+1]!=0 ) unsafe = i+1;
203 break;
204 }
205 case '"':
206 case '\'': {
207 if( inQuote==0 ){
@@ -203,21 +212,52 @@
212 break;
213 }
214 case '\\': {
215 if( z[i+1]==0 ){
216 unsafe = i+1;
217 }else if( inQuote!='\'' ){
218 i++;
219 }
220 break;
221 }
222 }
223 }
224 if( inQuote ) unsafe = i;
225 #else
226 /* Windows */
227 int i, c;
228 int inQuote = 0;
229 for(i=0; !unsafe && (c = z[i])!=0; i++){
230 switch( c ){
231 case '|':
232 case '&':
233 case '\n': {
234 if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1;
235 break;
236 }
237 case '"': {
238 if( inQuote==c ){
239 inQuote = 0;
240 }else{
241 inQuote = c;
242 }
243 break;
244 }
245 }
246 }
247 if( inQuote ) unsafe = i;
248 #endif
249 if( unsafe ){
250 char *zMsg = mprintf("Unsafe command string: %s\n%*shere ----^",
251 z, unsafe+13, "");
252 if( safeCmdStrTest ){
253 fossil_print("%z\n", zMsg);
254 }else{
255 fossil_panic("%s", zMsg);
256 }
257 }
258 return !unsafe;
259 }
260
261 /*
262 ** This function implements a cross-platform "system()" interface.
263 */
@@ -230,19 +270,20 @@
270 char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
271 wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd);
272 if( g.fSystemTrace ) {
273 fossil_trace("SYSTEM: %s\n", zNewCmd);
274 }
275 if( fossil_assert_safe_command_string(zOrigCmd) ){
276 rc = _wsystem(zUnicode);
277 }
278 fossil_unicode_free(zUnicode);
279 free(zNewCmd);
280 #else
281 /* On unix, evaluate the command directly.
282 */
283 if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd);
284 if( !fossil_assert_safe_command_string(zOrigCmd) ) return 1;
285
286 /* Unix systems should never shell-out while processing an HTTP request,
287 ** either via CGI, SCGI, or direct HTTP. The following assert verifies
288 ** this. And the following assert proves that Fossil is not vulnerable
289 ** to the ShellShock or BashDoor bug.
@@ -254,10 +295,34 @@
295 rc = system(zOrigCmd);
296 fossil_limit_memory(1);
297 #endif
298 return rc;
299 }
300
301 /*
302 ** COMMAND: test-fossil-system
303 **
304 ** Read lines of input and send them to fossil_system() for evaluation.
305 ** Use this command to verify that fossil_system() will not run "unsafe"
306 ** commands.
307 */
308 void test_fossil_system_cmd(void){
309 char zLine[10000];
310 safeCmdStrTest = 1;
311 while(1){
312 size_t n;
313 printf("system-test> ");
314 fflush(stdout);
315 if( !fgets(zLine, sizeof(zLine), stdin) ) break;
316 n = strlen(zLine);
317 while( n>0 && fossil_isspace(zLine[n-1]) ) n--;
318 zLine[n] = 0;
319 printf("cmd: [%s]\n", zLine);
320 fflush(stdout);
321 fossil_system(zLine);
322 }
323 }
324
325 /*
326 ** Like strcmp() except that it accepts NULL pointers. NULL sorts before
327 ** all non-NULL string pointers. Also, this strcmp() is a binary comparison
328 ** that does not consider locale.
329

Keyboard Shortcuts

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