aboutsummaryrefslogtreecommitdiff
path: root/src/http.c
diff options
context:
space:
mode:
authorfrosty <gabriel@bwaaa.monster>2026-08-16 17:55:19 -0400
committerfrosty <gabriel@bwaaa.monster>2026-08-16 17:55:19 -0400
commitacdd9e754a536d542099951e4c9a6acc8cdf1c51 (patch)
tree0a1d4333d2b160a8243fe2c35425268cc2690ac9 /src/http.c
parent916d3497b9316ec6b64646d456d3c03141b1fe21 (diff)
downloadbeaker-acdd9e754a536d542099951e4c9a6acc8cdf1c51.tar.gz
fix: harden HTTP request and response parsingHEADmasterindev
Diffstat (limited to 'src/http.c')
-rw-r--r--src/http.c163
1 files changed, 145 insertions, 18 deletions
diff --git a/src/http.c b/src/http.c
index bfd6f9b..974e761 100644
--- a/src/http.c
+++ b/src/http.c
@@ -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 "