Fossil SCM
Enhancements to the command-string sanitizer safety-net.
Commit
68b68ce673ed652af8972ed2b778edd4870e488f775580e35d4110814e71f77c
Parent
f618a25cc9877c3…
1 file changed
+78
-13
+78
-13
| --- src/util.c | ||
| +++ src/util.c | ||
| @@ -156,13 +156,20 @@ | ||
| 156 | 156 | } |
| 157 | 157 | } |
| 158 | 158 | return zStart; |
| 159 | 159 | } |
| 160 | 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 | + | |
| 161 | 168 | /* |
| 162 | 169 | ** 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: | |
| 164 | 171 | ** |
| 165 | 172 | ** * Any occurrance of '$' or '`' except after \ |
| 166 | 173 | ** * Any of the following characters, unquoted: ;|& or \n except |
| 167 | 174 | ** these characters are allowed as the very last character in the |
| 168 | 175 | ** string. |
| @@ -171,28 +178,30 @@ | ||
| 171 | 178 | ** This routine is intended as a second line of defense against attack. |
| 172 | 179 | ** It should never fail. Dangerous shell strings should be detected and |
| 173 | 180 | ** fixed before calling fossil_system(). This routine serves only as a |
| 174 | 181 | ** safety net in case of bugs elsewhere in the system. |
| 175 | 182 | ** |
| 176 | -** If an unsafe string is seen, the process aborts. | |
| 183 | +** If an unsafe string is seen, either abort or return false. | |
| 177 | 184 | */ |
| 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 */ | |
| 179 | 189 | int inQuote = 0; |
| 180 | 190 | 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++){ | |
| 183 | 192 | switch( c ){ |
| 184 | 193 | case '$': |
| 185 | 194 | case '`': { |
| 186 | - unsafe = i+1; | |
| 195 | + if( inQuote!='\'' ) unsafe = i+1; | |
| 187 | 196 | break; |
| 188 | 197 | } |
| 189 | 198 | case ';': |
| 190 | 199 | case '|': |
| 191 | 200 | case '&': |
| 192 | 201 | case '\n': { |
| 193 | - if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1; | |
| 202 | + if( inQuote!='\'' && z[i+1]!=0 ) unsafe = i+1; | |
| 194 | 203 | break; |
| 195 | 204 | } |
| 196 | 205 | case '"': |
| 197 | 206 | case '\'': { |
| 198 | 207 | if( inQuote==0 ){ |
| @@ -203,21 +212,52 @@ | ||
| 203 | 212 | break; |
| 204 | 213 | } |
| 205 | 214 | case '\\': { |
| 206 | 215 | if( z[i+1]==0 ){ |
| 207 | 216 | unsafe = i+1; |
| 208 | - }else{ | |
| 217 | + }else if( inQuote!='\'' ){ | |
| 209 | 218 | i++; |
| 210 | 219 | } |
| 211 | 220 | break; |
| 212 | 221 | } |
| 213 | 222 | } |
| 214 | 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 | |
| 215 | 249 | 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 | + } | |
| 218 | 257 | } |
| 258 | + return !unsafe; | |
| 219 | 259 | } |
| 220 | 260 | |
| 221 | 261 | /* |
| 222 | 262 | ** This function implements a cross-platform "system()" interface. |
| 223 | 263 | */ |
| @@ -230,19 +270,20 @@ | ||
| 230 | 270 | char *zNewCmd = mprintf("\"%s\"", zOrigCmd); |
| 231 | 271 | wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd); |
| 232 | 272 | if( g.fSystemTrace ) { |
| 233 | 273 | fossil_trace("SYSTEM: %s\n", zNewCmd); |
| 234 | 274 | } |
| 235 | - fossil_assert_safe_command_string(zOrigCmd); | |
| 236 | - rc = _wsystem(zUnicode); | |
| 275 | + if( fossil_assert_safe_command_string(zOrigCmd) ){ | |
| 276 | + rc = _wsystem(zUnicode); | |
| 277 | + } | |
| 237 | 278 | fossil_unicode_free(zUnicode); |
| 238 | 279 | free(zNewCmd); |
| 239 | 280 | #else |
| 240 | 281 | /* On unix, evaluate the command directly. |
| 241 | 282 | */ |
| 242 | 283 | 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; | |
| 244 | 285 | |
| 245 | 286 | /* Unix systems should never shell-out while processing an HTTP request, |
| 246 | 287 | ** either via CGI, SCGI, or direct HTTP. The following assert verifies |
| 247 | 288 | ** this. And the following assert proves that Fossil is not vulnerable |
| 248 | 289 | ** to the ShellShock or BashDoor bug. |
| @@ -254,10 +295,34 @@ | ||
| 254 | 295 | rc = system(zOrigCmd); |
| 255 | 296 | fossil_limit_memory(1); |
| 256 | 297 | #endif |
| 257 | 298 | return rc; |
| 258 | 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 | +} | |
| 259 | 324 | |
| 260 | 325 | /* |
| 261 | 326 | ** Like strcmp() except that it accepts NULL pointers. NULL sorts before |
| 262 | 327 | ** all non-NULL string pointers. Also, this strcmp() is a binary comparison |
| 263 | 328 | ** that does not consider locale. |
| 264 | 329 |
| --- 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 |