From 6be70187c2a6004295bf3145e728fc72e234aabb Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 8 Nov 2022 01:50:39 -0500 Subject: [PATCH 1/5] import httpd-2.4.37-51.module+el8.7.0+16050+02173b8e --- SOURCES/httpd-2.4.37-CVE-2022-22719.patch | 70 +++ SOURCES/httpd-2.4.37-CVE-2022-22721.patch | 103 ++++ SOURCES/httpd-2.4.37-CVE-2022-23943.patch | 377 +++++++++++++++ SOURCES/httpd-2.4.37-CVE-2022-26377.patch | 26 ++ SOURCES/httpd-2.4.37-CVE-2022-28614.patch | 47 ++ SOURCES/httpd-2.4.37-CVE-2022-28615.patch | 22 + SOURCES/httpd-2.4.37-CVE-2022-29404.patch | 126 +++++ SOURCES/httpd-2.4.37-CVE-2022-30522.patch | 541 ++++++++++++++++++++++ SOURCES/httpd-2.4.37-CVE-2022-30556.patch | 233 ++++++++++ SOURCES/httpd-2.4.37-CVE-2022-31813.patch | 229 +++++++++ SPECS/httpd.spec | 65 ++- 11 files changed, 1834 insertions(+), 5 deletions(-) create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-22719.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-22721.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-23943.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-26377.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-28614.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-28615.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-29404.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-30522.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-30556.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-31813.patch diff --git a/SOURCES/httpd-2.4.37-CVE-2022-22719.patch b/SOURCES/httpd-2.4.37-CVE-2022-22719.patch new file mode 100644 index 0000000..006cae3 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-22719.patch @@ -0,0 +1,70 @@ +--- a/modules/lua/lua_request.c 2022/03/07 14:48:54 1898693 ++++ b/modules/lua/lua_request.c 2022/03/07 14:51:19 1898694 +@@ -235,14 +235,16 @@ + { + int rc = OK; + ++ *rbuf = NULL; ++ *size = 0; ++ + if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) { + return (rc); + } + if (ap_should_client_block(r)) { + + /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +- char argsbuffer[HUGE_STRING_LEN]; +- apr_off_t rsize, len_read, rpos = 0; ++ apr_off_t len_read, rpos = 0; + apr_off_t length = r->remaining; + /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + +@@ -250,18 +252,18 @@ + return APR_EINCOMPLETE; /* Only room for incomplete data chunk :( */ + } + *rbuf = (const char *) apr_pcalloc(r->pool, (apr_size_t) (length + 1)); +- *size = length; +- while ((len_read = ap_get_client_block(r, argsbuffer, sizeof(argsbuffer))) > 0) { +- if ((rpos + len_read) > length) { +- rsize = length - rpos; +- } +- else { +- rsize = len_read; +- } +- +- memcpy((char *) *rbuf + rpos, argsbuffer, (size_t) rsize); +- rpos += rsize; ++ while ((rpos < length) ++ && (len_read = ap_get_client_block(r, (char *) *rbuf + rpos, ++ length - rpos)) > 0) { ++ rpos += len_read; + } ++ if (len_read < 0) { ++ return APR_EINCOMPLETE; ++ } ++ *size = rpos; ++ } ++ else { ++ rc = DONE; + } + + return (rc); +@@ -278,6 +280,8 @@ + { + apr_status_t rc = OK; + ++ *size = 0; ++ + if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) + return rc; + if (ap_should_client_block(r)) { +@@ -303,6 +307,9 @@ + rpos += rsize; + } + } ++ else { ++ rc = DONE; ++ } + + return rc; + } diff --git a/SOURCES/httpd-2.4.37-CVE-2022-22721.patch b/SOURCES/httpd-2.4.37-CVE-2022-22721.patch new file mode 100644 index 0000000..3985adb --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-22721.patch @@ -0,0 +1,103 @@ +diff --git a/docs/manual/mod/core.html.en b/docs/manual/mod/core.html.en +index 20d1e5a..e1ec8d0 100644 +--- a/docs/manual/mod/core.html.en ++++ b/docs/manual/mod/core.html.en +@@ -2935,12 +2935,19 @@ from the client + Status:Core + Module:core + +-

Limit (in bytes) on maximum size of an XML-based request +- body. A value of 0 will disable any checking.

++

Limit (in bytes) on the maximum size of an XML-based request ++ body. A value of 0 will apply a hard limit (depending on ++ 32bit vs 64bit system) allowing for XML escaping within the bounds of ++ the system addressable memory, but it exists for compatibility only ++ and is not recommended since it does not account for memory consumed ++ elsewhere or concurrent requests, which might result in an overall ++ system out-of-memory. ++

+ +

Example:

+ +-
LimitXMLRequestBody 0
++
# Limit of 1 MiB
++    LimitXMLRequestBody 1073741824
+ + + +diff --git a/server/core.c b/server/core.c +index e32613d..8abfa65 100644 +--- a/server/core.c ++++ b/server/core.c +@@ -70,6 +70,8 @@ + /* LimitXMLRequestBody handling */ + #define AP_LIMIT_UNSET ((long) -1) + #define AP_DEFAULT_LIMIT_XML_BODY ((apr_size_t)1000000) ++/* Hard limit for ap_escape_html2() */ ++#define AP_MAX_LIMIT_XML_BODY ((apr_size_t)(APR_SIZE_MAX / 6 - 1)) + + #define AP_MIN_SENDFILE_BYTES (256) + +@@ -3689,6 +3691,11 @@ static const char *set_limit_xml_req_body(cmd_parms *cmd, void *conf_, + if (conf->limit_xml_body < 0) + return "LimitXMLRequestBody requires a non-negative integer."; + ++ /* zero is AP_MAX_LIMIT_XML_BODY (implicitly) */ ++ if ((apr_size_t)conf->limit_xml_body > AP_MAX_LIMIT_XML_BODY) ++ return apr_psprintf(cmd->pool, "LimitXMLRequestBody must not exceed " ++ "%" APR_SIZE_T_FMT, AP_MAX_LIMIT_XML_BODY); ++ + return NULL; + } + +@@ -3777,6 +3784,8 @@ AP_DECLARE(apr_size_t) ap_get_limit_xml_body(const request_rec *r) + conf = ap_get_core_module_config(r->per_dir_config); + if (conf->limit_xml_body == AP_LIMIT_UNSET) + return AP_DEFAULT_LIMIT_XML_BODY; ++ if (conf->limit_xml_body == 0) ++ return AP_MAX_LIMIT_XML_BODY; + + return (apr_size_t)conf->limit_xml_body; + } +diff --git a/server/util.c b/server/util.c +index 2a5dd04..eefdafa 100644 +--- a/server/util.c ++++ b/server/util.c +@@ -2037,11 +2037,14 @@ AP_DECLARE(char *) ap_escape_urlencoded(apr_pool_t *p, const char *buffer) + + AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) + { +- int i, j; ++ apr_size_t i, j; + char *x; + + /* first, count the number of extra characters */ +- for (i = 0, j = 0; s[i] != '\0'; i++) ++ for (i = 0, j = 0; s[i] != '\0'; i++) { ++ if (i + j > APR_SIZE_MAX - 6) { ++ abort(); ++ } + if (s[i] == '<' || s[i] == '>') + j += 3; + else if (s[i] == '&') +@@ -2050,6 +2053,7 @@ AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) + j += 5; + else if (toasc && !apr_isascii(s[i])) + j += 5; ++ } + + if (j == 0) + return apr_pstrmemdup(p, s, i); +diff --git a/server/util_xml.c b/server/util_xml.c +index 4845194..22806fa 100644 +--- a/server/util_xml.c ++++ b/server/util_xml.c +@@ -85,7 +85,7 @@ AP_DECLARE(int) ap_xml_parse_input(request_rec * r, apr_xml_doc **pdoc) + } + + total_read += len; +- if (limit_xml_body && total_read > limit_xml_body) { ++ if (total_read > limit_xml_body) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00539) + "XML request body is larger than the configured " + "limit of %lu", (unsigned long)limit_xml_body); diff --git a/SOURCES/httpd-2.4.37-CVE-2022-23943.patch b/SOURCES/httpd-2.4.37-CVE-2022-23943.patch new file mode 100644 index 0000000..0383d2f --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-23943.patch @@ -0,0 +1,377 @@ +diff --git a/modules/filters/libsed.h b/modules/filters/libsed.h +index 76cbc0c..0256b1e 100644 +--- a/modules/filters/libsed.h ++++ b/modules/filters/libsed.h +@@ -60,7 +60,7 @@ struct sed_label_s { + }; + + typedef apr_status_t (sed_err_fn_t)(void *data, const char *error); +-typedef apr_status_t (sed_write_fn_t)(void *ctx, char *buf, int sz); ++typedef apr_status_t (sed_write_fn_t)(void *ctx, char *buf, apr_size_t sz); + + typedef struct sed_commands_s sed_commands_t; + #define NWFILES 11 /* 10 plus one for standard output */ +@@ -69,7 +69,7 @@ struct sed_commands_s { + sed_err_fn_t *errfn; + void *data; + +- unsigned lsize; ++ apr_size_t lsize; + char *linebuf; + char *lbend; + const char *saveq; +@@ -116,15 +116,15 @@ struct sed_eval_s { + apr_int64_t lnum; + void *fout; + +- unsigned lsize; ++ apr_size_t lsize; + char *linebuf; + char *lspend; + +- unsigned hsize; ++ apr_size_t hsize; + char *holdbuf; + char *hspend; + +- unsigned gsize; ++ apr_size_t gsize; + char *genbuf; + char *lcomend; + +@@ -160,7 +160,7 @@ apr_status_t sed_init_eval(sed_eval_t *eval, sed_commands_t *commands, + sed_err_fn_t *errfn, void *data, + sed_write_fn_t *writefn, apr_pool_t *p); + apr_status_t sed_reset_eval(sed_eval_t *eval, sed_commands_t *commands, sed_err_fn_t *errfn, void *data); +-apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void *fout); ++apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz, void *fout); + apr_status_t sed_eval_file(sed_eval_t *eval, apr_file_t *fin, void *fout); + apr_status_t sed_finalize_eval(sed_eval_t *eval, void *f); + void sed_destroy_eval(sed_eval_t *eval); +diff --git a/modules/filters/mod_sed.c b/modules/filters/mod_sed.c +index 346c210..8595e41 100644 +--- a/modules/filters/mod_sed.c ++++ b/modules/filters/mod_sed.c +@@ -51,7 +51,7 @@ typedef struct sed_filter_ctxt + apr_bucket_brigade *bbinp; + char *outbuf; + char *curoutbuf; +- int bufsize; ++ apr_size_t bufsize; + apr_pool_t *tpool; + int numbuckets; + } sed_filter_ctxt; +@@ -100,7 +100,7 @@ static void alloc_outbuf(sed_filter_ctxt* ctx) + /* append_bucket + * Allocate a new bucket from buf and sz and append to ctx->bb + */ +-static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz) ++static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, apr_size_t sz) + { + apr_status_t status = APR_SUCCESS; + apr_bucket *b; +@@ -133,7 +133,7 @@ static apr_status_t append_bucket(sed_filter_ctxt* ctx, char* buf, int sz) + */ + static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx) + { +- int size = ctx->curoutbuf - ctx->outbuf; ++ apr_size_t size = ctx->curoutbuf - ctx->outbuf; + char *out; + apr_status_t status = APR_SUCCESS; + if ((ctx->outbuf == NULL) || (size <=0)) +@@ -147,12 +147,12 @@ static apr_status_t flush_output_buffer(sed_filter_ctxt *ctx) + /* This is a call back function. When libsed wants to generate the output, + * this function will be invoked. + */ +-static apr_status_t sed_write_output(void *dummy, char *buf, int sz) ++static apr_status_t sed_write_output(void *dummy, char *buf, apr_size_t sz) + { + /* dummy is basically filter context. Context is passed during invocation + * of sed_eval_buffer + */ +- int remainbytes = 0; ++ apr_size_t remainbytes = 0; + apr_status_t status = APR_SUCCESS; + sed_filter_ctxt *ctx = (sed_filter_ctxt *) dummy; + if (ctx->outbuf == NULL) { +@@ -168,21 +168,29 @@ static apr_status_t sed_write_output(void *dummy, char *buf, int sz) + } + /* buffer is now full */ + status = append_bucket(ctx, ctx->outbuf, ctx->bufsize); +- /* old buffer is now used so allocate new buffer */ +- alloc_outbuf(ctx); +- /* if size is bigger than the allocated buffer directly add to output +- * brigade */ +- if ((status == APR_SUCCESS) && (sz >= ctx->bufsize)) { +- char* newbuf = apr_pmemdup(ctx->tpool, buf, sz); +- status = append_bucket(ctx, newbuf, sz); +- /* pool might get clear after append_bucket */ +- if (ctx->outbuf == NULL) { ++ if (status == APR_SUCCESS) { ++ /* if size is bigger than the allocated buffer directly add to output ++ * brigade */ ++ if (sz >= ctx->bufsize) { ++ char* newbuf = apr_pmemdup(ctx->tpool, buf, sz); ++ status = append_bucket(ctx, newbuf, sz); ++ if (status == APR_SUCCESS) { ++ /* old buffer is now used so allocate new buffer */ ++ alloc_outbuf(ctx); ++ } ++ else { ++ clear_ctxpool(ctx); ++ } ++ } ++ else { ++ /* old buffer is now used so allocate new buffer */ + alloc_outbuf(ctx); ++ memcpy(ctx->curoutbuf, buf, sz); ++ ctx->curoutbuf += sz; + } + } + else { +- memcpy(ctx->curoutbuf, buf, sz); +- ctx->curoutbuf += sz; ++ clear_ctxpool(ctx); + } + } + else { +diff --git a/modules/filters/sed1.c b/modules/filters/sed1.c +index be03506..67a8d06 100644 +--- a/modules/filters/sed1.c ++++ b/modules/filters/sed1.c +@@ -71,7 +71,7 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n, + static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2); + static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + step_vars_storage *step_vars); +-static apr_status_t wline(sed_eval_t *eval, char *buf, int sz); ++static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz); + static apr_status_t arout(sed_eval_t *eval); + + static void eval_errf(sed_eval_t *eval, const char *fmt, ...) +@@ -92,11 +92,11 @@ static void eval_errf(sed_eval_t *eval, const char *fmt, ...) + * grow_buffer + */ + static void grow_buffer(apr_pool_t *pool, char **buffer, +- char **spend, unsigned int *cursize, +- unsigned int newsize) ++ char **spend, apr_size_t *cursize, ++ apr_size_t newsize) + { + char* newbuffer = NULL; +- int spendsize = 0; ++ apr_size_t spendsize = 0; + if (*cursize >= newsize) + return; + /* Avoid number of times realloc is called. It could cause huge memory +@@ -124,7 +124,7 @@ static void grow_buffer(apr_pool_t *pool, char **buffer, + /* + * grow_line_buffer + */ +-static void grow_line_buffer(sed_eval_t *eval, int newsize) ++static void grow_line_buffer(sed_eval_t *eval, apr_size_t newsize) + { + grow_buffer(eval->pool, &eval->linebuf, &eval->lspend, + &eval->lsize, newsize); +@@ -133,7 +133,7 @@ static void grow_line_buffer(sed_eval_t *eval, int newsize) + /* + * grow_hold_buffer + */ +-static void grow_hold_buffer(sed_eval_t *eval, int newsize) ++static void grow_hold_buffer(sed_eval_t *eval, apr_size_t newsize) + { + grow_buffer(eval->pool, &eval->holdbuf, &eval->hspend, + &eval->hsize, newsize); +@@ -142,7 +142,7 @@ static void grow_hold_buffer(sed_eval_t *eval, int newsize) + /* + * grow_gen_buffer + */ +-static void grow_gen_buffer(sed_eval_t *eval, int newsize, ++static void grow_gen_buffer(sed_eval_t *eval, apr_size_t newsize, + char **gspend) + { + if (gspend == NULL) { +@@ -156,9 +156,9 @@ static void grow_gen_buffer(sed_eval_t *eval, int newsize, + /* + * appendmem_to_linebuf + */ +-static void appendmem_to_linebuf(sed_eval_t *eval, const char* sz, int len) ++static void appendmem_to_linebuf(sed_eval_t *eval, const char* sz, apr_size_t len) + { +- unsigned int reqsize = (eval->lspend - eval->linebuf) + len; ++ apr_size_t reqsize = (eval->lspend - eval->linebuf) + len; + if (eval->lsize < reqsize) { + grow_line_buffer(eval, reqsize); + } +@@ -169,21 +169,36 @@ static void appendmem_to_linebuf(sed_eval_t *eval, const char* sz, int len) + /* + * append_to_linebuf + */ +-static void append_to_linebuf(sed_eval_t *eval, const char* sz) ++static void append_to_linebuf(sed_eval_t *eval, const char* sz, ++ step_vars_storage *step_vars) + { +- int len = strlen(sz); ++ apr_size_t len = strlen(sz); ++ char *old_linebuf = eval->linebuf; + /* Copy string including null character */ + appendmem_to_linebuf(eval, sz, len + 1); + --eval->lspend; /* lspend will now point to NULL character */ ++ /* Sync step_vars after a possible linebuf expansion */ ++ if (step_vars && old_linebuf != eval->linebuf) { ++ if (step_vars->loc1) { ++ step_vars->loc1 = step_vars->loc1 - old_linebuf + eval->linebuf; ++ } ++ if (step_vars->loc2) { ++ step_vars->loc2 = step_vars->loc2 - old_linebuf + eval->linebuf; ++ } ++ if (step_vars->locs) { ++ step_vars->locs = step_vars->locs - old_linebuf + eval->linebuf; ++ } ++ } + } + + /* + * copy_to_linebuf + */ +-static void copy_to_linebuf(sed_eval_t *eval, const char* sz) ++static void copy_to_linebuf(sed_eval_t *eval, const char* sz, ++ step_vars_storage *step_vars) + { + eval->lspend = eval->linebuf; +- append_to_linebuf(eval, sz); ++ append_to_linebuf(eval, sz, step_vars); + } + + /* +@@ -191,8 +206,8 @@ static void copy_to_linebuf(sed_eval_t *eval, const char* sz) + */ + static void append_to_holdbuf(sed_eval_t *eval, const char* sz) + { +- int len = strlen(sz); +- unsigned int reqsize = (eval->hspend - eval->holdbuf) + len + 1; ++ apr_size_t len = strlen(sz); ++ apr_size_t reqsize = (eval->hspend - eval->holdbuf) + len + 1; + if (eval->hsize <= reqsize) { + grow_hold_buffer(eval, reqsize); + } +@@ -215,8 +230,8 @@ static void copy_to_holdbuf(sed_eval_t *eval, const char* sz) + */ + static void append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend) + { +- int len = strlen(sz); +- unsigned int reqsize = (*gspend - eval->genbuf) + len + 1; ++ apr_size_t len = strlen(sz); ++ apr_size_t reqsize = (*gspend - eval->genbuf) + len + 1; + if (eval->gsize < reqsize) { + grow_gen_buffer(eval, reqsize, gspend); + } +@@ -230,8 +245,8 @@ static void append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend) + */ + static void copy_to_genbuf(sed_eval_t *eval, const char* sz) + { +- int len = strlen(sz); +- unsigned int reqsize = len + 1; ++ apr_size_t len = strlen(sz); ++ apr_size_t reqsize = len + 1; + if (eval->gsize < reqsize) { + grow_gen_buffer(eval, reqsize, NULL); + } +@@ -353,7 +368,7 @@ apr_status_t sed_eval_file(sed_eval_t *eval, apr_file_t *fin, void *fout) + /* + * sed_eval_buffer + */ +-apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void *fout) ++apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz, void *fout) + { + apr_status_t rv; + +@@ -383,7 +398,7 @@ apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, int bufsz, void + + while (bufsz) { + char *n; +- int llen; ++ apr_size_t llen; + + n = memchr(buf, '\n', bufsz); + if (n == NULL) +@@ -442,7 +457,7 @@ apr_status_t sed_finalize_eval(sed_eval_t *eval, void *fout) + * buffer is not a newline. + */ + /* Assure space for NULL */ +- append_to_linebuf(eval, ""); ++ append_to_linebuf(eval, "", NULL); + } + + *eval->lspend = '\0'; +@@ -666,7 +681,7 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n, + lp = step_vars->loc2; + step_vars->loc2 = sp - eval->genbuf + eval->linebuf; + append_to_genbuf(eval, lp, &sp); +- copy_to_linebuf(eval, eval->genbuf); ++ copy_to_linebuf(eval, eval->genbuf, step_vars); + return rv; + } + +@@ -676,8 +691,8 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n, + static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2) + { + char *sp = asp; +- int n = al2 - al1; +- unsigned int reqsize = (sp - eval->genbuf) + n + 1; ++ apr_size_t n = al2 - al1; ++ apr_size_t reqsize = (sp - eval->genbuf) + n + 1; + + if (eval->gsize < reqsize) { + grow_gen_buffer(eval, reqsize, &sp); +@@ -735,7 +750,7 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + } + + p1++; +- copy_to_linebuf(eval, p1); ++ copy_to_linebuf(eval, p1, step_vars); + eval->jflag++; + break; + +@@ -745,12 +760,12 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + break; + + case GCOM: +- copy_to_linebuf(eval, eval->holdbuf); ++ copy_to_linebuf(eval, eval->holdbuf, step_vars); + break; + + case CGCOM: +- append_to_linebuf(eval, "\n"); +- append_to_linebuf(eval, eval->holdbuf); ++ append_to_linebuf(eval, "\n", step_vars); ++ append_to_linebuf(eval, eval->holdbuf, step_vars); + break; + + case HCOM: +@@ -881,7 +896,7 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + if (rv != APR_SUCCESS) + return rv; + } +- append_to_linebuf(eval, "\n"); ++ append_to_linebuf(eval, "\n", step_vars); + eval->pending = ipc->next; + break; + +@@ -956,7 +971,7 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + + case XCOM: + copy_to_genbuf(eval, eval->linebuf); +- copy_to_linebuf(eval, eval->holdbuf); ++ copy_to_linebuf(eval, eval->holdbuf, step_vars); + copy_to_holdbuf(eval, eval->genbuf); + break; + +@@ -1013,7 +1028,7 @@ static apr_status_t arout(sed_eval_t *eval) + /* + * wline + */ +-static apr_status_t wline(sed_eval_t *eval, char *buf, int sz) ++static apr_status_t wline(sed_eval_t *eval, char *buf, apr_size_t sz) + { + apr_status_t rv = APR_SUCCESS; + rv = eval->writefn(eval->fout, buf, sz); diff --git a/SOURCES/httpd-2.4.37-CVE-2022-26377.patch b/SOURCES/httpd-2.4.37-CVE-2022-26377.patch new file mode 100644 index 0000000..d954758 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-26377.patch @@ -0,0 +1,26 @@ +diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c +index 6faabea..058b03f 100644 +--- a/modules/proxy/mod_proxy_ajp.c ++++ b/modules/proxy/mod_proxy_ajp.c +@@ -249,9 +249,18 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, + /* read the first bloc of data */ + input_brigade = apr_brigade_create(p, r->connection->bucket_alloc); + tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); +- if (tenc && (strcasecmp(tenc, "chunked") == 0)) { +- /* The AJP protocol does not want body data yet */ +- ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00870) "request is chunked"); ++ if (tenc) { ++ if (ap_cstr_casecmp(tenc, "chunked") == 0) { ++ /* The AJP protocol does not want body data yet */ ++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00870) ++ "request is chunked"); ++ } ++ else { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10396) ++ "%s Transfer-Encoding is not supported", ++ tenc); ++ return HTTP_INTERNAL_SERVER_ERROR; ++ } + } else { + /* Get client provided Content-Length header */ + content_length = get_content_length(r); diff --git a/SOURCES/httpd-2.4.37-CVE-2022-28614.patch b/SOURCES/httpd-2.4.37-CVE-2022-28614.patch new file mode 100644 index 0000000..3860dbe --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-28614.patch @@ -0,0 +1,47 @@ +diff --git a/include/http_protocol.h b/include/http_protocol.h +index e1572dc..8ed77ac 100644 +--- a/include/http_protocol.h ++++ b/include/http_protocol.h +@@ -439,7 +439,27 @@ AP_DECLARE(int) ap_rwrite(const void *buf, int nbyte, request_rec *r); + */ + static APR_INLINE int ap_rputs(const char *str, request_rec *r) + { +- return ap_rwrite(str, (int)strlen(str), r); ++ apr_size_t len; ++ ++ len = strlen(str); ++ ++ for (;;) { ++ if (len <= INT_MAX) { ++ return ap_rwrite(str, (int)len, r); ++ } ++ else { ++ int rc; ++ ++ rc = ap_rwrite(str, INT_MAX, r); ++ if (rc < 0) { ++ return rc; ++ } ++ else { ++ str += INT_MAX; ++ len -= INT_MAX; ++ } ++ } ++ } + } + + /** +diff --git a/server/protocol.c b/server/protocol.c +index a554970..ea461a2 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -2107,6 +2107,9 @@ AP_DECLARE(int) ap_rputc(int c, request_rec *r) + + AP_DECLARE(int) ap_rwrite(const void *buf, int nbyte, request_rec *r) + { ++ if (nbyte < 0) ++ return -1; ++ + if (r->connection->aborted) + return -1; + diff --git a/SOURCES/httpd-2.4.37-CVE-2022-28615.patch b/SOURCES/httpd-2.4.37-CVE-2022-28615.patch new file mode 100644 index 0000000..575f4a3 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-28615.patch @@ -0,0 +1,22 @@ +diff --git a/server/util.c b/server/util.c +index eefdafa..45051b7 100644 +--- a/server/util.c ++++ b/server/util.c +@@ -186,7 +186,7 @@ AP_DECLARE(char *) ap_ht_time(apr_pool_t *p, apr_time_t t, const char *fmt, + */ + AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) + { +- int x, y; ++ apr_size_t x, y; + + for (x = 0, y = 0; expected[y]; ++y, ++x) { + if ((!str[x]) && (expected[y] != '*')) +@@ -210,7 +210,7 @@ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) + + AP_DECLARE(int) ap_strcasecmp_match(const char *str, const char *expected) + { +- int x, y; ++ apr_size_t x, y; + + for (x = 0, y = 0; expected[y]; ++y, ++x) { + if (!str[x] && expected[y] != '*') diff --git a/SOURCES/httpd-2.4.37-CVE-2022-29404.patch b/SOURCES/httpd-2.4.37-CVE-2022-29404.patch new file mode 100644 index 0000000..3d706be --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-29404.patch @@ -0,0 +1,126 @@ +diff --git a/docs/manual/mod/core.html.en b/docs/manual/mod/core.html.en +index e1ec8d0..833fa7b 100644 +--- a/docs/manual/mod/core.html.en ++++ b/docs/manual/mod/core.html.en +@@ -2748,16 +2748,16 @@ subrequests + Description:Restricts the total size of the HTTP request body sent + from the client + Syntax:LimitRequestBody bytes +-Default:LimitRequestBody 0 ++Default:LimitRequestBody 1073741824 + Context:server config, virtual host, directory, .htaccess + Override:All + Status:Core + Module:core ++Compatibility:In Apache HTTP Server 2.4.53 and earlier, the default value ++ was 0 (unlimited) + +-

