Fossil SCM
More work on making the xfer protocol fail fast for certain invalid inputs.
Commit
f9f90d9b15cb3669d803d5ebf4e0726cfc1c906cfa4bafe2b22ee0a4754e7afc
Parent
295b39603609784…
1 file changed
+28
-6
+28
-6
| --- src/xfer.c | ||
| +++ src/xfer.c | ||
| @@ -336,10 +336,11 @@ | ||
| 336 | 336 | || !blob_is_filename(&pXfer->aToken[1]) |
| 337 | 337 | || !blob_is_int64(&pXfer->aToken[2], &mtime) |
| 338 | 338 | || (!blob_eq(pHash,"-") && !blob_is_hname(pHash)) |
| 339 | 339 | || !blob_is_int(&pXfer->aToken[4], &sz) |
| 340 | 340 | || !blob_is_int(&pXfer->aToken[5], &flags) |
| 341 | + || (mtime<0 || sz<0 || flags<0) | |
| 341 | 342 | ){ |
| 342 | 343 | blob_appendf(&pXfer->err, "malformed uvfile line"); |
| 343 | 344 | return; |
| 344 | 345 | } |
| 345 | 346 | blob_init(&content, 0, 0); |
| @@ -1122,19 +1123,20 @@ | ||
| 1122 | 1123 | return db_get("xfer-ticket-script", 0); |
| 1123 | 1124 | } |
| 1124 | 1125 | |
| 1125 | 1126 | /* |
| 1126 | 1127 | ** Reset the CGI content, roll back any pending db transaction, and |
| 1127 | -** emit an "error" xfer message, which must be pre-fossilized by the | |
| 1128 | -** caller. | |
| 1128 | +** emit an "error" xfer message. The message text gets fossil-encoded | |
| 1129 | +** by this function. This is only intended for use with | |
| 1130 | +** fail-fast/fatal errors, not ones which can be skipped over. | |
| 1129 | 1131 | */ |
| 1130 | -static void xfer_error(const char *zFossilizedMsg){ | |
| 1132 | +static void xfer_fatal_error(const char *zMsg){ | |
| 1131 | 1133 | cgi_reset_content(); |
| 1132 | - if( db_transaction_nesting_depth() > 0 ){ | |
| 1134 | + if( db_transaction_nesting_depth()>0 ){ | |
| 1133 | 1135 | db_rollback_transaction(); |
| 1134 | 1136 | } |
| 1135 | - @ error %s(zFossilizedMsg) | |
| 1137 | + @ error %F(zMsg) | |
| 1136 | 1138 | } |
| 1137 | 1139 | |
| 1138 | 1140 | /* |
| 1139 | 1141 | ** Run the specified TH1 script, if any, and returns 1 on error. |
| 1140 | 1142 | */ |
| @@ -1475,11 +1477,11 @@ | ||
| 1475 | 1477 | if( iVers>=3 ){ |
| 1476 | 1478 | cgi_set_content_type("application/x-fossil-uncompressed"); |
| 1477 | 1479 | } |
| 1478 | 1480 | blob_is_int(&xfer.aToken[2], &seqno); |
| 1479 | 1481 | if( seqno<=0 ){ |
| 1480 | - xfer_error("invalid\\sclone\\ssequence\\snumber"); | |
| 1482 | + xfer_fatal_error("invalid clone sequence number"); | |
| 1481 | 1483 | return; |
| 1482 | 1484 | } |
| 1483 | 1485 | max = db_int(0, "SELECT max(rid) FROM blob"); |
| 1484 | 1486 | while( xfer.mxSend>(int)blob_size(xfer.pOut) && seqno<=max){ |
| 1485 | 1487 | if( time(NULL) >= xfer.maxTime ) break; |
| @@ -1549,10 +1551,14 @@ | ||
| 1549 | 1551 | */ |
| 1550 | 1552 | if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 |
| 1551 | 1553 | && blob_is_int(&xfer.aToken[2], &size) ){ |
| 1552 | 1554 | const char *zName = blob_str(&xfer.aToken[1]); |
| 1553 | 1555 | Blob content; |
| 1556 | + if( size<0 ){ | |
| 1557 | + xfer_fatal_error("invalid config record"); | |
| 1558 | + return; | |
| 1559 | + } | |
| 1554 | 1560 | blob_zero(&content); |
| 1555 | 1561 | blob_extract(xfer.pIn, size, &content); |
| 1556 | 1562 | if( !g.perm.Admin ){ |
| 1557 | 1563 | cgi_reset_content(); |
| 1558 | 1564 | @ error not\sauthorized\sto\spush\sconfiguration |
| @@ -2504,10 +2510,14 @@ | ||
| 2504 | 2510 | && (blob_eq(&xfer.aToken[3],"-") || blob_is_hname(&xfer.aToken[3])) |
| 2505 | 2511 | ){ |
| 2506 | 2512 | const char *zName = blob_str(&xfer.aToken[1]); |
| 2507 | 2513 | const char *zHash = blob_str(&xfer.aToken[3]); |
| 2508 | 2514 | int iStatus; |
| 2515 | + if( mtime<0 || size<0 ){ | |
| 2516 | + xfer_fatal_error("invalid uvigot"); | |
| 2517 | + return ++nErr; | |
| 2518 | + } | |
| 2509 | 2519 | iStatus = unversioned_status(zName, mtime, zHash); |
| 2510 | 2520 | if( (syncFlags & SYNC_UV_REVERT)!=0 ){ |
| 2511 | 2521 | if( iStatus==4 ) iStatus = 2; |
| 2512 | 2522 | if( iStatus==5 ) iStatus = 1; |
| 2513 | 2523 | } |
| @@ -2590,10 +2600,14 @@ | ||
| 2590 | 2600 | */ |
| 2591 | 2601 | if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 |
| 2592 | 2602 | && blob_is_int(&xfer.aToken[2], &size) ){ |
| 2593 | 2603 | const char *zName = blob_str(&xfer.aToken[1]); |
| 2594 | 2604 | Blob content; |
| 2605 | + if( size<0 ){ | |
| 2606 | + xfer_fatal_error("invalid config record"); | |
| 2607 | + return ++nErr; | |
| 2608 | + } | |
| 2595 | 2609 | blob_zero(&content); |
| 2596 | 2610 | blob_extract(xfer.pIn, size, &content); |
| 2597 | 2611 | g.perm.Admin = g.perm.RdAddr = 1; |
| 2598 | 2612 | configure_receive(zName, &content, origConfigRcvMask); |
| 2599 | 2613 | nCardRcvd++; |
| @@ -2634,10 +2648,14 @@ | ||
| 2634 | 2648 | ** blob that needs to be sent. If N<=0 that indicates that all blobs |
| 2635 | 2649 | ** have been sent. |
| 2636 | 2650 | */ |
| 2637 | 2651 | if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){ |
| 2638 | 2652 | blob_is_int(&xfer.aToken[1], &cloneSeqno); |
| 2653 | + if( cloneSeqno<0 ){ | |
| 2654 | + xfer_fatal_error("invalid clone_seqno"); | |
| 2655 | + return ++nErr; | |
| 2656 | + } | |
| 2639 | 2657 | }else |
| 2640 | 2658 | |
| 2641 | 2659 | /* message MESSAGE |
| 2642 | 2660 | ** |
| 2643 | 2661 | ** A message is received from the server. Print it. |
| @@ -2711,10 +2729,14 @@ | ||
| 2711 | 2729 | char *zUser = blob_terminate(&xfer.aToken[2]); |
| 2712 | 2730 | sqlite3_int64 mtime, iNow; |
| 2713 | 2731 | defossilize(zUser); |
| 2714 | 2732 | iNow = time(NULL); |
| 2715 | 2733 | if( blob_is_int64(&xfer.aToken[3], &mtime) && iNow>mtime ){ |
| 2734 | + if( mtime<0 ){ | |
| 2735 | + xfer_fatal_error("invalid ci-lock-fail time"); | |
| 2736 | + return ++nErr; | |
| 2737 | + } | |
| 2716 | 2738 | iNow = time(NULL); |
| 2717 | 2739 | fossil_print("\nParent check-in locked by %s %s ago\n", |
| 2718 | 2740 | zUser, human_readable_age((iNow+1-mtime)/86400.0)); |
| 2719 | 2741 | }else{ |
| 2720 | 2742 | fossil_print("\nParent check-in locked by %s\n", zUser); |
| 2721 | 2743 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -336,10 +336,11 @@ | |
| 336 | || !blob_is_filename(&pXfer->aToken[1]) |
| 337 | || !blob_is_int64(&pXfer->aToken[2], &mtime) |
| 338 | || (!blob_eq(pHash,"-") && !blob_is_hname(pHash)) |
| 339 | || !blob_is_int(&pXfer->aToken[4], &sz) |
| 340 | || !blob_is_int(&pXfer->aToken[5], &flags) |
| 341 | ){ |
| 342 | blob_appendf(&pXfer->err, "malformed uvfile line"); |
| 343 | return; |
| 344 | } |
| 345 | blob_init(&content, 0, 0); |
| @@ -1122,19 +1123,20 @@ | |
| 1122 | return db_get("xfer-ticket-script", 0); |
| 1123 | } |
| 1124 | |
| 1125 | /* |
| 1126 | ** Reset the CGI content, roll back any pending db transaction, and |
| 1127 | ** emit an "error" xfer message, which must be pre-fossilized by the |
| 1128 | ** caller. |
| 1129 | */ |
| 1130 | static void xfer_error(const char *zFossilizedMsg){ |
| 1131 | cgi_reset_content(); |
| 1132 | if( db_transaction_nesting_depth() > 0 ){ |
| 1133 | db_rollback_transaction(); |
| 1134 | } |
| 1135 | @ error %s(zFossilizedMsg) |
| 1136 | } |
| 1137 | |
| 1138 | /* |
| 1139 | ** Run the specified TH1 script, if any, and returns 1 on error. |
| 1140 | */ |
| @@ -1475,11 +1477,11 @@ | |
| 1475 | if( iVers>=3 ){ |
| 1476 | cgi_set_content_type("application/x-fossil-uncompressed"); |
| 1477 | } |
| 1478 | blob_is_int(&xfer.aToken[2], &seqno); |
| 1479 | if( seqno<=0 ){ |
| 1480 | xfer_error("invalid\\sclone\\ssequence\\snumber"); |
| 1481 | return; |
| 1482 | } |
| 1483 | max = db_int(0, "SELECT max(rid) FROM blob"); |
| 1484 | while( xfer.mxSend>(int)blob_size(xfer.pOut) && seqno<=max){ |
| 1485 | if( time(NULL) >= xfer.maxTime ) break; |
| @@ -1549,10 +1551,14 @@ | |
| 1549 | */ |
| 1550 | if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 |
| 1551 | && blob_is_int(&xfer.aToken[2], &size) ){ |
| 1552 | const char *zName = blob_str(&xfer.aToken[1]); |
| 1553 | Blob content; |
| 1554 | blob_zero(&content); |
| 1555 | blob_extract(xfer.pIn, size, &content); |
| 1556 | if( !g.perm.Admin ){ |
| 1557 | cgi_reset_content(); |
| 1558 | @ error not\sauthorized\sto\spush\sconfiguration |
| @@ -2504,10 +2510,14 @@ | |
| 2504 | && (blob_eq(&xfer.aToken[3],"-") || blob_is_hname(&xfer.aToken[3])) |
| 2505 | ){ |
| 2506 | const char *zName = blob_str(&xfer.aToken[1]); |
| 2507 | const char *zHash = blob_str(&xfer.aToken[3]); |
| 2508 | int iStatus; |
| 2509 | iStatus = unversioned_status(zName, mtime, zHash); |
| 2510 | if( (syncFlags & SYNC_UV_REVERT)!=0 ){ |
| 2511 | if( iStatus==4 ) iStatus = 2; |
| 2512 | if( iStatus==5 ) iStatus = 1; |
| 2513 | } |
| @@ -2590,10 +2600,14 @@ | |
| 2590 | */ |
| 2591 | if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 |
| 2592 | && blob_is_int(&xfer.aToken[2], &size) ){ |
| 2593 | const char *zName = blob_str(&xfer.aToken[1]); |
| 2594 | Blob content; |
| 2595 | blob_zero(&content); |
| 2596 | blob_extract(xfer.pIn, size, &content); |
| 2597 | g.perm.Admin = g.perm.RdAddr = 1; |
| 2598 | configure_receive(zName, &content, origConfigRcvMask); |
| 2599 | nCardRcvd++; |
| @@ -2634,10 +2648,14 @@ | |
| 2634 | ** blob that needs to be sent. If N<=0 that indicates that all blobs |
| 2635 | ** have been sent. |
| 2636 | */ |
| 2637 | if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){ |
| 2638 | blob_is_int(&xfer.aToken[1], &cloneSeqno); |
| 2639 | }else |
| 2640 | |
| 2641 | /* message MESSAGE |
| 2642 | ** |
| 2643 | ** A message is received from the server. Print it. |
| @@ -2711,10 +2729,14 @@ | |
| 2711 | char *zUser = blob_terminate(&xfer.aToken[2]); |
| 2712 | sqlite3_int64 mtime, iNow; |
| 2713 | defossilize(zUser); |
| 2714 | iNow = time(NULL); |
| 2715 | if( blob_is_int64(&xfer.aToken[3], &mtime) && iNow>mtime ){ |
| 2716 | iNow = time(NULL); |
| 2717 | fossil_print("\nParent check-in locked by %s %s ago\n", |
| 2718 | zUser, human_readable_age((iNow+1-mtime)/86400.0)); |
| 2719 | }else{ |
| 2720 | fossil_print("\nParent check-in locked by %s\n", zUser); |
| 2721 |
| --- src/xfer.c | |
| +++ src/xfer.c | |
| @@ -336,10 +336,11 @@ | |
| 336 | || !blob_is_filename(&pXfer->aToken[1]) |
| 337 | || !blob_is_int64(&pXfer->aToken[2], &mtime) |
| 338 | || (!blob_eq(pHash,"-") && !blob_is_hname(pHash)) |
| 339 | || !blob_is_int(&pXfer->aToken[4], &sz) |
| 340 | || !blob_is_int(&pXfer->aToken[5], &flags) |
| 341 | || (mtime<0 || sz<0 || flags<0) |
| 342 | ){ |
| 343 | blob_appendf(&pXfer->err, "malformed uvfile line"); |
| 344 | return; |
| 345 | } |
| 346 | blob_init(&content, 0, 0); |
| @@ -1122,19 +1123,20 @@ | |
| 1123 | return db_get("xfer-ticket-script", 0); |
| 1124 | } |
| 1125 | |
| 1126 | /* |
| 1127 | ** Reset the CGI content, roll back any pending db transaction, and |
| 1128 | ** emit an "error" xfer message. The message text gets fossil-encoded |
| 1129 | ** by this function. This is only intended for use with |
| 1130 | ** fail-fast/fatal errors, not ones which can be skipped over. |
| 1131 | */ |
| 1132 | static void xfer_fatal_error(const char *zMsg){ |
| 1133 | cgi_reset_content(); |
| 1134 | if( db_transaction_nesting_depth()>0 ){ |
| 1135 | db_rollback_transaction(); |
| 1136 | } |
| 1137 | @ error %F(zMsg) |
| 1138 | } |
| 1139 | |
| 1140 | /* |
| 1141 | ** Run the specified TH1 script, if any, and returns 1 on error. |
| 1142 | */ |
| @@ -1475,11 +1477,11 @@ | |
| 1477 | if( iVers>=3 ){ |
| 1478 | cgi_set_content_type("application/x-fossil-uncompressed"); |
| 1479 | } |
| 1480 | blob_is_int(&xfer.aToken[2], &seqno); |
| 1481 | if( seqno<=0 ){ |
| 1482 | xfer_fatal_error("invalid clone sequence number"); |
| 1483 | return; |
| 1484 | } |
| 1485 | max = db_int(0, "SELECT max(rid) FROM blob"); |
| 1486 | while( xfer.mxSend>(int)blob_size(xfer.pOut) && seqno<=max){ |
| 1487 | if( time(NULL) >= xfer.maxTime ) break; |
| @@ -1549,10 +1551,14 @@ | |
| 1551 | */ |
| 1552 | if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 |
| 1553 | && blob_is_int(&xfer.aToken[2], &size) ){ |
| 1554 | const char *zName = blob_str(&xfer.aToken[1]); |
| 1555 | Blob content; |
| 1556 | if( size<0 ){ |
| 1557 | xfer_fatal_error("invalid config record"); |
| 1558 | return; |
| 1559 | } |
| 1560 | blob_zero(&content); |
| 1561 | blob_extract(xfer.pIn, size, &content); |
| 1562 | if( !g.perm.Admin ){ |
| 1563 | cgi_reset_content(); |
| 1564 | @ error not\sauthorized\sto\spush\sconfiguration |
| @@ -2504,10 +2510,14 @@ | |
| 2510 | && (blob_eq(&xfer.aToken[3],"-") || blob_is_hname(&xfer.aToken[3])) |
| 2511 | ){ |
| 2512 | const char *zName = blob_str(&xfer.aToken[1]); |
| 2513 | const char *zHash = blob_str(&xfer.aToken[3]); |
| 2514 | int iStatus; |
| 2515 | if( mtime<0 || size<0 ){ |
| 2516 | xfer_fatal_error("invalid uvigot"); |
| 2517 | return ++nErr; |
| 2518 | } |
| 2519 | iStatus = unversioned_status(zName, mtime, zHash); |
| 2520 | if( (syncFlags & SYNC_UV_REVERT)!=0 ){ |
| 2521 | if( iStatus==4 ) iStatus = 2; |
| 2522 | if( iStatus==5 ) iStatus = 1; |
| 2523 | } |
| @@ -2590,10 +2600,14 @@ | |
| 2600 | */ |
| 2601 | if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3 |
| 2602 | && blob_is_int(&xfer.aToken[2], &size) ){ |
| 2603 | const char *zName = blob_str(&xfer.aToken[1]); |
| 2604 | Blob content; |
| 2605 | if( size<0 ){ |
| 2606 | xfer_fatal_error("invalid config record"); |
| 2607 | return ++nErr; |
| 2608 | } |
| 2609 | blob_zero(&content); |
| 2610 | blob_extract(xfer.pIn, size, &content); |
| 2611 | g.perm.Admin = g.perm.RdAddr = 1; |
| 2612 | configure_receive(zName, &content, origConfigRcvMask); |
| 2613 | nCardRcvd++; |
| @@ -2634,10 +2648,14 @@ | |
| 2648 | ** blob that needs to be sent. If N<=0 that indicates that all blobs |
| 2649 | ** have been sent. |
| 2650 | */ |
| 2651 | if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){ |
| 2652 | blob_is_int(&xfer.aToken[1], &cloneSeqno); |
| 2653 | if( cloneSeqno<0 ){ |
| 2654 | xfer_fatal_error("invalid clone_seqno"); |
| 2655 | return ++nErr; |
| 2656 | } |
| 2657 | }else |
| 2658 | |
| 2659 | /* message MESSAGE |
| 2660 | ** |
| 2661 | ** A message is received from the server. Print it. |
| @@ -2711,10 +2729,14 @@ | |
| 2729 | char *zUser = blob_terminate(&xfer.aToken[2]); |
| 2730 | sqlite3_int64 mtime, iNow; |
| 2731 | defossilize(zUser); |
| 2732 | iNow = time(NULL); |
| 2733 | if( blob_is_int64(&xfer.aToken[3], &mtime) && iNow>mtime ){ |
| 2734 | if( mtime<0 ){ |
| 2735 | xfer_fatal_error("invalid ci-lock-fail time"); |
| 2736 | return ++nErr; |
| 2737 | } |
| 2738 | iNow = time(NULL); |
| 2739 | fossil_print("\nParent check-in locked by %s %s ago\n", |
| 2740 | zUser, human_readable_age((iNow+1-mtime)/86400.0)); |
| 2741 | }else{ |
| 2742 | fossil_print("\nParent check-in locked by %s\n", zUser); |
| 2743 |