Fossil SCM
Add extension validity checks. RFC 9112: A recipient MUST ignore unrecognized chunk extensions, etc. From libfossil.
Commit
2343135ec968847fefb3f509bce7bd9a253ecb034f09f4b7026429889ea779ef
Parent
e288a41b3b1fdb2…
1 file changed
+30
-7
+30
-7
| --- src/http.c | ||
| +++ src/http.c | ||
| @@ -446,10 +446,26 @@ | ||
| 446 | 446 | n = blob_read_from_file(&x, zFilename, ExtFILE); |
| 447 | 447 | fossil_free(zFilename); |
| 448 | 448 | if( n<=0 ) return 0; |
| 449 | 449 | return blob_str(&x); |
| 450 | 450 | } |
| 451 | + | |
| 452 | +/* | |
| 453 | +** Return true if z is a valud extension according to RFC 9112 s 7.1.1. | |
| 454 | +** In addition to the ordinary checks, bare CR anywhere is invalid | |
| 455 | +** (section 2.2). | |
| 456 | +*/ | |
| 457 | +static int http_chunk_ext_ok(const char *z){ | |
| 458 | + if( strchr(z, '\r')!=0 ) return 0; | |
| 459 | + while( *z==' ' || *z=='\t' ) z++; | |
| 460 | + if( *z==0 ) return 1; | |
| 461 | + if( *z!=';' ) return 0; | |
| 462 | + z++; | |
| 463 | + while( *z==' ' || *z=='\t' ) z++; | |
| 464 | + return fossil_isalnum(*z) | |
| 465 | + || (*z!=0 && strchr("!#$%&'*+-.^_`|~", *z)!=0); | |
| 466 | +} | |
| 451 | 467 | |
| 452 | 468 | /* |
| 453 | 469 | ** Sign the content in pSend, compress it, and send it to the server |
| 454 | 470 | ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply |
| 455 | 471 | ** in pRecv. pRecv is assumed to be uninitialized when |
| @@ -815,26 +831,33 @@ | ||
| 815 | 831 | ** bare CRLF. A zero-length chunk terminates the body, after which any |
| 816 | 832 | ** trailer header lines are read and discarded up to the blank line. */ |
| 817 | 833 | char *zChunk; |
| 818 | 834 | int sawTerminator = 0; /* True once the 0-length chunk is seen */ |
| 819 | 835 | while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){ |
| 820 | - i64 nChunk; /* Size of this chunk in bytes (wide, unclamped) */ | |
| 836 | + i64 nChunk = 0; /* Size of this chunk in bytes (wide, unclamped) */ | |
| 821 | 837 | i64 nPrior; /* Bytes already in pReply (matches blob nUsed) */ |
| 822 | - char *zEnd = 0; /* End of the hex digits actually parsed */ | |
| 823 | - while( fossil_isspace(zChunk[0]) ) zChunk++; | |
| 824 | - nChunk = strtoll(zChunk, &zEnd, 16); | |
| 825 | - if( zEnd==zChunk ){ | |
| 838 | + int j; /* Number of hex digits in the chunk size */ | |
| 839 | + /* The size is one or more hex digits (RFC 9112 section 7.1), | |
| 840 | + ** with no sign, "0x" or leading space. */ | |
| 841 | + for(j=0; fossil_isXdigit(zChunk[j]) && nChunk<=0x7fffffff; j++){ | |
| 842 | + nChunk = nChunk*16 + hex_digit_value(zChunk[j]); | |
| 843 | + } | |
| 844 | + if( j==0 ){ | |
| 826 | 845 | /* No hex digit consumed: a blank or malformed chunk-size line, which |
| 827 | 846 | ** is the symptom of a connection that closed mid-stream. Treat it as |
| 828 | 847 | ** a truncated (failed) */ |
| 829 | 848 | fossil_warning("chunked reply: missing or malformed chunk size"); |
| 830 | 849 | goto write_err; |
| 831 | 850 | } |
| 832 | - if( nChunk<0 || nChunk>0x7fffffff ){ | |
| 833 | - /* Negative, or larger than we will ever accept in one chunk. */ | |
| 851 | + if( nChunk>0x7fffffff ){ | |
| 852 | + /* Larger than we will ever accept in one chunk. */ | |
| 834 | 853 | fossil_warning("chunked reply: invalid chunk size"); |
| 835 | 854 | goto write_err; |
| 855 | + } | |
| 856 | + if( !http_chunk_ext_ok(&zChunk[j]) ){ | |
| 857 | + fossil_warning("chunked reply: malformed chunk size line"); | |
| 858 | + goto write_err; | |
| 836 | 859 | } |
| 837 | 860 | if( nChunk==0 ){ |
| 838 | 861 | /* Final chunk: consume trailer lines up to the terminating blank. */ |
| 839 | 862 | sawTerminator = 1; |
| 840 | 863 | while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){} |
| 841 | 864 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -446,10 +446,26 @@ | |
| 446 | n = blob_read_from_file(&x, zFilename, ExtFILE); |
| 447 | fossil_free(zFilename); |
| 448 | if( n<=0 ) return 0; |
| 449 | return blob_str(&x); |
| 450 | } |
| 451 | |
| 452 | /* |
| 453 | ** Sign the content in pSend, compress it, and send it to the server |
| 454 | ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply |
| 455 | ** in pRecv. pRecv is assumed to be uninitialized when |
| @@ -815,26 +831,33 @@ | |
| 815 | ** bare CRLF. A zero-length chunk terminates the body, after which any |
| 816 | ** trailer header lines are read and discarded up to the blank line. */ |
| 817 | char *zChunk; |
| 818 | int sawTerminator = 0; /* True once the 0-length chunk is seen */ |
| 819 | while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){ |
| 820 | i64 nChunk; /* Size of this chunk in bytes (wide, unclamped) */ |
| 821 | i64 nPrior; /* Bytes already in pReply (matches blob nUsed) */ |
| 822 | char *zEnd = 0; /* End of the hex digits actually parsed */ |
| 823 | while( fossil_isspace(zChunk[0]) ) zChunk++; |
| 824 | nChunk = strtoll(zChunk, &zEnd, 16); |
| 825 | if( zEnd==zChunk ){ |
| 826 | /* No hex digit consumed: a blank or malformed chunk-size line, which |
| 827 | ** is the symptom of a connection that closed mid-stream. Treat it as |
| 828 | ** a truncated (failed) */ |
| 829 | fossil_warning("chunked reply: missing or malformed chunk size"); |
| 830 | goto write_err; |
| 831 | } |
| 832 | if( nChunk<0 || nChunk>0x7fffffff ){ |
| 833 | /* Negative, or larger than we will ever accept in one chunk. */ |
| 834 | fossil_warning("chunked reply: invalid chunk size"); |
| 835 | goto write_err; |
| 836 | } |
| 837 | if( nChunk==0 ){ |
| 838 | /* Final chunk: consume trailer lines up to the terminating blank. */ |
| 839 | sawTerminator = 1; |
| 840 | while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){} |
| 841 |
| --- src/http.c | |
| +++ src/http.c | |
| @@ -446,10 +446,26 @@ | |
| 446 | n = blob_read_from_file(&x, zFilename, ExtFILE); |
| 447 | fossil_free(zFilename); |
| 448 | if( n<=0 ) return 0; |
| 449 | return blob_str(&x); |
| 450 | } |
| 451 | |
| 452 | /* |
| 453 | ** Return true if z is a valud extension according to RFC 9112 s 7.1.1. |
| 454 | ** In addition to the ordinary checks, bare CR anywhere is invalid |
| 455 | ** (section 2.2). |
| 456 | */ |
| 457 | static int http_chunk_ext_ok(const char *z){ |
| 458 | if( strchr(z, '\r')!=0 ) return 0; |
| 459 | while( *z==' ' || *z=='\t' ) z++; |
| 460 | if( *z==0 ) return 1; |
| 461 | if( *z!=';' ) return 0; |
| 462 | z++; |
| 463 | while( *z==' ' || *z=='\t' ) z++; |
| 464 | return fossil_isalnum(*z) |
| 465 | || (*z!=0 && strchr("!#$%&'*+-.^_`|~", *z)!=0); |
| 466 | } |
| 467 | |
| 468 | /* |
| 469 | ** Sign the content in pSend, compress it, and send it to the server |
| 470 | ** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply |
| 471 | ** in pRecv. pRecv is assumed to be uninitialized when |
| @@ -815,26 +831,33 @@ | |
| 831 | ** bare CRLF. A zero-length chunk terminates the body, after which any |
| 832 | ** trailer header lines are read and discarded up to the blank line. */ |
| 833 | char *zChunk; |
| 834 | int sawTerminator = 0; /* True once the 0-length chunk is seen */ |
| 835 | while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){ |
| 836 | i64 nChunk = 0; /* Size of this chunk in bytes (wide, unclamped) */ |
| 837 | i64 nPrior; /* Bytes already in pReply (matches blob nUsed) */ |
| 838 | int j; /* Number of hex digits in the chunk size */ |
| 839 | /* The size is one or more hex digits (RFC 9112 section 7.1), |
| 840 | ** with no sign, "0x" or leading space. */ |
| 841 | for(j=0; fossil_isXdigit(zChunk[j]) && nChunk<=0x7fffffff; j++){ |
| 842 | nChunk = nChunk*16 + hex_digit_value(zChunk[j]); |
| 843 | } |
| 844 | if( j==0 ){ |
| 845 | /* No hex digit consumed: a blank or malformed chunk-size line, which |
| 846 | ** is the symptom of a connection that closed mid-stream. Treat it as |
| 847 | ** a truncated (failed) */ |
| 848 | fossil_warning("chunked reply: missing or malformed chunk size"); |
| 849 | goto write_err; |
| 850 | } |
| 851 | if( nChunk>0x7fffffff ){ |
| 852 | /* Larger than we will ever accept in one chunk. */ |
| 853 | fossil_warning("chunked reply: invalid chunk size"); |
| 854 | goto write_err; |
| 855 | } |
| 856 | if( !http_chunk_ext_ok(&zChunk[j]) ){ |
| 857 | fossil_warning("chunked reply: malformed chunk size line"); |
| 858 | goto write_err; |
| 859 | } |
| 860 | if( nChunk==0 ){ |
| 861 | /* Final chunk: consume trailer lines up to the terminating blank. */ |
| 862 | sawTerminator = 1; |
| 863 | while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){} |
| 864 |