Fossil SCM

Add extension validity checks. RFC 9112: A recipient MUST ignore unrecognized chunk extensions, etc. From libfossil.

danshearer 2026-10-12 07:28 UTC trunk
Commit 2343135ec968847fefb3f509bce7bd9a253ecb034f09f4b7026429889ea779ef
1 file changed +30 -7
+30 -7
--- src/http.c
+++ src/http.c
@@ -446,10 +446,26 @@
446446
n = blob_read_from_file(&x, zFilename, ExtFILE);
447447
fossil_free(zFilename);
448448
if( n<=0 ) return 0;
449449
return blob_str(&x);
450450
}
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
+}
451467
452468
/*
453469
** Sign the content in pSend, compress it, and send it to the server
454470
** via HTTP or HTTPS. Get a reply, uncompress the reply, and store the reply
455471
** in pRecv. pRecv is assumed to be uninitialized when
@@ -815,26 +831,33 @@
815831
** bare CRLF. A zero-length chunk terminates the body, after which any
816832
** trailer header lines are read and discarded up to the blank line. */
817833
char *zChunk;
818834
int sawTerminator = 0; /* True once the 0-length chunk is seen */
819835
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) */
821837
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 ){
826845
/* No hex digit consumed: a blank or malformed chunk-size line, which
827846
** is the symptom of a connection that closed mid-stream. Treat it as
828847
** a truncated (failed) */
829848
fossil_warning("chunked reply: missing or malformed chunk size");
830849
goto write_err;
831850
}
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. */
834853
fossil_warning("chunked reply: invalid chunk size");
835854
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;
836859
}
837860
if( nChunk==0 ){
838861
/* Final chunk: consume trailer lines up to the terminating blank. */
839862
sawTerminator = 1;
840863
while( (zChunk = transport_receive_line(&g.url))!=0 && zChunk[0]!=0 ){}
841864
--- 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

Keyboard Shortcuts

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