1
0
mirror of https://github.com/esphome/esphome.git synced 2025-10-07 04:13:47 +01:00

[web_server_idf] Improve parameter caching security and reduce memory overhead

This commit is contained in:
J. Nick Koston
2025-10-03 15:30:42 -05:00
parent f2aa5a754c
commit 0f05f5119a
2 changed files with 39 additions and 15 deletions

View File

@@ -164,8 +164,8 @@ esp_err_t AsyncWebServer::request_handler_(AsyncWebServerRequest *request) const
AsyncWebServerRequest::~AsyncWebServerRequest() { AsyncWebServerRequest::~AsyncWebServerRequest() {
delete this->rsp_; delete this->rsp_;
for (const auto &pair : this->params_) { for (auto *param : this->params_) {
delete pair.second; // NOLINT(cppcoreguidelines-owning-memory) delete param; // NOLINT(cppcoreguidelines-owning-memory)
} }
} }
@@ -205,10 +205,23 @@ void AsyncWebServerRequest::redirect(const std::string &url) {
} }
void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) { void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) {
httpd_resp_set_status(*this, code == 200 ? HTTPD_200 // Set status code - use constants for common codes to avoid string allocation
: code == 404 ? HTTPD_404 const char *status;
: code == 409 ? HTTPD_409 switch (code) {
: to_string(code).c_str()); case 200:
status = HTTPD_200;
break;
case 404:
status = HTTPD_404;
break;
case 409:
status = HTTPD_409;
break;
default:
status = to_string(code).c_str();
break;
}
httpd_resp_set_status(*this, status);
if (content_type && *content_type) { if (content_type && *content_type) {
httpd_resp_set_type(*this, content_type); httpd_resp_set_type(*this, content_type);
@@ -265,11 +278,14 @@ void AsyncWebServerRequest::requestAuthentication(const char *realm) const {
#endif #endif
AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) { AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
auto find = this->params_.find(name); // Check cache first - only successful lookups are cached
if (find != this->params_.end()) { for (auto *param : this->params_) {
return find->second; if (param->name() == name) {
return param;
}
} }
// Look up value from query strings
optional<std::string> val = query_key_value(this->post_query_, name); optional<std::string> val = query_key_value(this->post_query_, name);
if (!val.has_value()) { if (!val.has_value()) {
auto url_query = request_get_url_query(*this); auto url_query = request_get_url_query(*this);
@@ -278,11 +294,14 @@ AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) {
} }
} }
AsyncWebParameter *param = nullptr; // Don't cache misses to prevent memory exhaustion from malicious requests
if (val.has_value()) { // with thousands of non-existent parameter lookups
param = new AsyncWebParameter(val.value()); // NOLINT(cppcoreguidelines-owning-memory) if (!val.has_value()) {
return nullptr;
} }
this->params_.insert({name, param});
auto *param = new AsyncWebParameter(name, val.value()); // NOLINT(cppcoreguidelines-owning-memory)
this->params_.push_back(param);
return param; return param;
} }

View File

@@ -30,10 +30,12 @@ using String = std::string;
class AsyncWebParameter { class AsyncWebParameter {
public: public:
AsyncWebParameter(std::string value) : value_(std::move(value)) {} AsyncWebParameter(std::string name, std::string value) : name_(std::move(name)), value_(std::move(value)) {}
const std::string &name() const { return this->name_; }
const std::string &value() const { return this->value_; } const std::string &value() const { return this->value_; }
protected: protected:
std::string name_;
std::string value_; std::string value_;
}; };
@@ -174,7 +176,10 @@ class AsyncWebServerRequest {
protected: protected:
httpd_req_t *req_; httpd_req_t *req_;
AsyncWebServerResponse *rsp_{}; AsyncWebServerResponse *rsp_{};
std::map<std::string, AsyncWebParameter *> params_; // Use vector instead of map/unordered_map: most requests have 0-3 params, so linear search
// is faster than tree/hash overhead. AsyncWebParameter stores both name and value to avoid
// duplicate storage. Only successful lookups are cached to prevent memory exhaustion attacks.
std::vector<AsyncWebParameter *> params_;
std::string post_query_; std::string post_query_;
AsyncWebServerRequest(httpd_req_t *req) : req_(req) {} AsyncWebServerRequest(httpd_req_t *req) : req_(req) {}
AsyncWebServerRequest(httpd_req_t *req, std::string post_query) : req_(req), post_query_(std::move(post_query)) {} AsyncWebServerRequest(httpd_req_t *req, std::string post_query) : req_(req), post_query_(std::move(post_query)) {}