This directive specifies the number of bytes from 0 +- (meaning unlimited) to 2147483647 (2GB) that are allowed in a +- request body. See the note below for the limited applicability +- to proxy requests.

++

This directive specifies the number of bytes ++ that are allowed in a request body. A value of 0 means unlimited.

+ +

The LimitRequestBody directive allows + the user to set a limit on the allowed size of an HTTP request +@@ -2783,12 +2783,6 @@ from the client + +

LimitRequestBody 102400
+ +- +-

For a full description of how this directive is interpreted by +- proxy requests, see the mod_proxy documentation.

+-
+- +- + +
top
+

LimitRequestFields Directive

+diff --git a/docs/manual/mod/mod_proxy.html.en b/docs/manual/mod/mod_proxy.html.en +index 2cc6ace..c9e4634 100644 +--- a/docs/manual/mod/mod_proxy.html.en ++++ b/docs/manual/mod/mod_proxy.html.en +@@ -459,9 +459,6 @@ ProxyPass "/examples" "http://backend.example.com/examples" timeout=10 + Content-Length header, but the server is configured to filter incoming + request bodies.

+ +-

LimitRequestBody only applies to +- request bodies that the server will spool to disk

+- +
top
+
+

Reverse Proxy Request Headers

+diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c +index 6bedcac..393343a 100644 +--- a/modules/http/http_filters.c ++++ b/modules/http/http_filters.c +@@ -1710,6 +1710,7 @@ AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) + { + const char *tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); + const char *lenp = apr_table_get(r->headers_in, "Content-Length"); ++ apr_off_t limit_req_body = ap_get_limit_req_body(r); + + r->read_body = read_policy; + r->read_chunked = 0; +@@ -1748,6 +1749,11 @@ AP_DECLARE(int) ap_setup_client_block(request_rec *r, int read_policy) + return HTTP_REQUEST_ENTITY_TOO_LARGE; + } + ++ if (limit_req_body > 0 && (r->remaining > limit_req_body)) { ++ /* will be logged when the body is discarded */ ++ return HTTP_REQUEST_ENTITY_TOO_LARGE; ++ } ++ + #ifdef AP_DEBUG + { + /* Make sure ap_getline() didn't leave any droppings. */ +diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c +index 7da9bde..1b7bb81 100644 +--- a/modules/proxy/mod_proxy_http.c ++++ b/modules/proxy/mod_proxy_http.c +@@ -439,13 +439,10 @@ static int spool_reqbody_cl(proxy_http_req_t *req, apr_off_t *bytes_spooled) + apr_bucket *e; + apr_off_t bytes, fsize = 0; + apr_file_t *tmpfile = NULL; +- apr_off_t limit; + + body_brigade = apr_brigade_create(p, bucket_alloc); + *bytes_spooled = 0; + +- limit = ap_get_limit_req_body(r); +- + do { + if (APR_BRIGADE_EMPTY(input_brigade)) { + rv = stream_reqbody_read(req, input_brigade, 0); +@@ -462,17 +459,6 @@ static int spool_reqbody_cl(proxy_http_req_t *req, apr_off_t *bytes_spooled) + apr_brigade_length(input_brigade, 1, &bytes); + + if (*bytes_spooled + bytes > MAX_MEM_SPOOL) { +- /* +- * LimitRequestBody does not affect Proxy requests (Should it?). +- * Let it take effect if we decide to store the body in a +- * temporary file on disk. +- */ +- if (limit && (*bytes_spooled + bytes > limit)) { +- ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01088) +- "Request body is larger than the configured " +- "limit of %" APR_OFF_T_FMT, limit); +- return HTTP_REQUEST_ENTITY_TOO_LARGE; +- } + /* can't spool any more in memory; write latest brigade to disk */ + if (tmpfile == NULL) { + const char *temp_dir; +diff --git a/server/core.c b/server/core.c +index 09664fc..084e243 100644 +--- a/server/core.c ++++ b/server/core.c +@@ -65,7 +65,7 @@ + + /* LimitRequestBody handling */ + #define AP_LIMIT_REQ_BODY_UNSET ((apr_off_t) -1) +-#define AP_DEFAULT_LIMIT_REQ_BODY ((apr_off_t) 0) ++#define AP_DEFAULT_LIMIT_REQ_BODY ((apr_off_t) 1<<30) /* 1GB */ + + /* LimitXMLRequestBody handling */ + #define AP_LIMIT_UNSET ((long) -1) diff --git a/SOURCES/httpd-2.4.37-CVE-2022-30522.patch b/SOURCES/httpd-2.4.37-CVE-2022-30522.patch new file mode 100644 index 0000000..92cffdc --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-30522.patch @@ -0,0 +1,541 @@ +diff --git a/modules/filters/mod_sed.c b/modules/filters/mod_sed.c +index 8595e41..9b99a6b 100644 +--- a/modules/filters/mod_sed.c ++++ b/modules/filters/mod_sed.c +@@ -59,7 +59,7 @@ typedef struct sed_filter_ctxt + module AP_MODULE_DECLARE_DATA sed_module; + + /* This function will be call back from libsed functions if there is any error +- * happend during execution of sed scripts ++ * happened during execution of sed scripts + */ + static apr_status_t log_sed_errf(void *data, const char *error) + { +@@ -276,7 +276,7 @@ static apr_status_t sed_response_filter(ap_filter_t *f, + apr_bucket_brigade *bb) + { + apr_bucket *b; +- apr_status_t status; ++ apr_status_t status = APR_SUCCESS; + sed_config *cfg = ap_get_module_config(f->r->per_dir_config, + &sed_module); + sed_filter_ctxt *ctx = f->ctx; +@@ -301,9 +301,9 @@ static apr_status_t sed_response_filter(ap_filter_t *f, + return status; + ctx = f->ctx; + apr_table_unset(f->r->headers_out, "Content-Length"); +- } + +- ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); ++ ctx->bb = apr_brigade_create(f->r->pool, f->c->bucket_alloc); ++ } + + /* Here is the main logic. Iterate through all the buckets, read the + * content of the bucket, call sed_eval_buffer on the data. +@@ -325,63 +325,52 @@ static apr_status_t sed_response_filter(ap_filter_t *f, + * in sed's internal buffer which can't be flushed until new line + * character is arrived. + */ +- for (b = APR_BRIGADE_FIRST(bb); b != APR_BRIGADE_SENTINEL(bb);) { +- const char *buf = NULL; +- apr_size_t bytes = 0; ++ while (!APR_BRIGADE_EMPTY(bb)) { ++ b = APR_BRIGADE_FIRST(bb); + if (APR_BUCKET_IS_EOS(b)) { +- apr_bucket *b1 = APR_BUCKET_NEXT(b); + /* Now clean up the internal sed buffer */ + sed_finalize_eval(&ctx->eval, ctx); + status = flush_output_buffer(ctx); + if (status != APR_SUCCESS) { +- clear_ctxpool(ctx); +- return status; ++ break; + } ++ /* Move the eos bucket to ctx->bb brigade */ + APR_BUCKET_REMOVE(b); +- /* Insert the eos bucket to ctx->bb brigade */ + APR_BRIGADE_INSERT_TAIL(ctx->bb, b); +- b = b1; + } + else if (APR_BUCKET_IS_FLUSH(b)) { +- apr_bucket *b1 = APR_BUCKET_NEXT(b); +- APR_BUCKET_REMOVE(b); + status = flush_output_buffer(ctx); + if (status != APR_SUCCESS) { +- clear_ctxpool(ctx); +- return status; ++ break; + } ++ /* Move the flush bucket to ctx->bb brigade */ ++ APR_BUCKET_REMOVE(b); + APR_BRIGADE_INSERT_TAIL(ctx->bb, b); +- b = b1; +- } +- else if (APR_BUCKET_IS_METADATA(b)) { +- b = APR_BUCKET_NEXT(b); + } +- else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ) +- == APR_SUCCESS) { +- apr_bucket *b1 = APR_BUCKET_NEXT(b); +- status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx); +- if (status != APR_SUCCESS) { +- clear_ctxpool(ctx); +- return status; ++ else { ++ if (!APR_BUCKET_IS_METADATA(b)) { ++ const char *buf = NULL; ++ apr_size_t bytes = 0; ++ ++ status = apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ); ++ if (status == APR_SUCCESS) { ++ status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx); ++ } ++ if (status != APR_SUCCESS) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10394) "error evaluating sed on output"); ++ break; ++ } + } +- APR_BUCKET_REMOVE(b); + apr_bucket_delete(b); +- b = b1; +- } +- else { +- apr_bucket *b1 = APR_BUCKET_NEXT(b); +- APR_BUCKET_REMOVE(b); +- b = b1; + } + } +- apr_brigade_cleanup(bb); +- status = flush_output_buffer(ctx); +- if (status != APR_SUCCESS) { +- clear_ctxpool(ctx); +- return status; ++ if (status == APR_SUCCESS) { ++ status = flush_output_buffer(ctx); + } + if (!APR_BRIGADE_EMPTY(ctx->bb)) { +- status = ap_pass_brigade(f->next, ctx->bb); ++ if (status == APR_SUCCESS) { ++ status = ap_pass_brigade(f->next, ctx->bb); ++ } + apr_brigade_cleanup(ctx->bb); + } + clear_ctxpool(ctx); +@@ -432,7 +421,7 @@ static apr_status_t sed_request_filter(ap_filter_t *f, + * the buckets in bbinp and read the data from buckets and invoke + * sed_eval_buffer on the data. libsed will generate its output using + * sed_write_output which will add data in ctx->bb. Do it until it have +- * atleast one bucket in ctx->bb. At the end of data eos bucket ++ * at least one bucket in ctx->bb. At the end of data eos bucket + * should be there. + * + * Once eos bucket is seen, then invoke sed_finalize_eval to clear the +@@ -474,8 +463,10 @@ static apr_status_t sed_request_filter(ap_filter_t *f, + if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ) + == APR_SUCCESS) { + status = sed_eval_buffer(&ctx->eval, buf, bytes, ctx); +- if (status != APR_SUCCESS) ++ if (status != APR_SUCCESS) { ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, status, f->r, APLOGNO(10395) "error evaluating sed on input"); + return status; ++ } + flush_output_buffer(ctx); + } + } +diff --git a/modules/filters/sed1.c b/modules/filters/sed1.c +index 67a8d06..047f49b 100644 +--- a/modules/filters/sed1.c ++++ b/modules/filters/sed1.c +@@ -87,18 +87,20 @@ static void eval_errf(sed_eval_t *eval, const char *fmt, ...) + } + + #define INIT_BUF_SIZE 1024 ++#define MAX_BUF_SIZE 1024*8192 + + /* + * grow_buffer + */ +-static void grow_buffer(apr_pool_t *pool, char **buffer, ++static apr_status_t grow_buffer(apr_pool_t *pool, char **buffer, + char **spend, apr_size_t *cursize, + apr_size_t newsize) + { + char* newbuffer = NULL; + apr_size_t spendsize = 0; +- if (*cursize >= newsize) +- return; ++ if (*cursize >= newsize) { ++ return APR_SUCCESS; ++ } + /* Avoid number of times realloc is called. It could cause huge memory + * requirement if line size is huge e.g 2 MB */ + if (newsize < *cursize * 2) { +@@ -107,6 +109,9 @@ static void grow_buffer(apr_pool_t *pool, char **buffer, + + /* Align it to 4 KB boundary */ + newsize = (newsize + ((1 << 12) - 1)) & ~((1 << 12) - 1); ++ if (newsize > MAX_BUF_SIZE) { ++ return APR_ENOMEM; ++ } + newbuffer = apr_pcalloc(pool, newsize); + if (*spend && *buffer && (*cursize > 0)) { + spendsize = *spend - *buffer; +@@ -119,63 +124,77 @@ static void grow_buffer(apr_pool_t *pool, char **buffer, + if (spend != buffer) { + *spend = *buffer + spendsize; + } ++ return APR_SUCCESS; + } + + /* + * grow_line_buffer + */ +-static void grow_line_buffer(sed_eval_t *eval, apr_size_t newsize) ++static apr_status_t grow_line_buffer(sed_eval_t *eval, apr_size_t newsize) + { +- grow_buffer(eval->pool, &eval->linebuf, &eval->lspend, ++ return grow_buffer(eval->pool, &eval->linebuf, &eval->lspend, + &eval->lsize, newsize); + } + + /* + * grow_hold_buffer + */ +-static void grow_hold_buffer(sed_eval_t *eval, apr_size_t newsize) ++static apr_status_t grow_hold_buffer(sed_eval_t *eval, apr_size_t newsize) + { +- grow_buffer(eval->pool, &eval->holdbuf, &eval->hspend, ++ return grow_buffer(eval->pool, &eval->holdbuf, &eval->hspend, + &eval->hsize, newsize); + } + + /* + * grow_gen_buffer + */ +-static void grow_gen_buffer(sed_eval_t *eval, apr_size_t newsize, ++static apr_status_t grow_gen_buffer(sed_eval_t *eval, apr_size_t newsize, + char **gspend) + { ++ apr_status_t rc = 0; + if (gspend == NULL) { + gspend = &eval->genbuf; + } +- grow_buffer(eval->pool, &eval->genbuf, gspend, +- &eval->gsize, newsize); +- eval->lcomend = &eval->genbuf[71]; ++ rc = grow_buffer(eval->pool, &eval->genbuf, gspend, ++ &eval->gsize, newsize); ++ if (rc == APR_SUCCESS) { ++ eval->lcomend = &eval->genbuf[71]; ++ } ++ return rc; + } + + /* + * appendmem_to_linebuf + */ +-static void appendmem_to_linebuf(sed_eval_t *eval, const char* sz, apr_size_t len) ++static apr_status_t appendmem_to_linebuf(sed_eval_t *eval, const char* sz, apr_size_t len) + { ++ apr_status_t rc = 0; + apr_size_t reqsize = (eval->lspend - eval->linebuf) + len; + if (eval->lsize < reqsize) { +- grow_line_buffer(eval, reqsize); ++ rc = grow_line_buffer(eval, reqsize); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + } + memcpy(eval->lspend, sz, len); + eval->lspend += len; ++ return APR_SUCCESS; + } + + /* + * append_to_linebuf + */ +-static void append_to_linebuf(sed_eval_t *eval, const char* sz, ++static apr_status_t append_to_linebuf(sed_eval_t *eval, const char* sz, + step_vars_storage *step_vars) + { + apr_size_t len = strlen(sz); + char *old_linebuf = eval->linebuf; ++ apr_status_t rc = 0; + /* Copy string including null character */ +- appendmem_to_linebuf(eval, sz, len + 1); ++ rc = appendmem_to_linebuf(eval, sz, len + 1); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + --eval->lspend; /* lspend will now point to NULL character */ + /* Sync step_vars after a possible linebuf expansion */ + if (step_vars && old_linebuf != eval->linebuf) { +@@ -189,68 +208,84 @@ static void append_to_linebuf(sed_eval_t *eval, const char* sz, + step_vars->locs = step_vars->locs - old_linebuf + eval->linebuf; + } + } ++ return APR_SUCCESS; + } + + /* + * copy_to_linebuf + */ +-static void copy_to_linebuf(sed_eval_t *eval, const char* sz, ++static apr_status_t copy_to_linebuf(sed_eval_t *eval, const char* sz, + step_vars_storage *step_vars) + { + eval->lspend = eval->linebuf; +- append_to_linebuf(eval, sz, step_vars); ++ return append_to_linebuf(eval, sz, step_vars); + } + + /* + * append_to_holdbuf + */ +-static void append_to_holdbuf(sed_eval_t *eval, const char* sz) ++static apr_status_t append_to_holdbuf(sed_eval_t *eval, const char* sz) + { + apr_size_t len = strlen(sz); + apr_size_t reqsize = (eval->hspend - eval->holdbuf) + len + 1; ++ apr_status_t rc = 0; + if (eval->hsize <= reqsize) { +- grow_hold_buffer(eval, reqsize); ++ rc = grow_hold_buffer(eval, reqsize); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + } + memcpy(eval->hspend, sz, len + 1); + /* hspend will now point to NULL character */ + eval->hspend += len; ++ return APR_SUCCESS; + } + + /* + * copy_to_holdbuf + */ +-static void copy_to_holdbuf(sed_eval_t *eval, const char* sz) ++static apr_status_t copy_to_holdbuf(sed_eval_t *eval, const char* sz) + { + eval->hspend = eval->holdbuf; +- append_to_holdbuf(eval, sz); ++ return append_to_holdbuf(eval, sz); + } + + /* + * append_to_genbuf + */ +-static void append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend) ++static apr_status_t append_to_genbuf(sed_eval_t *eval, const char* sz, char **gspend) + { + apr_size_t len = strlen(sz); + apr_size_t reqsize = (*gspend - eval->genbuf) + len + 1; ++ apr_status_t rc = 0; + if (eval->gsize < reqsize) { +- grow_gen_buffer(eval, reqsize, gspend); ++ rc = grow_gen_buffer(eval, reqsize, gspend); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + } + memcpy(*gspend, sz, len + 1); + /* *gspend will now point to NULL character */ + *gspend += len; ++ return APR_SUCCESS; + } + + /* + * copy_to_genbuf + */ +-static void copy_to_genbuf(sed_eval_t *eval, const char* sz) ++static apr_status_t copy_to_genbuf(sed_eval_t *eval, const char* sz) + { + apr_size_t len = strlen(sz); + apr_size_t reqsize = len + 1; ++ apr_status_t rc = APR_SUCCESS;; + if (eval->gsize < reqsize) { +- grow_gen_buffer(eval, reqsize, NULL); ++ rc = grow_gen_buffer(eval, reqsize, NULL); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + } + memcpy(eval->genbuf, sz, len + 1); ++ return rc; + } + + /* +@@ -397,6 +432,7 @@ apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz + } + + while (bufsz) { ++ apr_status_t rc = 0; + char *n; + apr_size_t llen; + +@@ -411,7 +447,10 @@ apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz + break; + } + +- appendmem_to_linebuf(eval, buf, llen + 1); ++ rc = appendmem_to_linebuf(eval, buf, llen + 1); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + --eval->lspend; + /* replace new line character with NULL */ + *eval->lspend = '\0'; +@@ -426,7 +465,10 @@ apr_status_t sed_eval_buffer(sed_eval_t *eval, const char *buf, apr_size_t bufsz + + /* Save the leftovers for later */ + if (bufsz) { +- appendmem_to_linebuf(eval, buf, bufsz); ++ apr_status_t rc = appendmem_to_linebuf(eval, buf, bufsz); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + } + + return APR_SUCCESS; +@@ -448,6 +490,7 @@ apr_status_t sed_finalize_eval(sed_eval_t *eval, void *fout) + /* Process leftovers */ + if (eval->lspend > eval->linebuf) { + apr_status_t rv; ++ apr_status_t rc = 0; + + if (eval->lreadyflag) { + eval->lreadyflag = 0; +@@ -457,7 +500,10 @@ apr_status_t sed_finalize_eval(sed_eval_t *eval, void *fout) + * buffer is not a newline. + */ + /* Assure space for NULL */ +- append_to_linebuf(eval, "", NULL); ++ rc = append_to_linebuf(eval, "", NULL); ++ if (rc != APR_SUCCESS) { ++ return rc; ++ } + } + + *eval->lspend = '\0'; +@@ -655,11 +701,15 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n, + sp = eval->genbuf; + rp = rhsbuf; + sp = place(eval, sp, lp, step_vars->loc1); ++ if (sp == NULL) { ++ return APR_EGENERAL; ++ } + while ((c = *rp++) != 0) { + if (c == '&') { + sp = place(eval, sp, step_vars->loc1, step_vars->loc2); +- if (sp == NULL) ++ if (sp == NULL) { + return APR_EGENERAL; ++ } + } + else if (c == '\\') { + c = *rp++; +@@ -675,13 +725,19 @@ static apr_status_t dosub(sed_eval_t *eval, char *rhsbuf, int n, + *sp++ = c; + if (sp >= eval->genbuf + eval->gsize) { + /* expand genbuf and set the sp appropriately */ +- grow_gen_buffer(eval, eval->gsize + 1024, &sp); ++ rv = grow_gen_buffer(eval, eval->gsize + 1024, &sp); ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } + } + } + lp = step_vars->loc2; + step_vars->loc2 = sp - eval->genbuf + eval->linebuf; +- append_to_genbuf(eval, lp, &sp); +- copy_to_linebuf(eval, eval->genbuf, step_vars); ++ rv = append_to_genbuf(eval, lp, &sp); ++ if (rv != APR_SUCCESS) { ++ return rv; ++ } ++ rv = copy_to_linebuf(eval, eval->genbuf, step_vars); + return rv; + } + +@@ -695,7 +751,10 @@ static char *place(sed_eval_t *eval, char *asp, char *al1, char *al2) + apr_size_t reqsize = (sp - eval->genbuf) + n + 1; + + if (eval->gsize < reqsize) { +- grow_gen_buffer(eval, reqsize, &sp); ++ apr_status_t rc = grow_gen_buffer(eval, reqsize, &sp); ++ if (rc != APR_SUCCESS) { ++ return NULL; ++ } + } + memcpy(sp, al1, n); + return sp + n; +@@ -750,7 +809,8 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + } + + p1++; +- copy_to_linebuf(eval, p1, step_vars); ++ rv = copy_to_linebuf(eval, p1, step_vars); ++ if (rv != APR_SUCCESS) return rv; + eval->jflag++; + break; + +@@ -760,21 +820,27 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + break; + + case GCOM: +- copy_to_linebuf(eval, eval->holdbuf, step_vars); ++ rv = copy_to_linebuf(eval, eval->holdbuf, step_vars); ++ if (rv != APR_SUCCESS) return rv; + break; + + case CGCOM: +- append_to_linebuf(eval, "\n", step_vars); +- append_to_linebuf(eval, eval->holdbuf, step_vars); ++ rv = append_to_linebuf(eval, "\n", step_vars); ++ if (rv != APR_SUCCESS) return rv; ++ rv = append_to_linebuf(eval, eval->holdbuf, step_vars); ++ if (rv != APR_SUCCESS) return rv; + break; + + case HCOM: +- copy_to_holdbuf(eval, eval->linebuf); ++ rv = copy_to_holdbuf(eval, eval->linebuf); ++ if (rv != APR_SUCCESS) return rv; + break; + + case CHCOM: +- append_to_holdbuf(eval, "\n"); +- append_to_holdbuf(eval, eval->linebuf); ++ rv = append_to_holdbuf(eval, "\n"); ++ if (rv != APR_SUCCESS) return rv; ++ rv = append_to_holdbuf(eval, eval->linebuf); ++ if (rv != APR_SUCCESS) return rv; + break; + + case ICOM: +@@ -896,7 +962,8 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + if (rv != APR_SUCCESS) + return rv; + } +- append_to_linebuf(eval, "\n", step_vars); ++ rv = append_to_linebuf(eval, "\n", step_vars); ++ if (rv != APR_SUCCESS) return rv; + eval->pending = ipc->next; + break; + +@@ -970,9 +1037,12 @@ static apr_status_t command(sed_eval_t *eval, sed_reptr_t *ipc, + break; + + case XCOM: +- copy_to_genbuf(eval, eval->linebuf); +- copy_to_linebuf(eval, eval->holdbuf, step_vars); +- copy_to_holdbuf(eval, eval->genbuf); ++ rv = copy_to_genbuf(eval, eval->linebuf); ++ if (rv != APR_SUCCESS) return rv; ++ rv = copy_to_linebuf(eval, eval->holdbuf, step_vars); ++ if (rv != APR_SUCCESS) return rv; ++ rv = copy_to_holdbuf(eval, eval->genbuf); ++ if (rv != APR_SUCCESS) return rv; + break; + + case YCOM: diff --git a/SOURCES/httpd-2.4.37-CVE-2022-30556.patch b/SOURCES/httpd-2.4.37-CVE-2022-30556.patch new file mode 100644 index 0000000..971a8d9 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-30556.patch @@ -0,0 +1,233 @@ +diff --git a/modules/lua/lua_request.c b/modules/lua/lua_request.c +index ba63584..c1ba74a 100644 +--- a/modules/lua/lua_request.c ++++ b/modules/lua/lua_request.c +@@ -2193,23 +2193,20 @@ static int lua_websocket_greet(lua_State *L) + return 0; + } + +-static apr_status_t lua_websocket_readbytes(conn_rec* c, char* buffer, +- apr_off_t len) ++static apr_status_t lua_websocket_readbytes(conn_rec* c, ++ apr_bucket_brigade *brigade, ++ char* buffer, apr_off_t len) + { +- apr_bucket_brigade *brigade = apr_brigade_create(c->pool, c->bucket_alloc); ++ apr_size_t delivered; + apr_status_t rv; ++ + rv = ap_get_brigade(c->input_filters, brigade, AP_MODE_READBYTES, + APR_BLOCK_READ, len); + if (rv == APR_SUCCESS) { +- if (!APR_BRIGADE_EMPTY(brigade)) { +- apr_bucket* bucket = APR_BRIGADE_FIRST(brigade); +- const char* data = NULL; +- apr_size_t data_length = 0; +- rv = apr_bucket_read(bucket, &data, &data_length, APR_BLOCK_READ); +- if (rv == APR_SUCCESS) { +- memcpy(buffer, data, len); +- } +- apr_bucket_delete(bucket); ++ delivered = len; ++ rv = apr_brigade_flatten(brigade, buffer, &delivered); ++ if ((rv == APR_SUCCESS) && (delivered < len)) { ++ rv = APR_INCOMPLETE; + } + } + apr_brigade_cleanup(brigade); +@@ -2239,35 +2236,28 @@ static int lua_websocket_peek(lua_State *L) + + static int lua_websocket_read(lua_State *L) + { +- apr_socket_t *sock; + apr_status_t rv; + int do_read = 1; + int n = 0; +- apr_size_t len = 1; + apr_size_t plen = 0; + unsigned short payload_short = 0; + apr_uint64_t payload_long = 0; + unsigned char *mask_bytes; + char byte; +- int plaintext; +- +- ++ apr_bucket_brigade *brigade; ++ conn_rec* c; ++ + request_rec *r = ap_lua_check_request_rec(L, 1); +- plaintext = ap_lua_ssl_is_https(r->connection) ? 0 : 1; ++ c = r->connection; + +- + mask_bytes = apr_pcalloc(r->pool, 4); +- sock = ap_get_conn_socket(r->connection); ++ ++ brigade = apr_brigade_create(r->pool, c->bucket_alloc); + + while (do_read) { + do_read = 0; + /* Get opcode and FIN bit */ +- if (plaintext) { +- rv = apr_socket_recv(sock, &byte, &len); +- } +- else { +- rv = lua_websocket_readbytes(r->connection, &byte, 1); +- } ++ rv = lua_websocket_readbytes(c, brigade, &byte, 1); + if (rv == APR_SUCCESS) { + unsigned char ubyte, fin, opcode, mask, payload; + ubyte = (unsigned char)byte; +@@ -2277,12 +2267,7 @@ static int lua_websocket_read(lua_State *L) + opcode = ubyte & 0xf; + + /* Get the payload length and mask bit */ +- if (plaintext) { +- rv = apr_socket_recv(sock, &byte, &len); +- } +- else { +- rv = lua_websocket_readbytes(r->connection, &byte, 1); +- } ++ rv = lua_websocket_readbytes(c, brigade, &byte, 1); + if (rv == APR_SUCCESS) { + ubyte = (unsigned char)byte; + /* Mask is the first bit */ +@@ -2293,40 +2278,25 @@ static int lua_websocket_read(lua_State *L) + + /* Extended payload? */ + if (payload == 126) { +- len = 2; +- if (plaintext) { +- /* XXX: apr_socket_recv does not receive len bits, only up to len bits! */ +- rv = apr_socket_recv(sock, (char*) &payload_short, &len); +- } +- else { +- rv = lua_websocket_readbytes(r->connection, +- (char*) &payload_short, 2); +- } +- payload_short = ntohs(payload_short); ++ rv = lua_websocket_readbytes(c, brigade, ++ (char*) &payload_short, 2); + +- if (rv == APR_SUCCESS) { +- plen = payload_short; +- } +- else { ++ if (rv != APR_SUCCESS) { + return 0; + } ++ ++ plen = ntohs(payload_short); + } + /* Super duper extended payload? */ + if (payload == 127) { +- len = 8; +- if (plaintext) { +- rv = apr_socket_recv(sock, (char*) &payload_long, &len); +- } +- else { +- rv = lua_websocket_readbytes(r->connection, +- (char*) &payload_long, 8); +- } +- if (rv == APR_SUCCESS) { +- plen = ap_ntoh64(&payload_long); +- } +- else { ++ rv = lua_websocket_readbytes(c, brigade, ++ (char*) &payload_long, 8); ++ ++ if (rv != APR_SUCCESS) { + return 0; + } ++ ++ plen = ap_ntoh64(&payload_long); + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03210) + "Websocket: Reading %" APR_SIZE_T_FMT " (%s) bytes, masking is %s. %s", +@@ -2335,46 +2305,27 @@ static int lua_websocket_read(lua_State *L) + mask ? "on" : "off", + fin ? "This is a final frame" : "more to follow"); + if (mask) { +- len = 4; +- if (plaintext) { +- rv = apr_socket_recv(sock, (char*) mask_bytes, &len); +- } +- else { +- rv = lua_websocket_readbytes(r->connection, +- (char*) mask_bytes, 4); +- } ++ rv = lua_websocket_readbytes(c, brigade, ++ (char*) mask_bytes, 4); ++ + if (rv != APR_SUCCESS) { + return 0; + } + } + if (plen < (HUGE_STRING_LEN*1024) && plen > 0) { + apr_size_t remaining = plen; +- apr_size_t received; +- apr_off_t at = 0; + char *buffer = apr_palloc(r->pool, plen+1); + buffer[plen] = 0; + +- if (plaintext) { +- while (remaining > 0) { +- received = remaining; +- rv = apr_socket_recv(sock, buffer+at, &received); +- if (received > 0 ) { +- remaining -= received; +- at += received; +- } +- } +- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, +- "Websocket: Frame contained %" APR_OFF_T_FMT " bytes, pushed to Lua stack", +- at); +- } +- else { +- rv = lua_websocket_readbytes(r->connection, buffer, +- remaining); +- ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, +- "Websocket: SSL Frame contained %" APR_SIZE_T_FMT " bytes, "\ +- "pushed to Lua stack", +- remaining); ++ rv = lua_websocket_readbytes(c, brigade, buffer, remaining); ++ ++ if (rv != APR_SUCCESS) { ++ return 0; + } ++ ++ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, ++ "Websocket: Frame contained %" APR_SIZE_T_FMT \ ++ " bytes, pushed to Lua stack", remaining); + if (mask) { + for (n = 0; n < plen; n++) { + buffer[n] ^= mask_bytes[n%4]; +@@ -2386,14 +2337,25 @@ static int lua_websocket_read(lua_State *L) + return 2; + } + +- + /* Decide if we need to react to the opcode or not */ + if (opcode == 0x09) { /* ping */ + char frame[2]; +- plen = 2; ++ apr_bucket *b; ++ + frame[0] = 0x8A; + frame[1] = 0; +- apr_socket_send(sock, frame, &plen); /* Pong! */ ++ ++ /* Pong! */ ++ b = apr_bucket_transient_create(frame, 2, c->bucket_alloc); ++ APR_BRIGADE_INSERT_TAIL(brigade, b); ++ ++ rv = ap_pass_brigade(c->output_filters, brigade); ++ apr_brigade_cleanup(brigade); ++ ++ if (rv != APR_SUCCESS) { ++ return 0; ++ } ++ + do_read = 1; + } + } diff --git a/SOURCES/httpd-2.4.37-CVE-2022-31813.patch b/SOURCES/httpd-2.4.37-CVE-2022-31813.patch new file mode 100644 index 0000000..7fe91d1 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-31813.patch @@ -0,0 +1,229 @@ +diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c +index 3d5b220..ec9a414 100644 +--- a/modules/proxy/proxy_util.c ++++ b/modules/proxy/proxy_util.c +@@ -3621,12 +3621,14 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + char **old_cl_val, + char **old_te_val) + { ++ int rc = OK; + conn_rec *c = r->connection; + int counter; + char *buf; ++ apr_table_t *saved_headers_in = r->headers_in; ++ const char *saved_host = apr_table_get(saved_headers_in, "Host"); + const apr_array_header_t *headers_in_array; + const apr_table_entry_t *headers_in; +- apr_table_t *saved_headers_in; + apr_bucket *e; + int do_100_continue; + conn_rec *origin = p_conn->connection; +@@ -3662,6 +3664,52 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + ap_xlate_proto_to_ascii(buf, strlen(buf)); + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); ++ ++ /* ++ * Make a copy on r->headers_in for the request we make to the backend, ++ * modify the copy in place according to our configuration and connection ++ * handling, use it to fill in the forwarded headers' brigade, and finally ++ * restore the saved/original ones in r->headers_in. ++ * ++ * Note: We need to take r->pool for apr_table_copy as the key / value ++ * pairs in r->headers_in have been created out of r->pool and ++ * p might be (and actually is) a longer living pool. ++ * This would trigger the bad pool ancestry abort in apr_table_copy if ++ * apr is compiled with APR_POOL_DEBUG. ++ * ++ * icing: if p indeed lives longer than r->pool, we should allocate ++ * all new header values from r->pool as well and avoid leakage. ++ */ ++ r->headers_in = apr_table_copy(r->pool, saved_headers_in); ++ ++ /* Return the original Transfer-Encoding and/or Content-Length values ++ * then drop the headers, they must be set by the proxy handler based ++ * on the actual body being forwarded. ++ */ ++ if ((*old_te_val = (char *)apr_table_get(r->headers_in, ++ "Transfer-Encoding"))) { ++ apr_table_unset(r->headers_in, "Transfer-Encoding"); ++ } ++ if ((*old_cl_val = (char *)apr_table_get(r->headers_in, ++ "Content-Length"))) { ++ apr_table_unset(r->headers_in, "Content-Length"); ++ } ++ ++ /* Clear out hop-by-hop request headers not to forward */ ++ if (ap_proxy_clear_connection(r, r->headers_in) < 0) { ++ rc = HTTP_BAD_REQUEST; ++ goto cleanup; ++ } ++ ++ /* RFC2616 13.5.1 says we should strip these */ ++ apr_table_unset(r->headers_in, "Keep-Alive"); ++ apr_table_unset(r->headers_in, "Upgrade"); ++ apr_table_unset(r->headers_in, "Trailer"); ++ apr_table_unset(r->headers_in, "TE"); ++ ++ /* We used to send `Host: ` always first, so let's keep it that ++ * way. No telling which legacy backend is relying no this. ++ */ + if (dconf->preserve_host == 0) { + if (ap_strchr_c(uri->hostname, ':')) { /* if literal IPv6 address */ + if (uri->port_str && uri->port != DEFAULT_HTTP_PORT) { +@@ -3683,7 +3731,7 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + /* don't want to use r->hostname, as the incoming header might have a + * port attached + */ +- const char* hostname = apr_table_get(r->headers_in,"Host"); ++ const char* hostname = saved_host; + if (!hostname) { + hostname = r->server->server_hostname; + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01092) +@@ -3697,21 +3745,7 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + ap_xlate_proto_to_ascii(buf, strlen(buf)); + e = apr_bucket_pool_create(buf, strlen(buf), p, c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(header_brigade, e); +- +- /* +- * Save the original headers in here and restore them when leaving, since +- * we will apply proxy purpose only modifications (eg. clearing hop-by-hop +- * headers, add Via or X-Forwarded-* or Expect...), whereas the originals +- * will be needed later to prepare the correct response and logging. +- * +- * Note: We need to take r->pool for apr_table_copy as the key / value +- * pairs in r->headers_in have been created out of r->pool and +- * p might be (and actually is) a longer living pool. +- * This would trigger the bad pool ancestry abort in apr_table_copy if +- * apr is compiled with APR_POOL_DEBUG. +- */ +- saved_headers_in = r->headers_in; +- r->headers_in = apr_table_copy(r->pool, saved_headers_in); ++ apr_table_unset(r->headers_in, "Host"); + + /* handle Via */ + if (conf->viaopt == via_block) { +@@ -3778,8 +3812,6 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + */ + if (dconf->add_forwarded_headers) { + if (PROXYREQ_REVERSE == r->proxyreq) { +- const char *buf; +- + /* Add X-Forwarded-For: so that the upstream has a chance to + * determine, where the original request came from. + */ +@@ -3789,8 +3821,9 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + /* Add X-Forwarded-Host: so that upstream knows what the + * original request hostname was. + */ +- if ((buf = apr_table_get(r->headers_in, "Host"))) { +- apr_table_mergen(r->headers_in, "X-Forwarded-Host", buf); ++ if (saved_host) { ++ apr_table_mergen(r->headers_in, "X-Forwarded-Host", ++ saved_host); + } + + /* Add X-Forwarded-Server: so that upstream knows what the +@@ -3802,10 +3835,27 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + } + } + ++ /* Do we want to strip Proxy-Authorization ? ++ * If we haven't used it, then NO ++ * If we have used it then MAYBE: RFC2616 says we MAY propagate it. ++ * So let's make it configurable by env. ++ */ ++ if (r->user != NULL /* we've authenticated */ ++ && !apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) { ++ apr_table_unset(r->headers_in, "Proxy-Authorization"); ++ } ++ ++ /* for sub-requests, ignore freshness/expiry headers */ ++ if (r->main) { ++ apr_table_unset(r->headers_in, "If-Match"); ++ apr_table_unset(r->headers_in, "If-Modified-Since"); ++ apr_table_unset(r->headers_in, "If-Range"); ++ apr_table_unset(r->headers_in, "If-Unmodified-Since"); ++ apr_table_unset(r->headers_in, "If-None-Match"); ++ } ++ ++ /* run hook to fixup the request we are about to send */ + proxy_run_fixups(r); +- if (ap_proxy_clear_connection(r, r->headers_in) < 0) { +- return HTTP_BAD_REQUEST; +- } + + creds = apr_table_get(r->notes, "proxy-basic-creds"); + if (creds) { +@@ -3817,55 +3867,8 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + headers_in = (const apr_table_entry_t *) headers_in_array->elts; + for (counter = 0; counter < headers_in_array->nelts; counter++) { + if (headers_in[counter].key == NULL +- || headers_in[counter].val == NULL +- +- /* Already sent */ +- || !strcasecmp(headers_in[counter].key, "Host") +- +- /* Clear out hop-by-hop request headers not to send +- * RFC2616 13.5.1 says we should strip these headers +- */ +- || !strcasecmp(headers_in[counter].key, "Keep-Alive") +- || !strcasecmp(headers_in[counter].key, "TE") +- || !strcasecmp(headers_in[counter].key, "Trailer") +- || !strcasecmp(headers_in[counter].key, "Upgrade") +- +- ) { +- continue; +- } +- /* Do we want to strip Proxy-Authorization ? +- * If we haven't used it, then NO +- * If we have used it then MAYBE: RFC2616 says we MAY propagate it. +- * So let's make it configurable by env. +- */ +- if (!strcasecmp(headers_in[counter].key,"Proxy-Authorization")) { +- if (r->user != NULL) { /* we've authenticated */ +- if (!apr_table_get(r->subprocess_env, "Proxy-Chain-Auth")) { +- continue; +- } +- } +- } +- +- /* Skip Transfer-Encoding and Content-Length for now. +- */ +- if (!strcasecmp(headers_in[counter].key, "Transfer-Encoding")) { +- *old_te_val = headers_in[counter].val; +- continue; +- } +- if (!strcasecmp(headers_in[counter].key, "Content-Length")) { +- *old_cl_val = headers_in[counter].val; +- continue; +- } +- +- /* for sub-requests, ignore freshness/expiry headers */ +- if (r->main) { +- if ( !strcasecmp(headers_in[counter].key, "If-Match") +- || !strcasecmp(headers_in[counter].key, "If-Modified-Since") +- || !strcasecmp(headers_in[counter].key, "If-Range") +- || !strcasecmp(headers_in[counter].key, "If-Unmodified-Since") +- || !strcasecmp(headers_in[counter].key, "If-None-Match")) { +- continue; +- } ++ || headers_in[counter].val == NULL) { ++ continue; + } + + buf = apr_pstrcat(p, headers_in[counter].key, ": ", +@@ -3876,11 +3879,9 @@ PROXY_DECLARE(int) ap_proxy_create_hdrbrgd(apr_pool_t *p, + APR_BRIGADE_INSERT_TAIL(header_brigade, e); + } + +- /* Restore the original headers in (see comment above), +- * we won't modify them anymore. +- */ ++cleanup: + r->headers_in = saved_headers_in; +- return OK; ++ return rc; + } + + PROXY_DECLARE(int) ap_proxy_pass_brigade(apr_bucket_alloc_t *bucket_alloc, diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index 866e4cf..c97fc9a 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -13,7 +13,7 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.37 -Release: 47%{?dist}.2 +Release: 51%{?dist} URL: https://httpd.apache.org/ Source0: https://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 Source2: httpd.logrotate @@ -218,6 +218,27 @@ Patch222: httpd-2.4.37-CVE-2021-44224.patch Patch223: httpd-2.4.37-CVE-2022-22720.patch # https://bugzilla.redhat.com/show_bug.cgi?id=1966738 Patch224: httpd-2.4.37-CVE-2020-13950.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2064322 +Patch225: httpd-2.4.37-CVE-2022-22719.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2064320 +Patch226: httpd-2.4.37-CVE-2022-22721.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2065324 +Patch227: httpd-2.4.37-CVE-2022-23943.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2095002 +Patch228: httpd-2.4.37-CVE-2022-28614.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2095006 +Patch229: httpd-2.4.37-CVE-2022-28615.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2095015 +Patch230: httpd-2.4.37-CVE-2022-30522.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2095018 +Patch231: httpd-2.4.37-CVE-2022-30556.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2095020 +Patch232: httpd-2.4.37-CVE-2022-31813.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2095012 +Patch233: httpd-2.4.37-CVE-2022-29404.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2094997 +Patch234: httpd-2.4.37-CVE-2022-26377.patch + License: ASL 2.0 Group: System Environment/Daemons @@ -424,6 +445,16 @@ interface for storing and accessing per-user session data. %patch222 -p1 -b .CVE-2021-44224 %patch223 -p1 -b .CVE-2022-22720 %patch224 -p1 -b .CVE-2020-13950 +%patch225 -p1 -b .CVE-2022-22719 +%patch226 -p1 -b .CVE-2022-22721 +%patch227 -p1 -b .CVE-2022-23943 +%patch228 -p1 -b .CVE-2022-28614 +%patch229 -p1 -b .CVE-2022-28615 +%patch230 -p1 -b .CVE-2022-30522 +%patch231 -p1 -b .CVE-2022-30556 +%patch232 -p1 -b .CVE-2022-31813 +%patch233 -p1 -b .CVE-2022-29404 +%patch234 -p1 -b .CVE-2022-26377 # Patch in the vendor string sed -i '/^#define PLATFORM/s/Unix/%{vstring}/' os/unix/os.h @@ -929,12 +960,36 @@ rm -rf $RPM_BUILD_ROOT %{_rpmconfigdir}/macros.d/macros.httpd %changelog -* Wed Jun 15 2022 Luboš Uhliarik - 2.4.37-47.2 -- Resolves: #2097247 - CVE-2020-13950 httpd:2.4/httpd: mod_proxy NULL pointer +* Mon Jul 25 2022 Luboš Uhliarik - 2.4.37-51 +- Resolves: #2097015 - CVE-2022-28614 httpd:2.4/httpd: out-of-bounds read via + ap_rwrite() +- Resolves: #2097031 - CVE-2022-28615 httpd:2.4/httpd: out-of-bounds read in + ap_strcmp_match() +- Resolves: #2097458 - CVE-2022-30522 httpd:2.4/httpd: mod_sed: DoS + vulnerability +- Resolves: #2097480 - CVE-2022-30556 httpd:2.4/httpd: mod_lua: Information + disclosure with websockets +- Resolves: #2098247 - CVE-2022-31813 httpd:2.4/httpd: mod_proxy: + X-Forwarded-For dropped by hop-by-hop mechanism +- Resolves: #2097451 - CVE-2022-29404 httpd:2.4/httpd: mod_lua: DoS in + r:parsebody +- Resolves: #2096997 - CVE-2022-26377 httpd:2.4/httpd: mod_proxy_ajp: Possible + request smuggling + +* Tue Jun 21 2022 Luboš Uhliarik - 2.4.37-50 +- Resolves: #2065237 - CVE-2022-22719 httpd:2.4/httpd: mod_lua: Use of + uninitialized value of in r:parsebody +- Resolves: #2065267 - CVE-2022-22721 httpd:2.4/httpd: core: Possible buffer + overflow with very large or unlimited LimitXMLRequestBody +- Resolves: #2065324 - CVE-2022-23943 httpd:2.4/httpd: mod_sed: Read/write + beyond bounds + +* Fri Jun 10 2022 Luboš Uhliarik - 2.4.37-49 +- Resolves: #2090848 - CVE-2020-13950 httpd:2.4/httpd: mod_proxy NULL pointer dereference -* Mon Mar 21 2022 Luboš Uhliarik - 2.4.37-47.1 -- Resolves: #2065248 - CVE-2022-22720 httpd:2.4/httpd: HTTP request smuggling +* Mon Mar 21 2022 Luboš Uhliarik - 2.4.37-48 +- Resolves: #2065249 - CVE-2022-22720 httpd:2.4/httpd: HTTP request smuggling vulnerability in Apache HTTP Server 2.4.52 and earlier * Thu Jan 20 2022 Luboš Uhliarik - 2.4.37-47 From 7e7354bff1cc4a49bda647b11f6853eb6f5dcb65 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 21 Feb 2023 08:50:14 +0000 Subject: [PATCH 2/5] import httpd-2.4.37-51.module+el8.7.0+18026+7b169787.1 --- SOURCES/httpd-2.4.37-CVE-2006-20001.patch | 20 ++++ SOURCES/httpd-2.4.37-CVE-2022-36760.patch | 23 ++++ SOURCES/httpd-2.4.37-CVE-2022-37436.patch | 129 ++++++++++++++++++++++ SOURCES/httpd-init.service | 1 + SOURCES/httpd-ssl-gencerts | 1 + SPECS/httpd.spec | 19 +++- 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 SOURCES/httpd-2.4.37-CVE-2006-20001.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-36760.patch create mode 100644 SOURCES/httpd-2.4.37-CVE-2022-37436.patch diff --git a/SOURCES/httpd-2.4.37-CVE-2006-20001.patch b/SOURCES/httpd-2.4.37-CVE-2006-20001.patch new file mode 100644 index 0000000..26c9363 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2006-20001.patch @@ -0,0 +1,20 @@ +diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c +index 1ae5914027c..3f7822fc931 100644 +--- a/modules/dav/main/util.c ++++ b/modules/dav/main/util.c +@@ -801,8 +801,14 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih) + "for the same state."); + } + condition = DAV_IF_COND_NOT; ++ list += 2; ++ } ++ else { ++ return dav_new_error(r->pool, HTTP_BAD_REQUEST, ++ DAV_ERR_IF_UNK_CHAR, 0, ++ "Invalid \"If:\" header: " ++ "Unexpected character in List"); + } +- list += 2; + break; + + case ' ': diff --git a/SOURCES/httpd-2.4.37-CVE-2022-36760.patch b/SOURCES/httpd-2.4.37-CVE-2022-36760.patch new file mode 100644 index 0000000..5a34c30 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-36760.patch @@ -0,0 +1,23 @@ +From 5efc9507c487c37dfe2a279a4a0335cad701cd5f Mon Sep 17 00:00:00 2001 +From: Eric Covener +Date: Tue, 10 Jan 2023 13:19:07 +0000 +Subject: [PATCH] cleanup on error + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1906540 13f79535-47bb-0310-9956-ffa450edef68 +--- + modules/proxy/mod_proxy_ajp.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c +index 9cd7adbcbbf..07f37392d88 100644 +--- a/modules/proxy/mod_proxy_ajp.c ++++ b/modules/proxy/mod_proxy_ajp.c +@@ -255,6 +255,8 @@ static int ap_proxy_ajp_request(apr_pool_t *p, request_rec *r, + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10396) + "%s Transfer-Encoding is not supported", + tenc); ++ /* We had a failure: Close connection to backend */ ++ conn->close = 1; + return HTTP_INTERNAL_SERVER_ERROR; + } + } else { diff --git a/SOURCES/httpd-2.4.37-CVE-2022-37436.patch b/SOURCES/httpd-2.4.37-CVE-2022-37436.patch new file mode 100644 index 0000000..26311e8 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2022-37436.patch @@ -0,0 +1,129 @@ +From 8b6d55f6a047acf62675e32606b037f5eea8ccc7 Mon Sep 17 00:00:00 2001 +From: Eric Covener +Date: Tue, 10 Jan 2023 13:20:09 +0000 +Subject: [PATCH] Merge r1906539 from trunk: + +fail on bad header + +Submitted By: covener +Reviewed By: covener, rpluem, gbechis + + +git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1906541 13f79535-47bb-0310-9956-ffa450edef68 +--- + modules/proxy/mod_proxy_http.c | 46 ++++++++++++++++++++-------------- + server/protocol.c | 2 ++ + 2 files changed, 29 insertions(+), 19 deletions(-) + +diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c +index d74ae054ac9..ec4e7fb06b5 100644 +--- a/modules/proxy/mod_proxy_http.c ++++ b/modules/proxy/mod_proxy_http.c +@@ -788,7 +788,7 @@ static void process_proxy_header(request_rec *r, proxy_dir_conf *c, + * any sense at all, since we depend on buffer still containing + * what was read by ap_getline() upon return. + */ +-static void ap_proxy_read_headers(request_rec *r, request_rec *rr, ++static apr_status_t ap_proxy_read_headers(request_rec *r, request_rec *rr, + char *buffer, int size, + conn_rec *c, int *pread_len) + { +@@ -820,19 +820,26 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr, + rc = ap_proxygetline(tmp_bb, buffer, size, rr, + AP_GETLINE_FOLD | AP_GETLINE_NOSPC_EOL, &len); + +- if (len <= 0) +- break; + +- if (APR_STATUS_IS_ENOSPC(rc)) { +- /* The header could not fit in the provided buffer, warn. +- * XXX: falls through with the truncated header, 5xx instead? +- */ +- int trunc = (len > 128 ? 128 : len) / 2; +- ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10124) +- "header size is over the limit allowed by " +- "ResponseFieldSize (%d bytes). " +- "Bad response header: '%.*s[...]%s'", +- size, trunc, buffer, buffer + len - trunc); ++ if (rc != APR_SUCCESS) { ++ if (APR_STATUS_IS_ENOSPC(rc)) { ++ int trunc = (len > 128 ? 128 : len) / 2; ++ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10124) ++ "header size is over the limit allowed by " ++ "ResponseFieldSize (%d bytes). " ++ "Bad response header: '%.*s[...]%s'", ++ size, trunc, buffer, buffer + len - trunc); ++ } ++ else { ++ ap_log_rerror(APLOG_MARK, APLOG_WARNING, rc, r, APLOGNO(10404) ++ "Error reading headers from backend"); ++ } ++ r->headers_out = NULL; ++ return rc; ++ } ++ ++ if (len <= 0) { ++ break; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, "%s", buffer); +@@ -855,7 +862,7 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr, + if (psc->badopt == bad_error) { + /* Nope, it wasn't even an extra HTTP header. Give up. */ + r->headers_out = NULL; +- return; ++ return APR_EINVAL; + } + else if (psc->badopt == bad_body) { + /* if we've already started loading headers_out, then +@@ -869,13 +876,13 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr, + "in headers returned by %s (%s)", + r->uri, r->method); + *pread_len = len; +- return; ++ return APR_SUCCESS; + } + else { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01099) + "No HTTP headers returned by %s (%s)", + r->uri, r->method); +- return; ++ return APR_SUCCESS; + } + } + } +@@ -905,6 +912,7 @@ static void ap_proxy_read_headers(request_rec *r, request_rec *rr, + process_proxy_header(r, dconf, buffer, value); + saw_headers = 1; + } ++ return APR_SUCCESS; + } + + +@@ -1218,10 +1226,10 @@ int ap_proxy_http_process_response(proxy_http_req_t *req) + "Set-Cookie", NULL); + + /* shove the headers direct into r->headers_out */ +- ap_proxy_read_headers(r, backend->r, buffer, response_field_size, +- origin, &pread_len); ++ rc = ap_proxy_read_headers(r, backend->r, buffer, response_field_size, ++ origin, &pread_len); + +- if (r->headers_out == NULL) { ++ if (rc != APR_SUCCESS || r->headers_out == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01106) + "bad HTTP/%d.%d header returned by %s (%s)", + major, minor, r->uri, r->method); +diff --git a/server/protocol.c b/server/protocol.c +index 7adc7f75c10..6f9540ad1de 100644 +--- a/server/protocol.c ++++ b/server/protocol.c +@@ -508,6 +508,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, + /* PR#43039: We shouldn't accept NULL bytes within the line */ + bytes_handled = strlen(*s); + if (bytes_handled < *read) { ++ ap_log_data(APLOG_MARK, APLOG_DEBUG, ap_server_conf, ++ "NULL bytes in header", *s, *read, 0); + *read = bytes_handled; + if (rv == APR_SUCCESS) { + rv = APR_EINVAL; diff --git a/SOURCES/httpd-init.service b/SOURCES/httpd-init.service index 3074778..704c314 100644 --- a/SOURCES/httpd-init.service +++ b/SOURCES/httpd-init.service @@ -8,5 +8,6 @@ ConditionPathExists=|!/etc/pki/tls/private/localhost.key [Service] Type=oneshot RemainAfterExit=no +PrivateTmp=true ExecStart=/usr/libexec/httpd-ssl-gencerts diff --git a/SOURCES/httpd-ssl-gencerts b/SOURCES/httpd-ssl-gencerts index 350f5b5..5c271f7 100755 --- a/SOURCES/httpd-ssl-gencerts +++ b/SOURCES/httpd-ssl-gencerts @@ -33,6 +33,7 @@ sscg -q \ --cert-file /etc/pki/tls/certs/localhost.crt \ --cert-key-file /etc/pki/tls/private/localhost.key \ --ca-file /etc/pki/tls/certs/localhost.crt \ + --dhparams-file /tmp/dhparams.pem \ --lifetime 365 \ --hostname $FQDN \ --email root@$FQDN diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index c97fc9a..667199a 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -13,7 +13,7 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.37 -Release: 51%{?dist} +Release: 51%{?dist}.1 URL: https://httpd.apache.org/ Source0: https://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 Source2: httpd.logrotate @@ -238,6 +238,12 @@ Patch232: httpd-2.4.37-CVE-2022-31813.patch Patch233: httpd-2.4.37-CVE-2022-29404.patch # https://bugzilla.redhat.com/show_bug.cgi?id=2094997 Patch234: httpd-2.4.37-CVE-2022-26377.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2161773 +Patch235: httpd-2.4.37-CVE-2022-37436.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2161774 +Patch236: httpd-2.4.37-CVE-2006-20001.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2161777 +Patch237: httpd-2.4.37-CVE-2022-36760.patch License: ASL 2.0 @@ -455,6 +461,9 @@ interface for storing and accessing per-user session data. %patch232 -p1 -b .CVE-2022-31813 %patch233 -p1 -b .CVE-2022-29404 %patch234 -p1 -b .CVE-2022-26377 +%patch235 -p1 -b .CVE-2022-37436 +%patch236 -p1 -b .CVE-2006-20001 +%patch237 -p1 -b .CVE-2022-36760 # Patch in the vendor string sed -i '/^#define PLATFORM/s/Unix/%{vstring}/' os/unix/os.h @@ -960,6 +969,14 @@ rm -rf $RPM_BUILD_ROOT %{_rpmconfigdir}/macros.d/macros.httpd %changelog +* Tue Jan 31 2023 Luboš Uhliarik - 2.4.37-51.1 +- Resolves: #2165967 - prevent sscg creating /dhparams.pem +- Resolves: #2165976 - CVE-2006-20001 httpd: mod_dav: out-of-bounds read/write + of zero byte +- Resolves: #2165977 - CVE-2022-37436 httpd: mod_proxy: HTTP response splitting +- Resolves: #2165978 - CVE-2022-36760 httpd: mod_proxy_ajp: Possible request + smuggling + * Mon Jul 25 2022 Luboš Uhliarik - 2.4.37-51 - Resolves: #2097015 - CVE-2022-28614 httpd:2.4/httpd: out-of-bounds read via ap_rwrite() From 203fa9fde18435e7a42260fa2afddfe1ea19a699 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Thu, 6 Apr 2023 16:02:42 +0000 Subject: [PATCH 3/5] import httpd-2.4.37-51.module+el8.7.0+18499+2e106f0b.5 --- SOURCES/httpd-2.4.37-CVE-2023-25690.patch | 296 ++++++++++++++++++++++ SPECS/httpd.spec | 10 +- 2 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 SOURCES/httpd-2.4.37-CVE-2023-25690.patch diff --git a/SOURCES/httpd-2.4.37-CVE-2023-25690.patch b/SOURCES/httpd-2.4.37-CVE-2023-25690.patch new file mode 100644 index 0000000..878d6e6 --- /dev/null +++ b/SOURCES/httpd-2.4.37-CVE-2023-25690.patch @@ -0,0 +1,296 @@ +diff --git a/docs/manual/rewrite/flags.html.en b/docs/manual/rewrite/flags.html.en +index 80d0759..9673094 100644 +--- a/docs/manual/rewrite/flags.html.en ++++ b/docs/manual/rewrite/flags.html.en +@@ -85,10 +85,6 @@ of how you might use them.

