Fix #2292 - fail on stray \r character in headers

This commit is contained in:
cpq committed 2023-07-09 18:18:18 +01:00
1 parent bf8c4dfc83
commit 88e880f107
4 files changed
+131 -66

No files matched your search

+49 -31
View File
@@ -1412,16 +1412,6 @@ int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
}
return 0;
}
static const char *skip(const char *s, const char *e, const char *d,
struct mg_str *v) {
v->ptr = s;
while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
v->len = (size_t) (s - v->ptr);
while (s < e && strchr(d, *s) != NULL) s++;
return s;
}
struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
@@ -1431,22 +1421,42 @@ struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
return NULL;
}
// Get character length. Used to parse method, URI, headers
static size_t clen(const char *s) {
uint8_t c = *(uint8_t *) s;
if (c > ' ' && c < '~') return 1; // Usual ascii printed char
if ((c & 0xe0) == 0xc0) return 2; // 2-byte UTF8
if ((c & 0xf0) == 0xe0) return 3; // 3-byte UTF8
if ((c & 0xf8) == 0xf0) return 4; // 4-byte UTF8
return 0;
}
// Skip until the newline. Return advanced `s`, or NULL on error
static const char *skiptorn(const char *s, const char *end, struct mg_str *v) {
v->ptr = s;
while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline
if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r
if (s < end && s[0] == '\r') s++; // Skip \r
if (s >= end || *s++ != '\n') return NULL; // Skip \n
return s;
}
static bool mg_http_parse_headers(const char *s, const char *end,
struct mg_http_header *h, int max_headers) {
int i;
for (i = 0; i < max_headers; i++) {
struct mg_str k, v, tmp;
const char *he = skip(s, end, "\r\n", &tmp);
if (tmp.len == 0) break; // empty header = EOH
s = skip(s, he, ": \r\n", &k);
if (k.ptr[k.len] != ':') return false; // Invalid header
s = skip(s, he, "\r\n", &v);
struct mg_http_header *h, size_t max_hdrs) {
size_t i, n;
for (i = 0; i < max_hdrs; i++) {
struct mg_str k = {NULL, 0}, v = {NULL, 0};
if (s >= end) return false;
if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
k.ptr = s;
while (s < end && s[0] != ':' && (n = clen(s)) > 0) s += n, k.len += n;
if (k.len == 0) return false; // Empty name
if (s >= end || *s++ != ':') return false; // Invalid, not followed by :
while (s < end && s[0] == ' ') s++; // Skip spaces
if ((s = skiptorn(s, end, &v)) == NULL) return false;
while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces
if (k.len == 0) return false; // empty name
// MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len, tmp.ptr,
//(int) k.len, k.ptr, (int) v.len, v.ptr));
h[i].name = k;
h[i].value = v;
// MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.ptr, (int) v.len, v.ptr));
h[i].name = k, h[i].value = v; // Success. Assign values
}
return true;
}
@@ -1455,6 +1465,7 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL
struct mg_str *cl;
size_t n;
memset(hm, 0, sizeof(*hm));
if (req_len <= 0) return req_len;
@@ -1466,9 +1477,13 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite
// Parse request line
s = skip(s, end, " ", &hm->method);
s = skip(s, end, " ", &hm->uri);
s = skip(s, end, "\r\n", &hm->proto);
hm->method.ptr = s;
while (s < end && (n = clen(s)) > 0) s += n, hm->method.len += n;
while (s < end && s[0] == ' ') s++; // Skip spaces
hm->uri.ptr = s;
while (s < end && (n = clen(s)) > 0) s += n, hm->uri.len += n;
while (s < end && s[0] == ' ') s++; // Skip spaces
if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
// Sanity check. Allow protocol/reason to be empty
if (hm->method.len == 0 || hm->uri.len == 0) return -1;
@@ -6263,12 +6278,15 @@ static bool mg_ws_client_handshake(struct mg_connection *c) {
mg_error(c, "not http"); // Some just, not an HTTP request
} else if (n > 0) {
if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
mg_error(c, "handshake error");
mg_error(c, "ws handshake error");
} else {
struct mg_http_message hm;
mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
c->is_websocket = 1;
mg_call(c, MG_EV_WS_OPEN, &hm);
if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
c->is_websocket = 1;
mg_call(c, MG_EV_WS_OPEN, &hm);
} else {
mg_error(c, "ws handshake error");
}
}
mg_iobuf_del(&c->recv, 0, (size_t) n);
} else {
+42 -27
View File
@@ -180,16 +180,6 @@ int mg_http_get_request_len(const unsigned char *buf, size_t buf_len) {
}
return 0;
}
static const char *skip(const char *s, const char *e, const char *d,
struct mg_str *v) {
v->ptr = s;
while (s < e && *s != '\n' && strchr(d, *s) == NULL) s++;
v->len = (size_t) (s - v->ptr);
while (s < e && strchr(d, *s) != NULL) s++;
return s;
}
struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
size_t i, n = strlen(name), max = sizeof(h->headers) / sizeof(h->headers[0]);
for (i = 0; i < max && h->headers[i].name.len > 0; i++) {
@@ -199,22 +189,42 @@ struct mg_str *mg_http_get_header(struct mg_http_message *h, const char *name) {
return NULL;
}
// Get character length. Used to parse method, URI, headers
static size_t clen(const char *s) {
uint8_t c = *(uint8_t *) s;
if (c > ' ' && c < '~') return 1; // Usual ascii printed char
if ((c & 0xe0) == 0xc0) return 2; // 2-byte UTF8
if ((c & 0xf0) == 0xe0) return 3; // 3-byte UTF8
if ((c & 0xf8) == 0xf0) return 4; // 4-byte UTF8
return 0;
}
// Skip until the newline. Return advanced `s`, or NULL on error
static const char *skiptorn(const char *s, const char *end, struct mg_str *v) {
v->ptr = s;
while (s < end && s[0] != '\n' && s[0] != '\r') s++, v->len++; // To newline
if (s >= end || (s[0] == '\r' && s[1] != '\n')) return NULL; // Stray \r
if (s < end && s[0] == '\r') s++; // Skip \r
if (s >= end || *s++ != '\n') return NULL; // Skip \n
return s;
}
static bool mg_http_parse_headers(const char *s, const char *end,
struct mg_http_header *h, int max_headers) {
int i;
for (i = 0; i < max_headers; i++) {
struct mg_str k, v, tmp;
const char *he = skip(s, end, "\r\n", &tmp);
if (tmp.len == 0) break; // empty header = EOH
s = skip(s, he, ": \r\n", &k);
if (k.ptr[k.len] != ':') return false; // Invalid header
s = skip(s, he, "\r\n", &v);
struct mg_http_header *h, size_t max_hdrs) {
size_t i, n;
for (i = 0; i < max_hdrs; i++) {
struct mg_str k = {NULL, 0}, v = {NULL, 0};
if (s >= end) return false;
if (s[0] == '\n' || (s[0] == '\r' && s[1] == '\n')) break;
k.ptr = s;
while (s < end && s[0] != ':' && (n = clen(s)) > 0) s += n, k.len += n;
if (k.len == 0) return false; // Empty name
if (s >= end || *s++ != ':') return false; // Invalid, not followed by :
while (s < end && s[0] == ' ') s++; // Skip spaces
if ((s = skiptorn(s, end, &v)) == NULL) return false;
while (v.len > 0 && v.ptr[v.len - 1] == ' ') v.len--; // Trim spaces
if (k.len == 0) return false; // empty name
// MG_INFO(("--HH [%.*s] [%.*s] [%.*s]", (int) tmp.len, tmp.ptr,
//(int) k.len, k.ptr, (int) v.len, v.ptr));
h[i].name = k;
h[i].value = v;
// MG_INFO(("--HH [%.*s] [%.*s]", (int) k.len, k.ptr, (int) v.len, v.ptr));
h[i].name = k, h[i].value = v; // Success. Assign values
}
return true;
}
@@ -223,6 +233,7 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
int is_response, req_len = mg_http_get_request_len((unsigned char *) s, len);
const char *end = s == NULL ? NULL : s + req_len, *qs; // Cannot add to NULL
struct mg_str *cl;
size_t n;
memset(hm, 0, sizeof(*hm));
if (req_len <= 0) return req_len;
@@ -234,9 +245,13 @@ int mg_http_parse(const char *s, size_t len, struct mg_http_message *hm) {
hm->message.len = hm->body.len = (size_t) ~0; // Set body length to infinite
// Parse request line
s = skip(s, end, " ", &hm->method);
s = skip(s, end, " ", &hm->uri);
s = skip(s, end, "\r\n", &hm->proto);
hm->method.ptr = s;
while (s < end && (n = clen(s)) > 0) s += n, hm->method.len += n;
while (s < end && s[0] == ' ') s++; // Skip spaces
hm->uri.ptr = s;
while (s < end && (n = clen(s)) > 0) s += n, hm->uri.len += n;
while (s < end && s[0] == ' ') s++; // Skip spaces
if ((s = skiptorn(s, end, &hm->proto)) == NULL) return false;
// Sanity check. Allow protocol/reason to be empty
if (hm->method.len == 0 || hm->uri.len == 0) return -1;
+7 -4
View File
@@ -146,12 +146,15 @@ static bool mg_ws_client_handshake(struct mg_connection *c) {
mg_error(c, "not http"); // Some just, not an HTTP request
} else if (n > 0) {
if (n < 15 || memcmp(c->recv.buf + 9, "101", 3) != 0) {
mg_error(c, "handshake error");
mg_error(c, "ws handshake error");
} else {
struct mg_http_message hm;
mg_http_parse((char *) c->recv.buf, c->recv.len, &hm);
c->is_websocket = 1;
mg_call(c, MG_EV_WS_OPEN, &hm);
if (mg_http_parse((char *) c->recv.buf, c->recv.len, &hm)) {
c->is_websocket = 1;
mg_call(c, MG_EV_WS_OPEN, &hm);
} else {
mg_error(c, "ws handshake error");
}
}
mg_iobuf_del(&c->recv, 0, (size_t) n);
} else {
+33 -4
View File
@@ -745,7 +745,7 @@ static int cmpbody(const char *buf, const char *str) {
struct mg_str s = mg_str(str);
struct mg_http_message hm = gethm(buf);
size_t len = strlen(buf);
mg_http_parse(buf, len, &hm);
// mg_http_parse(buf, len, &hm);
if (hm.body.len > len) hm.body.len = len - (size_t) (hm.body.ptr - buf);
return mg_strcmp(hm.body, s);
}
@@ -857,6 +857,7 @@ static void test_http_server(void) {
// Fetch file with unicode chars in filename
ASSERT(fetch(&mgr, buf, url, "GET /київ.txt HTTP/1.0\n\n") == 200);
MG_INFO(("%s", buf));
ASSERT(cmpbody(buf, "є\n") == 0);
ASSERT(fetch(&mgr, buf, url, "GET /../fuzz.c HTTP/1.0\n\n") == 400);
@@ -1350,6 +1351,9 @@ static void test_http_parse(void) {
ASSERT(req.query.len == 0);
ASSERT(req.message.len == len);
ASSERT(req.body.len == 0);
ASSERT(mg_vcmp(&req.method, "GET") == 0);
ASSERT(mg_vcmp(&req.uri, "/blah") == 0);
ASSERT(mg_vcmp(&req.proto, "HTTP/1.0") == 0);
for (idx = 0; idx < len; idx++) ASSERT(mg_http_parse(s, idx, &req) == 0);
}
@@ -1386,6 +1390,30 @@ static void test_http_parse(void) {
ASSERT(req.body.len == 0);
}
// #2292: fail on stray \r inside the headers
ASSERT(mg_http_parse("a є\n\n", 6, &req) > 0);
ASSERT(mg_http_parse("a b\n\n", 5, &req) > 0);
ASSERT(mg_http_parse("a b\na:\n\n", 8, &req) > 0);
ASSERT(mg_http_parse("a b\na:\r\n\n", 9, &req) > 0);
ASSERT(mg_http_parse("a b\n\ra:\r\n\n", 10, &req) == -1);
ASSERT(mg_http_parse("a b\na:\r1\n\n", 10, &req) == -1);
ASSERT(mg_http_parse("a b\na: \r1\n\n", 11, &req) == -1);
ASSERT(mg_http_parse("a b\na: \rb:\n\n", 12, &req) == -1);
ASSERT(mg_http_parse("a b\na: \nb:\n\n", 12, &req) > 0);
{
const char *s = "ґєт /слеш вах вах\nмісто: кіїв \n\n";
ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s));
ASSERT(req.body.len == 0);
ASSERT(req.headers[1].name.len == 0);
ASSERT(mg_vcmp(&req.headers[0].name, "місто") == 0);
ASSERT(mg_vcmp(&req.headers[0].value, "кіїв") == 0);
ASSERT((v = mg_http_get_header(&req, "місто")) != NULL);
ASSERT(mg_vcmp(&req.method, "ґєт") == 0);
ASSERT(mg_vcmp(&req.uri, "/слеш") == 0);
ASSERT(mg_vcmp(&req.proto, "вах вах") == 0);
}
{
const char *s = "a b c\r\nContent-Length: 21 \r\nb: t\r\nv:v\r\n\r\nabc";
ASSERT(mg_http_parse(s, strlen(s), &req) == (int) strlen(s) - 3);
@@ -2570,7 +2598,7 @@ static void test_udp(void) {
}
static void test_check_ip_acl(void) {
struct mg_addr ip = {{1,2,3,4}, 0, false}; // 1.2.3.4
struct mg_addr ip = {{1, 2, 3, 4}, 0, false}; // 1.2.3.4
ASSERT(mg_check_ip_acl(mg_str(NULL), &ip) == 1);
ASSERT(mg_check_ip_acl(mg_str(""), &ip) == 1);
ASSERT(mg_check_ip_acl(mg_str("invalid"), &ip) == -1);
@@ -2581,7 +2609,8 @@ static void test_check_ip_acl(void) {
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.2.3.4"), &ip) == 1);
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0,+1.0.0.0/16"), &ip) == 0);
ip.is_ip6 = true;
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) == -1); // not yet supported
ASSERT(mg_check_ip_acl(mg_str("-0.0.0.0/0"), &ip) ==
-1); // not yet supported
}
static void w3(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
@@ -3057,6 +3086,7 @@ int main(void) {
test_str();
test_globmatch();
test_get_header_var();
test_http_parse();
test_rewrites();
test_check_ip_acl();
test_udp();
@@ -3068,7 +3098,6 @@ int main(void) {
test_http_chunked();
test_http_upload();
test_http_stream_buffer();
test_http_parse();
test_util();
test_dns();
test_timer();