Removed multiple heap-allocated copies in parse_pm_content

- The previous version of this function was doing three strdup copies
  to parse the pm content. The updated version only copies the value
  once (in order not to modify the Operator's m_param member variable),
  and then performs the updates inline.
- Binary parsing was broken because digits were not compared as
  characters.
  - Fail parsing when an invalid hex character is found.
- Error message in parse_pm_content would reference freed memory if
  accessed by caller. Removed anyway because it was unused.
This commit is contained in:
Eduardo Arias 2024-08-09 13:01:37 -07:00
parent 97c8766ef1
commit 3e9d8107a8
6 changed files with 75 additions and 189 deletions

View File

@ -219,7 +219,6 @@ OPERATORS = \
operators/no_match.cc \
operators/operator.cc \
operators/pm.cc \
operators/pm_f.cc \
operators/pm_from_file.cc \
operators/rbl.cc \
operators/rsub.cc \

View File

@ -15,20 +15,80 @@
#include "src/operators/pm.h"
#include <string.h>
#include <string>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
#include <list>
#include <memory>
#include "src/operators/operator.h"
#include "src/utils/acmp.h"
#include "src/utils/string.h"
static inline std::string parse_pm_content(const std::string &op_parm) {
auto offset = op_parm.find_first_not_of(" \t");
if (offset == std::string::npos) {
return op_parm;
}
auto size = op_parm.size() - offset;
if (size >= 2 &&
op_parm.at(offset) == '\"' && op_parm.back() == '\"') {
offset++;
size -= 2;
}
if (size == 0) {
return op_parm;
}
std::string parsed_parm(op_parm.c_str() + offset, size);
unsigned char bin_offset = 0;
unsigned char bin_parm[3] = { 0 };
bool bin = false, esc = false;
char *d = parsed_parm.data();
for(const char *s = d, *e = d + size; s != e; ++s) {
if (*s == '|') {
bin = !bin;
} else if(!esc && *s == '\\') {
esc = true;
} else {
if (bin) {
if (VALID_HEX(*s)) {
bin_parm[bin_offset] = (char)*s;
bin_offset++;
if (bin_offset == 2) {
unsigned char c = strtol((char *)bin_parm, (char **) nullptr, 16) & 0xFF;
bin_offset = 0;
*d++ = c;
}
} else {
// Invalid hex character
return op_parm;
}
} else if (esc) {
if (*s == ':' ||
*s == ';' ||
*s == '\\' ||
*s == '\"')
{
*d++ = *s;
} else {
// Unsupported escape sequence
return op_parm;
}
esc = false;
} else {
*d++ = *s;
}
}
}
parsed_parm.resize(d - parsed_parm.c_str());
return parsed_parm;
}
namespace modsecurity {
namespace operators {
@ -105,36 +165,19 @@ bool Pm::evaluate(Transaction *transaction, RuleWithActions *rule,
bool Pm::init(const std::string &file, std::string *error) {
std::vector<std::string> vec;
std::istringstream *iss;
const char *err = NULL;
const auto op_parm = parse_pm_content(m_param);
char *content = parse_pm_content(m_param.c_str(), m_param.length(), &err);
if (content == NULL) {
iss = new std::istringstream(m_param);
} else {
iss = new std::istringstream(content);
}
std::copy(std::istream_iterator<std::string>(*iss),
std::istringstream iss{op_parm};
std::for_each(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>(),
back_inserter(vec));
for (auto &a : vec) {
acmp_add_pattern(m_p, a.c_str(), NULL, NULL, a.length());
}
[this](const auto &a) {
acmp_add_pattern(m_p, a.c_str(), NULL, NULL, a.length());
});
while (m_p->is_failtree_done == 0) {
acmp_prepare(m_p);
}
if (content) {
free(content);
content = NULL;
}
delete iss;
return true;
}

View File

@ -17,7 +17,6 @@
#define SRC_OPERATORS_PM_H_
#include <string>
#include <list>
#include <memory>
#include <utility>
#include <mutex>

View File

@ -1,27 +0,0 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* If any of the files related to licensing are missing or if you have any
* other questions related to licensing please contact Trustwave Holdings, Inc.
* directly using the email address security@modsecurity.org.
*
*/
#include "src/operators/pm_f.h"
#include <string>
#include "src/operators/pm_from_file.h"
namespace modsecurity {
namespace operators {
} // namespace operators
} // namespace modsecurity

View File

@ -33,136 +33,9 @@
* that should be mitigated. This ACMP parser should be re-written to
* consume less memory.
*/
extern "C" {
char *parse_pm_content(const char *op_parm, unsigned short int op_len, const char **error_msg) {
char *parm = NULL;
char *content;
unsigned short int offset = 0;
// char converted = 0;
int i, x;
unsigned char bin = 0, esc = 0, bin_offset = 0;
unsigned char c = 0;
unsigned char bin_parm[3] = { 0 };
char *processed = NULL;
content = strdup(op_parm);
if (content == NULL) {
*error_msg = std::string("Error allocating memory for pattern matching content.").c_str();
return NULL;
}
while (offset < op_len && (content[offset] == ' ' || content[offset] == '\t')) {
offset++;
};
op_len = strlen(content);
if (content[offset] == '\"' && content[op_len-1] == '\"') {
parm = strdup(content + offset + 1);
if (parm == NULL) {
*error_msg = std::string("Error allocating memory for pattern matching content.").c_str();
free(content);
content = NULL;
return NULL;
}
parm[op_len - offset - 2] = '\0';
} else {
parm = strdup(content + offset);
if (parm == NULL) {
free(content);
content = NULL;
*error_msg = std::string("Error allocating memory for pattern matching content.").c_str();
return NULL;
}
}
free(content);
content = NULL;
op_len = strlen(parm);
if (op_len == 0) {
*error_msg = "Content length is 0.";
free(parm);
return NULL;
}
for (i = 0, x = 0; i < op_len; i++) {
if (parm[i] == '|') {
if (bin) {
bin = 0;
} else {
bin = 1;
}
} else if(!esc && parm[i] == '\\') {
esc = 1;
} else {
if (bin) {
if (parm[i] == 0 || parm[i] == 1 || parm[i] == 2 ||
parm[i] == 3 || parm[i] == 4 || parm[i] == 5 ||
parm[i] == 6 || parm[i] == 7 || parm[i] == 8 ||
parm[i] == 9 ||
parm[i] == 'A' || parm[i] == 'a' ||
parm[i] == 'B' || parm[i] == 'b' ||
parm[i] == 'C' || parm[i] == 'c' ||
parm[i] == 'D' || parm[i] == 'd' ||
parm[i] == 'E' || parm[i] == 'e' ||
parm[i] == 'F' || parm[i] == 'f')
{
bin_parm[bin_offset] = (char)parm[i];
bin_offset++;
if (bin_offset == 2) {
c = strtol((char *)bin_parm, (char **) NULL, 16) & 0xFF;
bin_offset = 0;
parm[x] = c;
x++;
//converted = 1;
}
} else if (parm[i] == ' ') {
}
} else if (esc) {
if (parm[i] == ':' ||
parm[i] == ';' ||
parm[i] == '\\' ||
parm[i] == '\"')
{
parm[x] = parm[i];
x++;
} else {
*error_msg = std::string("Unsupported escape sequence.").c_str();
free(parm);
return NULL;
}
esc = 0;
//converted = 1;
} else {
parm[x] = parm[i];
x++;
}
}
}
#if 0
if (converted) {
op_len = x;
}
#endif
//processed = memcpy(processed, parm, op_len);
processed = strdup(parm);
free(parm);
parm = NULL;
if (processed == NULL) {
*error_msg = std::string("Error allocating memory for pattern matching content.").c_str();
return NULL;
}
return processed;
}
/*
*******************************************************************************
*******************************************************************************

View File

@ -23,6 +23,7 @@
#endif
#include <cstddef>
#include <string>
extern "C" {
@ -189,8 +190,6 @@ int acmp_process_quick(ACMPT *acmpt, const char **match, const char *data, size_
*/
int acmp_prepare(ACMP *parser);
char *parse_pm_content(const char *op_parm, unsigned short int op_len, const char **error_msg);
}
#endif /*ACMP_H_*/