Streamline hardening helpers and expand coverage

This commit is contained in:
udo
2026-05-21 10:12:11 +00:00
parent 0d8b74930c
commit 41e9ca219f
14 changed files with 158 additions and 70 deletions
+7 -42
View File
@@ -36,7 +36,6 @@
#include <cstring>
#include <stdexcept>
#include <cctype>
#include <errno.h> // E*
#include <fcntl.h>
@@ -124,39 +123,15 @@ make_http_text_response(String status_line, String body, String extra_headers =
);
}
static bool
header_token_safe(String name)
{
if(name == "")
return(false);
for(char c : name)
{
if(!(std::isalnum((unsigned char)c) || c == '-' || c == '_'))
return(false);
}
return(true);
}
static String
header_value_sanitize(String value)
{
for(char& c : value)
{
if(c == '\r' || c == '\n')
c = ' ';
}
return(value);
}
static String
render_header_map(StringMap headers)
{
String result;
for(auto& item : headers)
{
if(!header_token_safe(item.first))
if(!http_header_name_valid(item.first))
continue;
result += item.first + ": " + header_value_sanitize(item.second) + "\r\n";
result += item.first + ": " + http_header_value_clean(item.second) + "\r\n";
}
return(result);
}
@@ -167,23 +142,13 @@ render_set_cookie_headers(StringList headers)
String result;
for(String header : headers)
{
if(header.find('\r') != String::npos || header.find('\n') != String::npos)
continue;
if(!str_starts_with(to_lower(header), "set-cookie: "))
if(!http_set_cookie_header_valid(header))
continue;
result += header + "\r\n";
}
return(result);
}
static String
safe_status_line(String status_line)
{
if(status_line.find('\r') != String::npos || status_line.find('\n') != String::npos)
return("Status: 500 Internal Server Error");
return(status_line);
}
static String
http_script_root()
{
@@ -865,14 +830,14 @@ FastCGIServer::process_http_request(FastCGIRequest& request, String& data)
if(!parse_http_message(request, data))
return;
if(request.params["SCRIPT_FILENAME"] == "" && request.params["DOCUMENT_URI"] != "")
if(resolve_http_script_filename && request.params["SCRIPT_FILENAME"] == "" && request.params["DOCUMENT_URI"] != "")
{
String document_root = http_script_root();
String document_uri = strip_leading_slashes(request.params["DOCUMENT_URI"]);
String candidate = path_join(document_root, document_uri);
String real_root = path_real(document_root);
String real_candidate = path_real(candidate);
if(real_root == "" || real_candidate == "" || !path_is_within(real_candidate, real_root))
if(real_root == "" || real_candidate == "" || !path_is_within(candidate, document_root))
{
reject_http_connection(*client_sockets[request.resources.client_socket], "HTTP/1.1 404 Not Found", "script not found\n");
return;
@@ -1339,7 +1304,7 @@ void
FastCGIServer::assemble_output_buffer(FastCGIRequest& request, Connection* connection)
{
request.out =
safe_status_line(request.response_code)+"\r\n"+
http_status_line_clean(request.response_code)+"\r\n"+
render_header_map(request.header) +
render_set_cookie_headers(request.set_cookies) +
"\r\n";
@@ -1354,7 +1319,7 @@ FastCGIServer::assemble_output_buffer(FastCGIRequest& request, Connection* conne
request.set_status(500, "Response Too Large");
request.header.clear();
request.header["Content-Type"] = "text/plain; charset=utf-8";
request.out = safe_status_line(request.response_code) + "\r\n" + render_header_map(request.header) + "\r\nresponse exceeded configured output limit\n";
request.out = http_status_line_clean(request.response_code) + "\r\n" + render_header_map(request.header) + "\r\nresponse exceeded configured output limit\n";
}
request.ob_stack.clear();
request.flags.output_closed = true;
+1
View File
@@ -61,6 +61,7 @@ public:
void process(int timeout_ms = -1); // timeout_ms<0 blocks forever
void process_forever();
int calls_until_termination = 8; // set this to -1 to never terminate
bool resolve_http_script_filename = true;
typedef unsigned RequestID;
typedef std::map<RequestID, FastCGIRequest*> RequestList;
+3 -2
View File
@@ -2,6 +2,7 @@
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#include <cctype>
#include <stdexcept>
String var_dump(StringMap map, String prefix, String postfix)
@@ -47,14 +48,14 @@ u8 hex_to_u8(String src)
String to_lower(String s)
{
String result = s;
std::transform(result.begin(), result.end(),result.begin(), ::tolower);
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return((char)std::tolower(c)); });
return(result);
}
String to_upper(String s)
{
String result = s;
std::transform(result.begin(), result.end(),result.begin(), ::toupper);
std::transform(result.begin(), result.end(), result.begin(), [](unsigned char c) { return((char)std::toupper(c)); });
return(result);
}
+6 -8
View File
@@ -496,14 +496,12 @@ String socket_read(u64 sockfd, u32 max_length, u32 timeout)
tv.tv_sec = timeout;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
char buf[max_length+1];
auto byte_count = recv(sockfd, buf, sizeof(buf), 0);
if(max_length == 0)
return("");
std::vector<char> buf(max_length);
auto byte_count = recv(sockfd, buf.data(), buf.size(), 0);
if(byte_count > 0)
{
buf[byte_count] = 0;
String result(buf, byte_count+1);
return(result);
}
return(String(buf.data(), byte_count));
return("");
}
@@ -512,7 +510,7 @@ String memcache_escape_key(String key)
String result;
for(auto c : key)
{
if(isspace(c))
if(isspace((unsigned char)c))
c = '_';
result.append(1, c);
}
+1 -1
View File
@@ -222,7 +222,7 @@ struct Request {
bool websocket_is_binary = false;
bool websocket_is_text = false;
String current_unit_file = "";
std::string params_buffer;
String params_buffer;
} resources;
void ob_start();
+37 -4
View File
@@ -1,5 +1,6 @@
#include "uri.h"
#include <cctype>
#include <fcntl.h>
#include <unistd.h>
@@ -174,7 +175,7 @@ String uri_decode(String q)
for(u32 i = 0; i < q.length(); i++)
{
char c = q[i];
if(c == '%' && q[i+1] != '%')
if(c == '%' && i + 2 < q.length() && isxdigit((unsigned char)q[i + 1]) && isxdigit((unsigned char)q[i + 2]))
{
result.append(1, hex_to_u8(q.substr(i+1, 2)));
i += 2;
@@ -197,12 +198,12 @@ String uri_encode(String q)
for(u32 i = 0; i < q.length(); i++)
{
char c = q[i];
if(isalnum(c) || c == '~' || c == '.' || c == '_' || c == '-')
if(isalnum((unsigned char)c) || c == '~' || c == '.' || c == '_' || c == '-')
result.append(1, c);
else
{
result.append(1, '%');
result.append(to_hex(c));
result.append(to_hex((u8)c, 2));
}
}
return(result);
@@ -259,7 +260,17 @@ String encode_query(StringMap map)
return(result);
}
namespace {
bool http_header_name_valid(String name)
{
if(name == "")
return(false);
for(char c : name)
{
if(!(std::isalnum((unsigned char)c) || c == '-' || c == '_'))
return(false);
}
return(true);
}
String http_header_value_clean(String value)
{
@@ -271,6 +282,22 @@ String http_header_value_clean(String value)
return(value);
}
bool http_set_cookie_header_valid(String header)
{
if(header.find('\r') != String::npos || header.find('\n') != String::npos)
return(false);
return(str_starts_with(to_lower(header), "set-cookie: "));
}
String http_status_line_clean(String status_line)
{
if(status_line.find('\r') != String::npos || status_line.find('\n') != String::npos)
return("Status: 500 Internal Server Error");
return(status_line);
}
namespace {
String cookie_attribute_value_clean(String value)
{
for(char& c : value)
@@ -410,6 +437,12 @@ URI parse_uri(String uri_String)
{
URI result;
if(uri_String == "")
{
result.parts["raw"] = "";
return(result);
}
u8 state = 0;
String current = "";
char expect = 0;
+12 -1
View File
@@ -40,6 +40,12 @@ bool zip_entry_name_safe(String name)
if(name.find(":") != String::npos)
return(false);
for(char c : name)
{
if(c == '\0' || (unsigned char)c < 0x20)
return(false);
}
auto parts = split(replace(name, "\\", "/"), "/");
for(auto part : parts)
{
@@ -330,7 +336,12 @@ String gz_uncompress(String compressed)
if(!out)
throw std::runtime_error("gz_uncompress(): decompression failed");
archive_check_size("gz_uncompress", "output", out_len, "ARCHIVE_MAX_OUTPUT_BYTES", 64 * 1024 * 1024);
u64 output_limit = archive_config_u64("ARCHIVE_MAX_OUTPUT_BYTES", 64 * 1024 * 1024);
if(output_limit > 0 && out_len > output_limit)
{
mz_free(out);
throw std::runtime_error("gz_uncompress(): output exceeds configured limit");
}
String result((char*)out, out_len);
mz_free(out);
+1
View File
@@ -1298,6 +1298,7 @@ void custom_server_http_dispatcher_loop(String key)
FastCGIServer dispatcher;
custom_server_bind_http(dispatcher, cfg["bind"]);
dispatcher.calls_until_termination = -1;
dispatcher.resolve_http_script_filename = false;
dispatcher.on_complete = &custom_server_http_complete;
for(;;)
dispatcher.process(-1);