diff options
Diffstat (limited to 'src/http.c')
| -rw-r--r-- | src/http.c | 163 |
1 files changed, 145 insertions, 18 deletions
@@ -6,17 +6,109 @@ #include <sys/socket.h> #include <unistd.h> +static bool is_http_token_char(unsigned char c) { + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z')) { + return true; + } + + return c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || + c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' || + c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; +} + +bool beaker_is_valid_http_token(const char *value) { + if (value == NULL) { + return false; + } + + return beaker_is_valid_http_token_span(value, strlen(value)); +} + +bool beaker_is_valid_http_token_span(const char *value, size_t length) { + if (value == NULL || length == 0) + return false; + + for (size_t i = 0; i < length; i++) { + if (!is_http_token_char((unsigned char)value[i])) { + return false; + } + } + return true; +} + +bool beaker_is_valid_header_value(const char *value) { + if (value == NULL) { + return false; + } + + for (size_t i = 0; value[i] != '\0'; i++) { + unsigned char c = (unsigned char)value[i]; + if (c < 0x20 || c == 0x7f) { + return false; + } + } + return true; +} + +static bool is_valid_cookie_value(const char *value) { + if (value == NULL) { + return false; + } + + for (size_t i = 0; value[i] != '\0'; i++) { + unsigned char c = (unsigned char)value[i]; + if (!(c == 0x21 || (c >= 0x23 && c <= 0x2b) || (c >= 0x2d && c <= 0x3a) || + (c >= 0x3c && c <= 0x5b) || (c >= 0x5d && c <= 0x7e))) { + return false; + } + } + return true; +} + +static bool is_valid_cookie_attribute(const char *value) { + return value == NULL || + (beaker_is_valid_header_value(value) && strchr(value, ';') == NULL); +} + +static bool parse_status_line(const char *status_line, int *status_code) { + if (status_line == NULL || status_code == NULL) + return false; + + size_t status_line_len = strlen(status_line); + if (status_line_len < 5 || status_line_len >= 128 || status_line[0] < '0' || + status_line[0] > '9' || status_line[1] < '0' || status_line[1] > '9' || + status_line[2] < '0' || status_line[2] > '9' || status_line[3] != ' ' || + status_line[4] == '\0' || !beaker_is_valid_header_value(status_line)) { + return false; + } + + int code = (status_line[0] - '0') * 100 + (status_line[1] - '0') * 10 + + (status_line[2] - '0'); + if (code < 100 || code > 599) { + return false; + } + + *status_code = code; + return true; +} + static void build_cookie_headers(char *cookie_headers_buffer, size_t buffer_size) { cookie_headers_buffer[0] = '\0'; for (int i = 0; i < cookies_to_set_count; i++) { - char single_cookie_header[MAX_VALUE_LEN * 2]; + char single_cookie_header[MAX_KEY_LEN * 2 + MAX_VALUE_LEN * 2 + 128]; - snprintf(single_cookie_header, sizeof(single_cookie_header), - "Set-Cookie: %s=%s", cookies_to_set[i].name, - cookies_to_set[i].value); + int header_length = snprintf( + single_cookie_header, sizeof(single_cookie_header), "Set-Cookie: %s=%s", + cookies_to_set[i].name, cookies_to_set[i].value); + if (header_length < 0 || + (size_t)header_length >= sizeof(single_cookie_header)) { + beaker_log("ERROR", "build_cookie_headers: Cookie is too large."); + continue; + } if (strlen(cookies_to_set[i].expires) > 0) { strncat(single_cookie_header, "; Expires=", @@ -70,15 +162,26 @@ void send_status(const char *status_line) { return; } + int status_code; + if (!parse_status_line(status_line, &status_code)) { + beaker_log("SECURITY", "send_status: Rejected invalid HTTP status line."); + status_line = "500 Internal Server Error"; + status_code = 500; + } + char http_response[BUFFER_SIZE]; - current_response_status = atoi(status_line); + current_response_status = status_code; current_response_size = 0; - snprintf(http_response, sizeof(http_response), - "HTTP/1.1 %s\r\n" - "Content-Length: 0\r\n" - "Connection: close\r\n" - "\r\n", - status_line); + int response_length = snprintf(http_response, sizeof(http_response), + "HTTP/1.1 %s\r\n" + "Content-Length: 0\r\n" + "Connection: close\r\n" + "\r\n", + status_line); + if (response_length < 0 || (size_t)response_length >= sizeof(http_response)) { + beaker_log("ERROR", "send_status: Failed to construct HTTP status."); + return; + } if (beaker_send_all(current_client_socket, http_response, strlen(http_response)) < 0) { @@ -135,6 +238,14 @@ void send_redirect(const char *location) { return; } + if (location == NULL || location[0] == '\0' || + !beaker_is_valid_header_value(location)) { + beaker_log("SECURITY", "send_redirect: Rejected invalid Location value."); + beaker_clear_response_cookies(); + send_status("500 Internal Server Error"); + return; + } + char http_response_header[BUFFER_SIZE * 2]; current_response_status = 302; current_response_size = 0; @@ -143,13 +254,20 @@ void send_redirect(const char *location) { build_cookie_headers(cookie_headers, sizeof(cookie_headers)); beaker_clear_response_cookies(); - snprintf(http_response_header, sizeof(http_response_header), - "HTTP/1.1 302 Found\r\n" - "Location: %s\r\n" - "%s" - "Connection: close\r\n" - "\r\n", - location, cookie_headers); + int header_length = + snprintf(http_response_header, sizeof(http_response_header), + "HTTP/1.1 302 Found\r\n" + "Location: %s\r\n" + "%s" + "Connection: close\r\n" + "\r\n", + location, cookie_headers); + if (header_length < 0 || + (size_t)header_length >= sizeof(http_response_header)) { + beaker_log("SECURITY", "send_redirect: Location value is too long."); + send_status("500 Internal Server Error"); + return; + } if (beaker_send_all(current_client_socket, http_response_header, strlen(http_response_header)) < 0) { @@ -162,6 +280,15 @@ void send_redirect(const char *location) { void set_cookie(const char *name, const char *value, const char *expires, const char *path, bool http_only, bool secure) { + if (!beaker_is_valid_http_token(name) || !is_valid_cookie_value(value) || + !is_valid_cookie_attribute(expires) || !is_valid_cookie_attribute(path) || + strlen(name) >= MAX_KEY_LEN || strlen(value) >= MAX_VALUE_LEN || + (expires != NULL && strlen(expires) >= MAX_VALUE_LEN) || + (path != NULL && strlen(path) >= MAX_KEY_LEN)) { + beaker_log("SECURITY", "set_cookie: Rejected invalid cookie data."); + return; + } + if (cookies_to_set_count >= MAX_COOKIES) { beaker_log("WARN", "set_cookie: Maximum number of cookies to set reached. Cannot " |