+

B (escape backreferences)

+

The [B] flag instructs RewriteRule to escape non-alphanumeric + characters before applying the transformation.

+-

In 2.4.26 and later, you can limit the escaping to specific characters +-in backreferences by listing them: [B=#?;]. Note: The space +-character can be used in the list of characters to escape, but it cannot be +-the last character in the list.

+ +

mod_rewrite has to unescape URLs before mapping them, + so backreferences are unescaped at the time they are applied. +@@ -120,6 +116,16 @@ when the backend may break if presented with an unescaped URL.

+ +

An alternative to this flag is using a RewriteCond to capture against %{THE_REQUEST} which will capture + strings in the encoded form.

++ ++

In 2.4.26 and later, you can limit the escaping to specific characters ++in backreferences by listing them: [B=#?;]. Note: The space ++character can be used in the list of characters to escape, but you must quote ++the entire third argument of RewriteRule ++and the space must not be the last character in the list.

++ ++
# Escape spaces and question marks.
++RewriteRule "^search/(.*)$" "/search.php?term=$1" "[B= ?]"
++ +
top
+
+

BNP|backrefnoplus (don't escape space to +)

+diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c +index 38dbb24..c3937ad 100644 +--- a/modules/mappers/mod_rewrite.c ++++ b/modules/mappers/mod_rewrite.c +@@ -168,6 +168,7 @@ static const char* really_last_key = "rewrite_really_last"; + #define RULEFLAG_END (1<<17) + #define RULEFLAG_ESCAPENOPLUS (1<<18) + #define RULEFLAG_QSLAST (1<<19) ++#define RULEFLAG_QSNONE (1<<20) /* programattic only */ + + /* return code of the rewrite rule + * the result may be escaped - or not +@@ -761,15 +762,24 @@ static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme) + ap_escape_uri(p, cp), NULL); + } + ++ + /* + * split out a QUERY_STRING part from + * the current URI string + */ +-static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard, +- int qslast) ++static void splitout_queryargs(request_rec *r, int flags) + { + char *q; + int split; ++ int qsappend = flags & RULEFLAG_QSAPPEND; ++ int qsdiscard = flags & RULEFLAG_QSDISCARD; ++ int qslast = flags & RULEFLAG_QSLAST; ++ ++ if (flags & RULEFLAG_QSNONE) { ++ rewritelog((r, 2, NULL, "discarding query string, no parse from substitution")); ++ r->args = NULL; ++ return; ++ } + + /* don't touch, unless it's a scheme for which a query string makes sense. + * See RFC 1738 and RFC 2368. +@@ -794,7 +804,7 @@ static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard, + olduri = apr_pstrdup(r->pool, r->filename); + *q++ = '\0'; + if (qsappend) { +- if (*q) { ++ if (*q) { + r->args = apr_pstrcat(r->pool, q, "&" , r->args, NULL); + } + } +@@ -802,9 +812,9 @@ static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard, + r->args = apr_pstrdup(r->pool, q); + } + +- if (r->args) { ++ if (r->args) { + len = strlen(r->args); +- ++ + if (!len) { + r->args = NULL; + } +@@ -2733,7 +2743,7 @@ static apr_status_t rewritelock_remove(void *data) + * XXX: what an inclined parser. Seems we have to leave it so + * for backwards compat. *sigh* + */ +-static int parseargline(char *str, char **a1, char **a2, char **a3) ++static int parseargline(char *str, char **a1, char **a2, char **a2_end, char **a3) + { + char quote; + +@@ -2784,8 +2794,10 @@ static int parseargline(char *str, char **a1, char **a2, char **a3) + + if (!*str) { + *a3 = NULL; /* 3rd argument is optional */ ++ *a2_end = str; + return 0; + } ++ *a2_end = str; + *str++ = '\0'; + + while (apr_isspace(*str)) { +@@ -3323,7 +3335,7 @@ static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf, + rewrite_server_conf *sconf; + rewritecond_entry *newcond; + ap_regex_t *regexp; +- char *a1 = NULL, *a2 = NULL, *a3 = NULL; ++ char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL; + const char *err; + + sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); +@@ -3341,7 +3353,7 @@ static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf, + * of the argument line. So we can use a1 .. a3 without + * copying them again. + */ +- if (parseargline(str, &a1, &a2, &a3)) { ++ if (parseargline(str, &a1, &a2, &a2_end, &a3)) { + return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str, + "'", NULL); + } +@@ -3749,7 +3761,7 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, + rewrite_server_conf *sconf; + rewriterule_entry *newrule; + ap_regex_t *regexp; +- char *a1 = NULL, *a2 = NULL, *a3 = NULL; ++ char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL; + const char *err; + + sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); +@@ -3763,7 +3775,7 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, + } + + /* parse the argument line ourself */ +- if (parseargline(str, &a1, &a2, &a3)) { ++ if (parseargline(str, &a1, &a2, &a2_end, &a3)) { + return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str, + "'", NULL); + } +@@ -3810,6 +3822,16 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, + newrule->flags |= RULEFLAG_NOSUB; + } + ++ if (*(a2_end-1) == '?') { ++ /* a literal ? at the end of the unsubstituted rewrite rule */ ++ newrule->flags |= RULEFLAG_QSNONE; ++ } ++ else if (newrule->flags & RULEFLAG_QSDISCARD) { ++ if (NULL == ap_strchr(newrule->output, '?')) { ++ newrule->flags |= RULEFLAG_QSNONE; ++ } ++ } ++ + /* now, if the server or per-dir config holds an + * array of RewriteCond entries, we take it for us + * and clear the array +@@ -4215,9 +4237,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) + r->path_info = NULL; + } + +- splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND, +- p->flags & RULEFLAG_QSDISCARD, +- p->flags & RULEFLAG_QSLAST); ++ splitout_queryargs(r, p->flags); + + /* Add the previously stripped per-directory location prefix, unless + * (1) it's an absolute URL path and +@@ -4699,6 +4719,17 @@ static int hook_uri2file(request_rec *r) + unsigned skip; + apr_size_t flen; + ++ if (r->args && *(ap_scan_vchar_obstext(r->args))) { ++ /* ++ * We have a raw control character or a ' ' in r->args. ++ * Correct encoding was missed. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10410) ++ "Rewritten query string contains control " ++ "characters or spaces"); ++ return HTTP_FORBIDDEN; ++ } ++ + if (ACTION_STATUS == rulestatus) { + int n = r->status; + +@@ -4983,6 +5014,17 @@ static int hook_fixup(request_rec *r) + if (rulestatus) { + unsigned skip; + ++ if (r->args && *(ap_scan_vchar_obstext(r->args))) { ++ /* ++ * We have a raw control character or a ' ' in r->args. ++ * Correct encoding was missed. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10411) ++ "Rewritten query string contains control " ++ "characters or spaces"); ++ return HTTP_FORBIDDEN; ++ } ++ + if (ACTION_STATUS == rulestatus) { + int n = r->status; + +diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c +index 058b03f..a529c02 100644 +--- a/modules/proxy/mod_proxy_ajp.c ++++ b/modules/proxy/mod_proxy_ajp.c +@@ -69,6 +69,16 @@ static int proxy_ajp_canon(request_rec *r, char *url) + path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, + r->proxyreq); + search = r->args; ++ if (search && *(ap_scan_vchar_obstext(search))) { ++ /* ++ * We have a raw control character or a ' ' in r->args. ++ * Correct encoding was missed. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10406) ++ "To be forwarded query string contains control " ++ "characters or spaces"); ++ return HTTP_FORBIDDEN; ++ } + } + if (path == NULL) + return HTTP_BAD_REQUEST; +diff --git a/modules/proxy/mod_proxy_balancer.c b/modules/proxy/mod_proxy_balancer.c +index 3a28038..c599e1a 100644 +--- a/modules/proxy/mod_proxy_balancer.c ++++ b/modules/proxy/mod_proxy_balancer.c +@@ -106,6 +106,16 @@ static int proxy_balancer_canon(request_rec *r, char *url) + path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, + r->proxyreq); + search = r->args; ++ if (search && *(ap_scan_vchar_obstext(search))) { ++ /* ++ * We have a raw control character or a ' ' in r->args. ++ * Correct encoding was missed. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10407) ++ "To be forwarded query string contains control " ++ "characters or spaces"); ++ return HTTP_FORBIDDEN; ++ } + } + if (path == NULL) + return HTTP_BAD_REQUEST; +diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c +index c1c591a..58a8c86 100644 +--- a/modules/proxy/mod_proxy_http.c ++++ b/modules/proxy/mod_proxy_http.c +@@ -90,6 +90,16 @@ static int proxy_http_canon(request_rec *r, char *url) + path = ap_proxy_canonenc(r->pool, url, strlen(url), + enc_path, 0, r->proxyreq); + search = r->args; ++ if (search && *(ap_scan_vchar_obstext(search))) { ++ /* ++ * We have a raw control character or a ' ' in r->args. ++ * Correct encoding was missed. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10408) ++ "To be forwarded query string contains control " ++ "characters or spaces"); ++ return HTTP_FORBIDDEN; ++ } + } + break; + case PROXYREQ_PROXY: +diff --git a/modules/proxy/mod_proxy_wstunnel.c b/modules/proxy/mod_proxy_wstunnel.c +index e005a94..f5e27d9 100644 +--- a/modules/proxy/mod_proxy_wstunnel.c ++++ b/modules/proxy/mod_proxy_wstunnel.c +@@ -77,6 +77,16 @@ static int proxy_wstunnel_canon(request_rec *r, char *url) + path = ap_proxy_canonenc(r->pool, url, strlen(url), enc_path, 0, + r->proxyreq); + search = r->args; ++ if (search && *(ap_scan_vchar_obstext(search))) { ++ /* ++ * We have a raw control character or a ' ' in r->args. ++ * Correct encoding was missed. ++ */ ++ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10409) ++ "To be forwarded query string contains control " ++ "characters or spaces"); ++ return HTTP_FORBIDDEN; ++ } + } + if (path == NULL) + return HTTP_BAD_REQUEST; diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index 667199a..059349a 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -13,7 +13,7 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.37 -Release: 51%{?dist}.1 +Release: 51%{?dist}.5 URL: https://httpd.apache.org/ Source0: https://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 Source2: httpd.logrotate @@ -244,7 +244,8 @@ Patch235: httpd-2.4.37-CVE-2022-37436.patch Patch236: httpd-2.4.37-CVE-2006-20001.patch # https://bugzilla.redhat.com/show_bug.cgi?id=2161777 Patch237: httpd-2.4.37-CVE-2022-36760.patch - +# https://bugzilla.redhat.com/show_bug.cgi?id=2176209 +Patch238: httpd-2.4.37-CVE-2023-25690.patch License: ASL 2.0 Group: System Environment/Daemons @@ -464,6 +465,7 @@ interface for storing and accessing per-user session data. %patch235 -p1 -b .CVE-2022-37436 %patch236 -p1 -b .CVE-2006-20001 %patch237 -p1 -b .CVE-2022-36760 +%patch238 -p1 -b .CVE-2023-25690 # Patch in the vendor string sed -i '/^#define PLATFORM/s/Unix/%{vstring}/' os/unix/os.h @@ -969,6 +971,10 @@ rm -rf $RPM_BUILD_ROOT %{_rpmconfigdir}/macros.d/macros.httpd %changelog +* Sat Mar 18 2023 Luboš Uhliarik - 2.4.37-51.5 +- Resolves: #2177747 - CVE-2023-25690 httpd:2.4/httpd: HTTP request splitting + with mod_rewrite and mod_proxy + * Tue Jan 31 2023 Luboš Uhliarik - 2.4.37-51.1 - Resolves: #2165967 - prevent sscg creating /dhparams.pem - Resolves: #2165976 - CVE-2006-20001 httpd: mod_dav: out-of-bounds read/write From 0a9df3405ec91ffce76c6f330939d41ad80176a0 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 16 May 2023 06:05:42 +0000 Subject: [PATCH 4/5] import httpd-2.4.37-56.module+el8.8.0+18556+a66138c1.4 --- SOURCES/httpd-2.4.37-add-SNI-support.patch | 92 ++++++++++++++++++++++ SPECS/httpd.spec | 36 ++++++--- 2 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 SOURCES/httpd-2.4.37-add-SNI-support.patch diff --git a/SOURCES/httpd-2.4.37-add-SNI-support.patch b/SOURCES/httpd-2.4.37-add-SNI-support.patch new file mode 100644 index 0000000..8b8d9ad --- /dev/null +++ b/SOURCES/httpd-2.4.37-add-SNI-support.patch @@ -0,0 +1,92 @@ +commit 4c0e27d7bfbf46f14dfbd5d888e56c64ad8c8de5 +Author: Tomas Korbar +Date: Mon Sep 19 13:22:27 2022 +0200 + + Backport refactor of SNI support to httpd-2.4.37 + +diff --git a/modules/http2/mod_proxy_http2.c b/modules/http2/mod_proxy_http2.c +index a7e0dcd..31ccd32 100644 +--- a/modules/http2/mod_proxy_http2.c ++++ b/modules/http2/mod_proxy_http2.c +@@ -591,16 +591,6 @@ run_connect: + } + + if (!ctx->p_conn->data) { +- /* New conection: set a note on the connection what CN is +- * requested and what protocol we want */ +- if (ctx->p_conn->ssl_hostname) { +- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, ctx->owner, +- "set SNI to %s for (%s)", +- ctx->p_conn->ssl_hostname, +- ctx->p_conn->hostname); +- apr_table_setn(ctx->p_conn->connection->notes, +- "proxy-request-hostname", ctx->p_conn->ssl_hostname); +- } + if (ctx->is_ssl) { + apr_table_setn(ctx->p_conn->connection->notes, + "proxy-request-alpn-protos", "h2"); +diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c +index 1b7bb81..c1c591a 100644 +--- a/modules/proxy/mod_proxy_http.c ++++ b/modules/proxy/mod_proxy_http.c +@@ -2111,19 +2111,6 @@ static int proxy_http_handler(request_rec *r, proxy_worker *worker, + req->origin->keepalive = AP_CONN_CLOSE; + } + +- /* +- * On SSL connections set a note on the connection what CN is +- * requested, such that mod_ssl can check if it is requested to do +- * so. +- * +- * https://github.com/apache/httpd/commit/7d272e2628b4ae05f68cdc74b070707250896a34 +- */ +- if (backend->ssl_hostname) { +- apr_table_setn(backend->connection->notes, +- "proxy-request-hostname", +- backend->ssl_hostname); +- } +- + /* Step Four: Send the Request + * On the off-chance that we forced a 100-Continue as a + * kinda HTTP ping test, allow for retries +diff --git a/modules/proxy/proxy_util.c b/modules/proxy/proxy_util.c +index ec9a414..805820d 100644 +--- a/modules/proxy/proxy_util.c ++++ b/modules/proxy/proxy_util.c +@@ -3261,6 +3261,16 @@ static int proxy_connection_create(const char *proxy_function, + backend_addr, conn->hostname); + return HTTP_INTERNAL_SERVER_ERROR; + } ++ if (conn->ssl_hostname) { ++ /* Set a note on the connection about what CN is requested, ++ * such that mod_ssl can check if it is requested to do so. ++ */ ++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, conn->connection, ++ "%s: set SNI to %s for (%s)", proxy_function, ++ conn->ssl_hostname, conn->hostname); ++ apr_table_setn(conn->connection->notes, "proxy-request-hostname", ++ conn->ssl_hostname); ++ } + } + else { + /* TODO: See if this will break FTP */ +diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c +index 4e3875a..9b4280c 100644 +--- a/modules/ssl/ssl_engine_io.c ++++ b/modules/ssl/ssl_engine_io.c +@@ -1273,7 +1273,6 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) + ((dc->proxy->ssl_check_peer_cn != FALSE) || + (dc->proxy->ssl_check_peer_name == TRUE)) && + hostname_note) { +- apr_table_unset(c->notes, "proxy-request-hostname"); + if (!cert + || modssl_X509_match_name(c->pool, cert, hostname_note, + TRUE, server) == FALSE) { +@@ -1290,7 +1289,6 @@ static apr_status_t ssl_io_filter_handshake(ssl_filter_ctx_t *filter_ctx) + + hostname = ssl_var_lookup(NULL, server, c, NULL, + "SSL_CLIENT_S_DN_CN"); +- apr_table_unset(c->notes, "proxy-request-hostname"); + + /* Do string match or simplest wildcard match if that + * fails. */ diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index 059349a..8493acc 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -13,7 +13,7 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.37 -Release: 51%{?dist}.5 +Release: 56%{?dist}.4 URL: https://httpd.apache.org/ Source0: https://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 Source2: httpd.logrotate @@ -163,6 +163,8 @@ Patch88: httpd-2.4.37-r1845768+.patch Patch89: httpd-2.4.37-r1862410.patch # https://bugzilla.redhat.com/show_bug.cgi?id=1984828 Patch90: httpd-2.4.37-hcheck-mem-issues.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=2017543 +Patch91: httpd-2.4.37-add-SNI-support.patch # Security fixes Patch200: httpd-2.4.37-r1851471.patch @@ -263,7 +265,7 @@ Provides: mod_dav = %{version}-%{release}, httpd-suexec = %{version}-%{release} Provides: httpd-mmn = %{mmn}, httpd-mmn = %{mmnisa} Requires: httpd-tools = %{version}-%{release} Requires: httpd-filesystem = %{version}-%{release} -Requires: mod_http2 +Requires: mod_http2 >= 1.15.7-5 Requires(pre): httpd-filesystem Requires(preun): systemd-units Requires(postun): systemd-units @@ -327,7 +329,7 @@ Epoch: 1 BuildRequires: openssl-devel Requires(pre): httpd-filesystem Requires: httpd = 0:%{version}-%{release}, httpd-mmn = %{mmnisa} -Requires: sscg >= 2.2.0 +Requires: sscg >= 3.0.0-7, /usr/bin/hostname Obsoletes: stronghold-mod_ssl # Require an OpenSSL which supports PROFILE=SYSTEM Conflicts: openssl-libs < 1:1.0.1h-4 @@ -426,6 +428,7 @@ interface for storing and accessing per-user session data. %patch88 -p1 -b .r1845768+ %patch89 -p1 -b .r1862410 %patch90 -p1 -b .hcheck-mem-issues +%patch91 -p1 -b .SNI %patch200 -p1 -b .r1851471 %patch201 -p1 -b .CVE-2019-0211 @@ -971,18 +974,31 @@ rm -rf $RPM_BUILD_ROOT %{_rpmconfigdir}/macros.d/macros.httpd %changelog -* Sat Mar 18 2023 Luboš Uhliarik - 2.4.37-51.5 -- Resolves: #2177747 - CVE-2023-25690 httpd:2.4/httpd: HTTP request splitting +* Sat Mar 18 2023 Luboš Uhliarik - 2.4.37-56.4 +- Resolves: #2177748 - CVE-2023-25690 httpd:2.4/httpd: HTTP request splitting with mod_rewrite and mod_proxy -* Tue Jan 31 2023 Luboš Uhliarik - 2.4.37-51.1 -- Resolves: #2165967 - prevent sscg creating /dhparams.pem -- Resolves: #2165976 - CVE-2006-20001 httpd: mod_dav: out-of-bounds read/write +* Tue Jan 31 2023 Luboš Uhliarik - 2.4.37-56 +- Resolves: #2162499 - CVE-2006-20001 httpd: mod_dav: out-of-bounds read/write of zero byte -- Resolves: #2165977 - CVE-2022-37436 httpd: mod_proxy: HTTP response splitting -- Resolves: #2165978 - CVE-2022-36760 httpd: mod_proxy_ajp: Possible request +- Resolves: #2162485 - CVE-2022-37436 httpd: mod_proxy: HTTP response splitting +- Resolves: #2162509 - CVE-2022-36760 httpd: mod_proxy_ajp: Possible request smuggling +* Thu Jan 26 2023 Luboš Uhliarik - 2.4.37-55 +- Resolves: #2155961 - prevent sscg creating /dhparams.pem + +* Thu Dec 08 2022 Luboš Uhliarik - 2.4.37-54 +- Resolves: #2095650 - Dependency from mod_http2 on httpd broken + +* Wed Nov 09 2022 Luboš Uhliarik - 2.4.37-53 +- Resolves: #2050888 - httpd with SSL fails to start unless hostname command + was installed + +* Mon Sep 19 2022 Tomas Korbar - 2.4.37-52 +- Add the SNI support in mod_proxy_wstunnel module for Apache httpd +- Resolves: rhbz#2017543 + * Mon Jul 25 2022 Luboš Uhliarik - 2.4.37-51 - Resolves: #2097015 - CVE-2022-28614 httpd:2.4/httpd: out-of-bounds read via ap_rwrite() From 1007e8ea16f00966f53cedba0927af06eada61f6 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Wed, 17 May 2023 13:16:18 +0000 Subject: [PATCH 5/5] import httpd-2.4.37-56.module+el8.8.0+18758+b3a9c8da.6 --- SOURCES/httpd-2.4.37-CVE-2023-25690.patch | 487 ++++++++++++++++++++-- SPECS/httpd.spec | 5 +- 2 files changed, 467 insertions(+), 25 deletions(-) diff --git a/SOURCES/httpd-2.4.37-CVE-2023-25690.patch b/SOURCES/httpd-2.4.37-CVE-2023-25690.patch index 878d6e6..5b24a06 100644 --- a/SOURCES/httpd-2.4.37-CVE-2023-25690.patch +++ b/SOURCES/httpd-2.4.37-CVE-2023-25690.patch @@ -1,5 +1,28 @@ +diff --git a/docs/manual/mod/mod_rewrite.html.en b/docs/manual/mod/mod_rewrite.html.en +index 815ec72..2b8ed35 100644 +--- a/docs/manual/mod/mod_rewrite.html.en ++++ b/docs/manual/mod/mod_rewrite.html.en +@@ -1265,7 +1265,17 @@ cannot use $N in the substitution string! + B + Escape non-alphanumeric characters in backreferences before + applying the transformation. details ... +- ++ ++ ++ BCTLS ++ Like [B], but only escape control characters and spaces. ++ details ... ++ ++ ++ BNE ++ Characters of [B] or [BCTLS] which should not be escaped. ++ details ... ++ + + backrefnoplus|BNP + If backreferences are being escaped, spaces should be escaped to diff --git a/docs/manual/rewrite/flags.html.en b/docs/manual/rewrite/flags.html.en -index 80d0759..9673094 100644 +index 80d0759..21edfe7 100644 --- a/docs/manual/rewrite/flags.html.en +++ b/docs/manual/rewrite/flags.html.en @@ -85,10 +85,6 @@ of how you might use them.

@@ -13,7 +36,7 @@ index 80d0759..9673094 100644

mod_rewrite has to unescape URLs before mapping them, so backreferences are unescaped at the time they are applied. -@@ -120,6 +116,16 @@ when the backend may break if presented with an unescaped URL.

+@@ -120,6 +116,20 @@ when the backend may break if presented with an unescaped URL.

An alternative to this flag is using a RewriteCond to capture against %{THE_REQUEST} which will capture strings in the encoded form.

@@ -24,25 +47,266 @@ index 80d0759..9673094 100644 +the entire third argument of RewriteRule +and the space must not be the last character in the list.

+ -+
# Escape spaces and question marks.
++
# Escape spaces and question marks.  The quotes around the final argument
++# are required when a space is included.
 +RewriteRule "^search/(.*)$" "/search.php?term=$1" "[B= ?]"
++ ++

To limit the characters escaped this way, see #flag_bne ++and #flag_bctls

+
top

BNP|backrefnoplus (don't escape space to +)

+@@ -127,8 +137,40 @@ strings in the encoded form.

+ in a backreference to %20 rather than '+'. Useful when the backreference + will be used in the path component rather than the query string.

+ ++
++# Escape spaces to %20 in the path instead of + as used in form submission via
++# the query string
++RewriteRule "^search/(.*)$" "/search.php/$1" "[B,BNP]"
++
++ +

This flag is available in version 2.4.26 and later.

+ ++
top
++
++

BCTLS

++

The [BCTLS] flag is similar to the [B] flag, but only escapes ++control characters and the space character. This is the same set of ++characters rejected when they are copied into the query string unencoded. ++

++ ++
++# Escape control characters and spaces
++RewriteRule "^search/(.*)$" "/search.php/$1" "[BCTLS]"
++
++ ++
top
++
++

BNE

++

The list of characters in [BNE=...] are treated as exclusions to the ++characters of the [B] or [BCTLS] flags. The listed characters will not be ++escaped. ++

++ ++
++# Escape the default characters, but leave /
++RewriteRule "^search/(.*)$" "/search.php?term=$1" "[B,BNE=/]"
++
++ +
top
+
+

C|chain

+@@ -204,7 +246,7 @@ browsers that support this feature. +

Consider this example:

+ +
RewriteEngine On
+-RewriteRule "^/index\.html" "-" [CO=frontdoor:yes:.example.com:1440:/]
++RewriteRule "^/index\.html" "-" [CO=frontdoor:yes:.example.com:1440:/] + + +

In the example give, the rule doesn't rewrite the request. +@@ -410,8 +452,8 @@ argument to index.php, however, the index.php, the RewriteRule will be skipped.

+ +
RewriteBase "/"
+-RewriteCond "%{REQUEST_URI}" "!=/index.php"
+-RewriteRule "^(.*)" "/index.php?req=$1" [L,PT]
++RewriteCond "%{REQUEST_URI}" !=/index.php ++RewriteRule "^(.*)" "/index.php?req=$1" [L,PT] + +
top
+
+@@ -434,11 +476,11 @@ pattern still matches (i.e., while the URI still contains an + A), perform this substitution (i.e., replace the + A with a B).

+ +-

In 2.4.8 and later, this module returns an error after 32,000 iterations to ++

In 2.4.8 and later, this module returns an error after 10,000 iterations to + protect against unintended looping. An alternative maximum number of + iterations can be specified by adding to the N flag.

+
# Be willing to replace 1 character in each pass of the loop
+-RewriteRule "(.+)[><;]$" "$1" [N=64000]
++RewriteRule "(.+)[><;]$" "$1" [N=32000]
+ # ... or, give up if after 10 loops
+ RewriteRule "(.+)[><;]$" "$1" [N=10]
+ +@@ -681,19 +723,21 @@ URI in request' warnings. +

The [S] flag is used to skip rules that you don't want to run. The + syntax of the skip flag is [S=N], where N signifies + the number of rules to skip (provided the +-RewriteRule matches). This can be thought of as a goto +-statement in your rewrite ruleset. In the following example, we only want +-to run the RewriteRule if the +-requested URI doesn't correspond with an actual file.

++RewriteRule and any preceding ++RewriteCond directives match). This can be thought of as a ++goto statement in your rewrite ruleset. In the following ++example, we only want to run the ++RewriteRule if the requested URI doesn't correspond with an ++actual file.

+ +
# Is the request for a non-existent file?
+-RewriteCond "%{REQUEST_FILENAME}" "!-f"
+-RewriteCond "%{REQUEST_FILENAME}" "!-d"
++RewriteCond "%{REQUEST_FILENAME}" !-f
++RewriteCond "%{REQUEST_FILENAME}" !-d
+ # If so, skip these two RewriteRules
+-RewriteRule ".?" "-" [S=2]
++RewriteRule ".?"                  "-" [S=2]
+ 
+-RewriteRule "(.*\.gif)" "images.php?$1"
+-RewriteRule "(.*\.html)" "docs.php?$1"
++RewriteRule "(.*\.gif)" "images.php?$1" ++RewriteRule "(.*\.html)" "docs.php?$1" + + +

This technique is useful because a RewriteCond only applies to the +@@ -705,18 +749,18 @@ use this to make pseudo if-then-else constructs: The last rule of + the then-clause becomes skip=N, where N is the + number of rules in the else-clause:

+
# Does the file exist?
+-RewriteCond "%{REQUEST_FILENAME}" "!-f"
+-RewriteCond "%{REQUEST_FILENAME}" "!-d"
++RewriteCond "%{REQUEST_FILENAME}" !-f
++RewriteCond "%{REQUEST_FILENAME}" !-d
+ # Create an if-then-else construct by skipping 3 lines if we meant to go to the "else" stanza.
+-RewriteRule ".?" "-" [S=3]
++RewriteRule ".?"                  "-" [S=3]
+ 
+ # IF the file exists, then:
+-    RewriteRule "(.*\.gif)" "images.php?$1"
++    RewriteRule "(.*\.gif)"  "images.php?$1"
+     RewriteRule "(.*\.html)" "docs.php?$1"
+     # Skip past the "else" stanza.
+-    RewriteRule ".?" "-" [S=1]
++    RewriteRule ".?"         "-" [S=1]
+ # ELSE...
+-    RewriteRule "(.*)" "404.php?file=$1"
++    RewriteRule "(.*)"       "404.php?file=$1"
+ # END
+ + +@@ -733,7 +777,7 @@ sent. This has the same effect as the # Serve .pl files as plain text +-RewriteRule "\.pl$" "-" [T=text/plain] ++RewriteRule "\.pl$" "-" [T=text/plain] + + +

Or, perhaps, if you have a camera that produces jpeg images without +@@ -741,7 +785,7 @@ file extensions, you could force those images to be served with the + correct MIME type by virtue of their file names:

+ +
# Files with 'IMG' in the name are jpg images.
+-RewriteRule "IMG" "-" [T=image/jpg]
++RewriteRule "IMG" "-" [T=image/jpg] + + +

Please note that this is a trivial example, and could be better done diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c -index 38dbb24..c3937ad 100644 +index 38dbb24..b71c67c 100644 --- a/modules/mappers/mod_rewrite.c +++ b/modules/mappers/mod_rewrite.c -@@ -168,6 +168,7 @@ static const char* really_last_key = "rewrite_really_last"; +@@ -101,6 +101,8 @@ + #include "mod_rewrite.h" + #include "ap_expr.h" + ++#include "test_char.h" ++ + static ap_dbd_t *(*dbd_acquire)(request_rec*) = NULL; + static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL; + static const char* really_last_key = "rewrite_really_last"; +@@ -168,6 +170,8 @@ static const char* really_last_key = "rewrite_really_last"; #define RULEFLAG_END (1<<17) #define RULEFLAG_ESCAPENOPLUS (1<<18) #define RULEFLAG_QSLAST (1<<19) +#define RULEFLAG_QSNONE (1<<20) /* programattic only */ ++#define RULEFLAG_ESCAPECTLS (1<<21) /* return code of the rewrite rule * the result may be escaped - or not -@@ -761,15 +762,24 @@ static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme) +@@ -321,7 +325,8 @@ typedef struct { + data_item *cookie; /* added cookies */ + int skip; /* number of next rules to skip */ + int maxrounds; /* limit on number of loops with N flag */ +- char *escapes; /* specific backref escapes */ ++ const char *escapes; /* specific backref escapes */ ++ const char *noescapes; /* specific backref chars not to escape */ + } rewriterule_entry; + + typedef struct { +@@ -422,7 +427,9 @@ static const char *rewritemap_mutex_type = "rewrite-map"; + /* Optional functions imported from mod_ssl when loaded: */ + static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *rewrite_ssl_lookup = NULL; + static APR_OPTIONAL_FN_TYPE(ssl_is_https) *rewrite_is_https = NULL; +-static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus); ++static char *escape_backref(apr_pool_t *p, const char *path, ++ const char *escapeme, const char *noescapeme, ++ int flags); + + /* + * +-------------------------------------------------------+ +@@ -645,18 +652,26 @@ static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, + return where; + } + ++ + /* + * Escapes a backreference in a similar way as php's urlencode does. + * Based on ap_os_escape_path in server/util.c + */ +-static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus) { +- char *copy = apr_palloc(p, 3 * strlen(path) + 3); ++static char *escape_backref(apr_pool_t *p, const char *path, ++ const char *escapeme, const char *noescapeme, ++ int flags) ++{ ++ char *copy = apr_palloc(p, 3 * strlen(path) + 1); + const unsigned char *s = (const unsigned char *)path; + unsigned char *d = (unsigned char *)copy; +- unsigned c; ++ int noplus = (flags & RULEFLAG_ESCAPENOPLUS) != 0; ++ int ctls = (flags & RULEFLAG_ESCAPECTLS) != 0; ++ unsigned char c; + + while ((c = *s)) { +- if (!escapeme) { ++ if (((ctls ? !TEST_CHAR(c, T_VCHAR_OBSTEXT) : !escapeme) ++ || (escapeme && ap_strchr_c(escapeme, c))) ++ && (!noescapeme || !ap_strchr_c(noescapeme, c))) { + if (apr_isalnum(c) || c == '_') { + *d++ = c; + } +@@ -667,23 +682,8 @@ static char *escape_backref(apr_pool_t *p, const char *path, const char *escapem + d = c2x(c, '%', d); + } + } +- else { +- const char *esc = escapeme; +- while (*esc) { +- if (c == *esc) { +- if (c == ' ' && !noplus) { +- *d++ = '+'; +- } +- else { +- d = c2x(c, '%', d); +- } +- break; +- } +- ++esc; +- } +- if (!*esc) { +- *d++ = c; +- } ++ else { ++ *d++ = c; + } + ++s; + } +@@ -761,15 +761,24 @@ static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme) ap_escape_uri(p, cp), NULL); } @@ -69,7 +333,7 @@ index 38dbb24..c3937ad 100644 /* don't touch, unless it's a scheme for which a query string makes sense. * See RFC 1738 and RFC 2368. -@@ -794,7 +804,7 @@ static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard, +@@ -794,7 +803,7 @@ static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard, olduri = apr_pstrdup(r->pool, r->filename); *q++ = '\0'; if (qsappend) { @@ -78,7 +342,7 @@ index 38dbb24..c3937ad 100644 r->args = apr_pstrcat(r->pool, q, "&" , r->args, NULL); } } -@@ -802,9 +812,9 @@ static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard, +@@ -802,9 +811,9 @@ static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard, r->args = apr_pstrdup(r->pool, q); } @@ -90,6 +354,16 @@ index 38dbb24..c3937ad 100644 if (!len) { r->args = NULL; } +@@ -2436,7 +2445,8 @@ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) + /* escape the backreference */ + char *tmp2, *tmp; + tmp = apr_pstrmemdup(pool, bri->source + bri->regmatch[n].rm_so, span); +- tmp2 = escape_backref(pool, tmp, entry->escapes, entry->flags & RULEFLAG_ESCAPENOPLUS); ++ tmp2 = escape_backref(pool, tmp, entry->escapes, entry->noescapes, ++ entry->flags); + rewritelog((ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'", + tmp, tmp2)); + @@ -2733,7 +2743,7 @@ static apr_status_t rewritelock_remove(void *data) * XXX: what an inclined parser. Seems we have to leave it so * for backwards compat. *sigh* @@ -128,7 +402,33 @@ index 38dbb24..c3937ad 100644 return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str, "'", NULL); } -@@ -3749,7 +3761,7 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, +@@ -3500,13 +3512,24 @@ static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg, + case 'B': + if (!*key || !strcasecmp(key, "ackrefescaping")) { + cfg->flags |= RULEFLAG_ESCAPEBACKREF; +- if (val && *val) { ++ if (val && *val) { + cfg->escapes = val; + } + } ++ else if (!strcasecmp(key, "NE")) { ++ if (val && *val) { ++ cfg->noescapes = val; ++ } ++ else { ++ return "flag 'BNE' wants a list of characters (i.e. [BNE=...])"; ++ } ++ } + else if (!strcasecmp(key, "NP") || !strcasecmp(key, "ackrefernoplus")) { + cfg->flags |= RULEFLAG_ESCAPENOPLUS; + } ++ else if (!strcasecmp(key, "CTLS")) { ++ cfg->flags |= RULEFLAG_ESCAPECTLS|RULEFLAG_ESCAPEBACKREF; ++ } + else { + ++error; + } +@@ -3749,7 +3772,7 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, rewrite_server_conf *sconf; rewriterule_entry *newrule; ap_regex_t *regexp; @@ -137,7 +437,7 @@ index 38dbb24..c3937ad 100644 const char *err; sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); -@@ -3763,7 +3775,7 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, +@@ -3763,12 +3786,11 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, } /* parse the argument line ourself */ @@ -146,13 +446,29 @@ index 38dbb24..c3937ad 100644 return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str, "'", NULL); } -@@ -3810,6 +3822,16 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, + +- /* arg3: optional flags field */ + newrule->forced_mimetype = NULL; + newrule->forced_handler = NULL; + newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY; +@@ -3777,6 +3799,9 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, + newrule->cookie = NULL; + newrule->skip = 0; + newrule->maxrounds = REWRITE_MAX_ROUNDS; ++ newrule->escapes = newrule->noescapes = NULL; ++ ++ /* arg3: optional flags field */ + if (a3 != NULL) { + if ((err = cmd_parseflagfield(cmd->pool, newrule, a3, + cmd_rewriterule_setflag)) != NULL) { +@@ -3810,6 +3835,17 @@ static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, newrule->flags |= RULEFLAG_NOSUB; } + if (*(a2_end-1) == '?') { + /* a literal ? at the end of the unsubstituted rewrite rule */ + newrule->flags |= RULEFLAG_QSNONE; ++ *(a2_end-1) = '\0'; /* trailing ? has done its job */ + } + else if (newrule->flags & RULEFLAG_QSDISCARD) { + if (NULL == ap_strchr(newrule->output, '?')) { @@ -163,7 +479,7 @@ index 38dbb24..c3937ad 100644 /* now, if the server or per-dir config holds an * array of RewriteCond entries, we take it for us * and clear the array -@@ -4215,9 +4237,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) +@@ -4215,9 +4251,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) r->path_info = NULL; } @@ -174,29 +490,78 @@ index 38dbb24..c3937ad 100644 /* Add the previously stripped per-directory location prefix, unless * (1) it's an absolute URL path and -@@ -4699,6 +4719,17 @@ static int hook_uri2file(request_rec *r) - unsigned skip; - apr_size_t flen; +@@ -4696,8 +4730,25 @@ static int hook_uri2file(request_rec *r) + } -+ if (r->args && *(ap_scan_vchar_obstext(r->args))) { + if (rulestatus) { +- unsigned skip; +- apr_size_t flen; ++ unsigned skip_absolute = is_absolute_uri(r->filename, NULL); ++ apr_size_t flen = r->filename ? strlen(r->filename) : 0; ++ int to_proxyreq = (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0); ++ int will_escape = skip_absolute && (rulestatus != ACTION_NOESCAPE); ++ ++ if (r->args ++ && !will_escape ++ && *(ap_scan_vchar_obstext(r->args))) { + /* + * We have a raw control character or a ' ' in r->args. + * Correct encoding was missed. ++ * Correct encoding was missed and we're not going to escape ++ * it before returning. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10410) + "Rewritten query string contains control " + "characters or spaces"); + return HTTP_FORBIDDEN; + } -+ + if (ACTION_STATUS == rulestatus) { int n = r->status; +@@ -4706,8 +4757,7 @@ static int hook_uri2file(request_rec *r) + return n; + } -@@ -4983,6 +5014,17 @@ static int hook_fixup(request_rec *r) +- flen = r->filename ? strlen(r->filename) : 0; +- if (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0) { ++ if (to_proxyreq) { + /* it should be go on as an internal proxy request */ + + /* check if the proxy module is enabled, so +@@ -4749,7 +4799,7 @@ static int hook_uri2file(request_rec *r) + r->filename)); + return OK; + } +- else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) { ++ else if (skip_absolute > 0) { + int n; + + /* it was finally rewritten to a remote URL */ +@@ -4757,7 +4807,7 @@ static int hook_uri2file(request_rec *r) + if (rulestatus != ACTION_NOESCAPE) { + rewritelog((r, 1, NULL, "escaping %s for redirect", + r->filename)); +- r->filename = escape_absolute_uri(r->pool, r->filename, skip); ++ r->filename = escape_absolute_uri(r->pool, r->filename, skip_absolute); + } + + /* append the QUERY_STRING part */ +@@ -4981,7 +5031,26 @@ static int hook_fixup(request_rec *r) + */ + rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory); if (rulestatus) { - unsigned skip; - -+ if (r->args && *(ap_scan_vchar_obstext(r->args))) { +- unsigned skip; ++ unsigned skip_absolute = is_absolute_uri(r->filename, NULL); ++ int to_proxyreq = 0; ++ int will_escape = 0; ++ ++ l = strlen(r->filename); ++ to_proxyreq = l > 6 && strncmp(r->filename, "proxy:", 6) == 0; ++ will_escape = skip_absolute && (rulestatus != ACTION_NOESCAPE); ++ ++ if (r->args ++ && !will_escape ++ && *(ap_scan_vchar_obstext(r->args))) { + /* + * We have a raw control character or a ' ' in r->args. + * Correct encoding was missed. @@ -206,12 +571,48 @@ index 38dbb24..c3937ad 100644 + "characters or spaces"); + return HTTP_FORBIDDEN; + } -+ + if (ACTION_STATUS == rulestatus) { int n = r->status; +@@ -4990,8 +5059,7 @@ static int hook_fixup(request_rec *r) + return n; + } +- l = strlen(r->filename); +- if (l > 6 && strncmp(r->filename, "proxy:", 6) == 0) { ++ if (to_proxyreq) { + /* it should go on as an internal proxy request */ + + /* make sure the QUERY_STRING and +@@ -5015,7 +5083,7 @@ static int hook_fixup(request_rec *r) + "%s [OK]", r->filename)); + return OK; + } +- else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) { ++ else if (skip_absolute > 0) { + /* it was finally rewritten to a remote URL */ + + /* because we are in a per-dir context +@@ -5024,7 +5092,7 @@ static int hook_fixup(request_rec *r) + */ + if (dconf->baseurl != NULL) { + /* skip 'scheme://' */ +- cp = r->filename + skip; ++ cp = r->filename + skip_absolute; + + if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) { + rewritelog((r, 2, dconf->directory, +@@ -5069,7 +5137,7 @@ static int hook_fixup(request_rec *r) + if (rulestatus != ACTION_NOESCAPE) { + rewritelog((r, 1, dconf->directory, "escaping %s for redirect", + r->filename)); +- r->filename = escape_absolute_uri(r->pool, r->filename, skip); ++ r->filename = escape_absolute_uri(r->pool, r->filename, skip_absolute); + } + + /* append the QUERY_STRING part */ diff --git a/modules/proxy/mod_proxy_ajp.c b/modules/proxy/mod_proxy_ajp.c -index 058b03f..a529c02 100644 +index 6faabea..59396a8 100644 --- a/modules/proxy/mod_proxy_ajp.c +++ b/modules/proxy/mod_proxy_ajp.c @@ -69,6 +69,16 @@ static int proxy_ajp_canon(request_rec *r, char *url) @@ -253,7 +654,7 @@ index 3a28038..c599e1a 100644 if (path == NULL) return HTTP_BAD_REQUEST; diff --git a/modules/proxy/mod_proxy_http.c b/modules/proxy/mod_proxy_http.c -index c1c591a..58a8c86 100644 +index 7da9bde..2cdc61e 100644 --- a/modules/proxy/mod_proxy_http.c +++ b/modules/proxy/mod_proxy_http.c @@ -90,6 +90,16 @@ static int proxy_http_canon(request_rec *r, char *url) @@ -294,3 +695,41 @@ index e005a94..f5e27d9 100644 } if (path == NULL) return HTTP_BAD_REQUEST; +diff --git a/server/gen_test_char.c b/server/gen_test_char.c +index 48ae6f4..6a153a3 100644 +--- a/server/gen_test_char.c ++++ b/server/gen_test_char.c +@@ -169,5 +169,15 @@ int main(int argc, char *argv[]) + + printf("\n};\n"); + ++ ++ printf( ++ "/* we assume the folks using this ensure 0 <= c < 256... which means\n" ++ " * you need a cast to (unsigned char) first, you can't just plug a\n" ++ " * char in here and get it to work, because if char is signed then it\n" ++ " * will first be sign extended.\n" ++ " */\n" ++ "#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f))\n" ++ ); ++ + return 0; + } +diff --git a/server/util.c b/server/util.c +index 2a5dd04..1d82fd8 100644 +--- a/server/util.c ++++ b/server/util.c +@@ -74,13 +74,6 @@ + */ + #include "test_char.h" + +-/* we assume the folks using this ensure 0 <= c < 256... which means +- * you need a cast to (unsigned char) first, you can't just plug a +- * char in here and get it to work, because if char is signed then it +- * will first be sign extended. +- */ +-#define TEST_CHAR(c, f) (test_char_table[(unsigned char)(c)] & (f)) +- + /* Win32/NetWare/OS2 need to check for both forward and back slashes + * in ap_getparents() and ap_escape_url. + */ diff --git a/SPECS/httpd.spec b/SPECS/httpd.spec index 8493acc..6a8ef49 100644 --- a/SPECS/httpd.spec +++ b/SPECS/httpd.spec @@ -13,7 +13,7 @@ Summary: Apache HTTP Server Name: httpd Version: 2.4.37 -Release: 56%{?dist}.4 +Release: 56%{?dist}.6 URL: https://httpd.apache.org/ Source0: https://www.apache.org/dist/httpd/httpd-%{version}.tar.bz2 Source2: httpd.logrotate @@ -974,6 +974,9 @@ rm -rf $RPM_BUILD_ROOT %{_rpmconfigdir}/macros.d/macros.httpd %changelog +* Thu Apr 27 2023 Luboš Uhliarik - 2.4.37-56.6 +- Resolves: #2190133 - mod_rewrite regression with CVE-2023-25690 + * Sat Mar 18 2023 Luboš Uhliarik - 2.4.37-56.4 - Resolves: #2177748 - CVE-2023-25690 httpd:2.4/httpd: HTTP request splitting with mod_rewrite and mod_proxy