Fossil SCM
Improvements to command-string sanitizing and the the sanitizer test command.
Commit
e3185aee7f33384ec2afe8275d810aee05e6253ea052e563f33a77748216771d
Parent
9c38a004adefea0…
1 file changed
+50
-12
+50
-12
| --- src/util.c | ||
| +++ src/util.c | ||
| @@ -156,10 +156,17 @@ | ||
| 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 | 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 \ |
| @@ -171,30 +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){ | |
| 179 | 186 | int unsafe = 0; |
| 180 | 187 | #ifndef _WIN32 |
| 181 | 188 | /* Unix */ |
| 182 | 189 | int inQuote = 0; |
| 183 | 190 | int i, c; |
| 184 | - for(i=0; (c = z[i])!=0; i++){ | |
| 191 | + for(i=0; !unsafe && (c = z[i])!=0; i++){ | |
| 185 | 192 | switch( c ){ |
| 186 | 193 | case '$': |
| 187 | 194 | case '`': { |
| 188 | - unsafe = i+1; | |
| 195 | + if( inQuote!='\'' ) unsafe = i+1; | |
| 189 | 196 | break; |
| 190 | 197 | } |
| 191 | 198 | case ';': |
| 192 | 199 | case '|': |
| 193 | 200 | case '&': |
| 194 | 201 | case '\n': { |
| 195 | - if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1; | |
| 202 | + if( inQuote!='\'' && z[i+1]!=0 ) unsafe = i+1; | |
| 196 | 203 | break; |
| 197 | 204 | } |
| 198 | 205 | case '"': |
| 199 | 206 | case '\'': { |
| 200 | 207 | if( inQuote==0 ){ |
| @@ -205,25 +212,52 @@ | ||
| 205 | 212 | break; |
| 206 | 213 | } |
| 207 | 214 | case '\\': { |
| 208 | 215 | if( z[i+1]==0 ){ |
| 209 | 216 | unsafe = i+1; |
| 210 | - }else{ | |
| 217 | + }else if( inQuote!='\'' ){ | |
| 211 | 218 | i++; |
| 212 | 219 | } |
| 213 | 220 | break; |
| 214 | 221 | } |
| 215 | 222 | } |
| 216 | 223 | } |
| 224 | + if( inQuote ) unsafe = i; | |
| 217 | 225 | #else |
| 218 | 226 | /* Windows */ |
| 219 | - | |
| 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; | |
| 220 | 248 | #endif |
| 221 | 249 | if( unsafe ){ |
| 222 | - fossil_fatal("Unsafe command string: %s\n%*shere ----^", | |
| 223 | - 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 | + } | |
| 224 | 257 | } |
| 258 | + return !unsafe; | |
| 225 | 259 | } |
| 226 | 260 | |
| 227 | 261 | /* |
| 228 | 262 | ** This function implements a cross-platform "system()" interface. |
| 229 | 263 | */ |
| @@ -236,19 +270,20 @@ | ||
| 236 | 270 | char *zNewCmd = mprintf("\"%s\"", zOrigCmd); |
| 237 | 271 | wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd); |
| 238 | 272 | if( g.fSystemTrace ) { |
| 239 | 273 | fossil_trace("SYSTEM: %s\n", zNewCmd); |
| 240 | 274 | } |
| 241 | - fossil_assert_safe_command_string(zOrigCmd); | |
| 242 | - rc = _wsystem(zUnicode); | |
| 275 | + if( fossil_assert_safe_command_string(zOrigCmd) ){ | |
| 276 | + rc = _wsystem(zUnicode); | |
| 277 | + } | |
| 243 | 278 | fossil_unicode_free(zUnicode); |
| 244 | 279 | free(zNewCmd); |
| 245 | 280 | #else |
| 246 | 281 | /* On unix, evaluate the command directly. |
| 247 | 282 | */ |
| 248 | 283 | if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd); |
| 249 | - fossil_assert_safe_command_string(zOrigCmd); | |
| 284 | + if( !fossil_assert_safe_command_string(zOrigCmd) ) return 1; | |
| 250 | 285 | |
| 251 | 286 | /* Unix systems should never shell-out while processing an HTTP request, |
| 252 | 287 | ** either via CGI, SCGI, or direct HTTP. The following assert verifies |
| 253 | 288 | ** this. And the following assert proves that Fossil is not vulnerable |
| 254 | 289 | ** to the ShellShock or BashDoor bug. |
| @@ -265,13 +300,16 @@ | ||
| 265 | 300 | |
| 266 | 301 | /* |
| 267 | 302 | ** COMMAND: test-fossil-system |
| 268 | 303 | ** |
| 269 | 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. | |
| 270 | 307 | */ |
| 271 | 308 | void test_fossil_system_cmd(void){ |
| 272 | 309 | char zLine[10000]; |
| 310 | + safeCmdStrTest = 1; | |
| 273 | 311 | while(1){ |
| 274 | 312 | size_t n; |
| 275 | 313 | printf("system-test> "); |
| 276 | 314 | fflush(stdout); |
| 277 | 315 | if( !fgets(zLine, sizeof(zLine), stdin) ) break; |
| 278 | 316 |
| --- src/util.c | |
| +++ src/util.c | |
| @@ -156,10 +156,17 @@ | |
| 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() on unix if it contains any of the following: |
| 164 | ** |
| 165 | ** * Any occurrance of '$' or '`' except after \ |
| @@ -171,30 +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 unsafe = 0; |
| 180 | #ifndef _WIN32 |
| 181 | /* Unix */ |
| 182 | int inQuote = 0; |
| 183 | int i, c; |
| 184 | for(i=0; (c = z[i])!=0; i++){ |
| 185 | switch( c ){ |
| 186 | case '$': |
| 187 | case '`': { |
| 188 | unsafe = i+1; |
| 189 | break; |
| 190 | } |
| 191 | case ';': |
| 192 | case '|': |
| 193 | case '&': |
| 194 | case '\n': { |
| 195 | if( inQuote==0 && z[i+1]!=0 ) unsafe = i+1; |
| 196 | break; |
| 197 | } |
| 198 | case '"': |
| 199 | case '\'': { |
| 200 | if( inQuote==0 ){ |
| @@ -205,25 +212,52 @@ | |
| 205 | break; |
| 206 | } |
| 207 | case '\\': { |
| 208 | if( z[i+1]==0 ){ |
| 209 | unsafe = i+1; |
| 210 | }else{ |
| 211 | i++; |
| 212 | } |
| 213 | break; |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | #else |
| 218 | /* Windows */ |
| 219 | |
| 220 | #endif |
| 221 | if( unsafe ){ |
| 222 | fossil_fatal("Unsafe command string: %s\n%*shere ----^", |
| 223 | z, unsafe+13, ""); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | /* |
| 228 | ** This function implements a cross-platform "system()" interface. |
| 229 | */ |
| @@ -236,19 +270,20 @@ | |
| 236 | char *zNewCmd = mprintf("\"%s\"", zOrigCmd); |
| 237 | wchar_t *zUnicode = fossil_utf8_to_unicode(zNewCmd); |
| 238 | if( g.fSystemTrace ) { |
| 239 | fossil_trace("SYSTEM: %s\n", zNewCmd); |
| 240 | } |
| 241 | fossil_assert_safe_command_string(zOrigCmd); |
| 242 | rc = _wsystem(zUnicode); |
| 243 | fossil_unicode_free(zUnicode); |
| 244 | free(zNewCmd); |
| 245 | #else |
| 246 | /* On unix, evaluate the command directly. |
| 247 | */ |
| 248 | if( g.fSystemTrace ) fprintf(stderr, "SYSTEM: %s\n", zOrigCmd); |
| 249 | fossil_assert_safe_command_string(zOrigCmd); |
| 250 | |
| 251 | /* Unix systems should never shell-out while processing an HTTP request, |
| 252 | ** either via CGI, SCGI, or direct HTTP. The following assert verifies |
| 253 | ** this. And the following assert proves that Fossil is not vulnerable |
| 254 | ** to the ShellShock or BashDoor bug. |
| @@ -265,13 +300,16 @@ | |
| 265 | |
| 266 | /* |
| 267 | ** COMMAND: test-fossil-system |
| 268 | ** |
| 269 | ** Read lines of input and send them to fossil_system() for evaluation. |
| 270 | */ |
| 271 | void test_fossil_system_cmd(void){ |
| 272 | char zLine[10000]; |
| 273 | while(1){ |
| 274 | size_t n; |
| 275 | printf("system-test> "); |
| 276 | fflush(stdout); |
| 277 | if( !fgets(zLine, sizeof(zLine), stdin) ) break; |
| 278 |
| --- src/util.c | |
| +++ src/util.c | |
| @@ -156,10 +156,17 @@ | |
| 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 \ |
| @@ -171,30 +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 ){ |
| @@ -205,25 +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 | */ |
| @@ -236,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. |
| @@ -265,13 +300,16 @@ | |
| 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 |